十面埋伏分析.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. const logger = require("./utils/logger");
  2. const NumKit = require('./utils/num-kit')
  3. const TimeKit = require('./utils/time-kit')
  4. const ChartKit = require('./utils/chart-kit');
  5. const fs = require('fs').promises;
  6. async function readData() {
  7. let data = await fs.readFile('./data/k_lines.json', 'utf8')
  8. return JSON.parse(data)
  9. }
  10. /* 概率学说 */
  11. // 统计过去N根累计涨幅,【有一定关系】
  12. function statisticA(kLines, index) {
  13. let startK = kLines[index - 12]
  14. let endK = kLines[index - 1]
  15. if (!startK || !endK) {
  16. return 0
  17. }
  18. return parseInt(100 * (endK.Close - startK.Open) / startK.Open) + 100
  19. }
  20. // 返回过去一共NK涨幅超过M%【这个有关的】
  21. function statisticB(kLines, index) {
  22. let count = 0
  23. for (let i = index - 1; i >= index - 2; i--) {
  24. let kLine = kLines[i]
  25. if (!kLines[i - 1]) break
  26. if (100 * (kLine.Close - kLine.Open) / kLine.Open < 1) continue
  27. count += 1
  28. }
  29. return count
  30. }
  31. /* 指标学说 */
  32. // 一个新的指标,前N根共有M根与BTC走势一致
  33. function statisticC(kLines, index, btcKLines, btcIndex) {
  34. let count = 0
  35. for (let i = 6; i >= 1; i--) {
  36. let kLine = kLines[index - i]
  37. let btcKLine = btcKLines[btcIndex - i]
  38. if (!kLines[index - i - 1] || !btcKLines[btcIndex - i - 1]) break
  39. if (kLine.Open > kLine.Close && btcKLine.Open > btcKLine.Close) count += 1
  40. if (kLine.Open < kLine.Close && btcKLine.Open < btcKLine.Close) count += 1
  41. }
  42. return count
  43. }
  44. /* 形态学说 */
  45. // 如果前一根k线是阴线,并且收了影线,影线涨幅>=实体跌幅*0.732【有用,能过滤60%的交易,但是过滤的交易很多都是能盈利的,所以这个因子只能说保留一下】
  46. // function statisticD(_kLines, _index) {
  47. // let k = kLines[index - 1]
  48. //
  49. // if (!k) return 0
  50. //
  51. // // 计算实体的跌幅
  52. // const bodyDecline = k.Open - k.Close;
  53. //
  54. // // 如果跌幅小于5%,不适用这个过滤
  55. // if (100 * (k.Low - k.Open) / k.Open > -10) return 1
  56. //
  57. // // 计算下影线的涨幅
  58. // const lowerShadowGain = Math.min(k.Open, k.Close) - k.Low;
  59. //
  60. // // 判断下影线的涨幅是否大于或等于实体的跌幅
  61. // const hasLongLowerShadow = (lowerShadowGain / k.Low) >= (bodyDecline / k.Open) * 1;
  62. //
  63. // // 阴线并且下影线涨幅大于或等于实体跌幅
  64. // return hasLongLowerShadow ? 1 : 0;
  65. //
  66. // return 0
  67. // }
  68. // function statisticE(kLines, index) {
  69. // let kLine = kLines[index]
  70. // let date = new Date(kLine.Time)
  71. //
  72. // // date.getDay() * 100
  73. // return date.getHours()
  74. // }
  75. // 过去24小时主动买入比例
  76. // function statisticF(kLines, index) {
  77. // let sum = 0
  78. // let buySum = 0
  79. // for (let i = index - 1; i >= index - 24; i--) {
  80. // let kLine = kLines[i]
  81. //
  82. // if (!kLines[i - 1]) break
  83. //
  84. // sum += kLine.Turnover
  85. // buySum += kLine.BuyTurnover
  86. // }
  87. //
  88. // return parseInt(100 * buySum / sum)
  89. // }
  90. // 指标过滤
  91. function filter(btcKLines, kLines, index, btcIndex, symbol) {
  92. // 过去N根K的累计涨幅
  93. let upRateN = statisticA(kLines, index)
  94. if (upRateN >= 90) {
  95. return false
  96. }
  97. // 一共NK涨幅超过M%
  98. if (statisticB(kLines, index) > 0) {
  99. return false
  100. }
  101. // 过去N根k线一共有M根与btc走势一致
  102. if (statisticC(kLines, index, btcKLines, btcIndex) > 4) {
  103. return false
  104. }
  105. return NOOBS.indexOf(symbol) === -1;
  106. }
  107. let realCountMap = {}
  108. function getRealDragonMap(btcKLines, kLinesMap, dayCount, BUY_LIMIT_RATE) {
  109. let realDragonMap = {}
  110. for (let symbol in kLinesMap) {
  111. let kLines = kLinesMap[symbol]
  112. let index = kLines.length - (dayCount + 1)
  113. let btcIndex = btcKLines.length - (dayCount + 1)
  114. let kLine = kLines[index]
  115. let prevKline = kLines[index - 1]
  116. // 开盘第一K不计算
  117. if (!kLine || !prevKline) continue
  118. // 注意btc的时间戳要和计算的k的时间戳一样
  119. if (btcKLines[btcIndex].Time !== kLine.Time) continue
  120. // 指标过滤
  121. if (!filter(btcKLines, kLines, index, btcIndex, symbol)) continue
  122. let rate = 100 * (kLine.Close - kLine.Open) / kLine.Open
  123. // let upRate = 100 * (kLine.High - kLine.Open) / kLine.Open
  124. if (rate > BUY_LIMIT_RATE) {
  125. kLine.Rate = rate
  126. kLine.Profit = NumKit.getSubFloat(rate - BUY_LIMIT_RATE, 2)
  127. // if (upRate > 150) kLine.Profit = 140
  128. realDragonMap[symbol] = kLine
  129. if (!realCountMap[symbol]) {
  130. realCountMap[symbol] = kLine.Profit
  131. } else {
  132. realCountMap[symbol] += kLine.Profit
  133. }
  134. realCountMap[symbol] = NumKit.getSubFloat(realCountMap[symbol], 2)
  135. }
  136. }
  137. return realDragonMap
  138. }
  139. let fakeCountMap = {}
  140. function getFakeDragonMap(btcKLines, kLinesMap, dayCount, BUY_LIMIT_RATE) {
  141. let fakeDragonMap = {}
  142. for (let symbol in kLinesMap) {
  143. let kLines = kLinesMap[symbol]
  144. let index = kLines.length - (dayCount + 1)
  145. let btcIndex = btcKLines.length - (dayCount + 1)
  146. let kLine = kLines[index]
  147. let prevKline = kLines[index - 1]
  148. // 开盘第一K不计算
  149. if (!kLine || (!prevKline)) continue
  150. // 注意btc的时间戳要和计算的k的时间戳一样
  151. if (btcKLines[btcIndex].Time !== kLine.Time) continue
  152. // 指标过滤
  153. if (!filter(btcKLines, kLines, index, btcIndex, symbol)) continue
  154. let rate = 100 * (kLine.Close - kLine.Open) / kLine.Open
  155. if (rate < BUY_LIMIT_RATE) {
  156. kLine.Rate = rate
  157. // kLine.Profit = NumKit.getSubFloat(rate > 0 ? rate - BUY_LIMIT_RATE : -BUY_LIMIT_RATE, 2)
  158. kLine.Profit = NumKit.getSubFloat(rate - BUY_LIMIT_RATE, 2)
  159. fakeDragonMap[symbol] = kLine
  160. if (!fakeCountMap[symbol]) {
  161. fakeCountMap[symbol] = kLine.Profit
  162. } else {
  163. fakeCountMap[symbol] += kLine.Profit
  164. }
  165. fakeCountMap[symbol] = NumKit.getSubFloat(fakeCountMap[symbol], 2)
  166. }
  167. }
  168. return fakeDragonMap
  169. }
  170. let dataLeft = {}
  171. let dataRight = {}
  172. let tempLeft = []
  173. let tempRight = []
  174. let dataY = []
  175. function dragonAnalysis(btcKLines, kLinesMap, dragonMap, _dayCount) {
  176. for (let symbol in dragonMap) {
  177. // let kLines = kLinesMap[symbol]
  178. // let index = kLines.length - (dayCount + 1)
  179. // let x = statisticE(kLines, index)
  180. // dataY = [100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, 420, 422, 500, 502, 504, 506, 508, 510, 512, 514, 516, 518, 520, 522, 600, 602, 604, 606, 608, 610, 612, 614, 616, 618, 620, 622, 700, 702, 704, 706, 708, 710, 712, 714, 716, 718, 720, 722]
  181. // let x = statisticE(btcKLines, kLines, index)
  182. let x = symbol
  183. // let x = statisticF(kLines, index)
  184. let y = dragonMap[symbol].Profit
  185. // 打印实际的量
  186. if (1 === 1) {
  187. if (y > 0) {
  188. if (dataRight[x]) {
  189. dataRight[x] += y
  190. } else {
  191. dataRight[x] = y
  192. }
  193. } else {
  194. if (dataLeft[x]) {
  195. dataLeft[x] += y
  196. } else {
  197. dataLeft[x] = y
  198. }
  199. }
  200. } else {
  201. // 打印倍数
  202. if (y > 0) {
  203. if (tempRight[x]) {
  204. tempRight[x] += y
  205. } else {
  206. tempRight[x] = y
  207. }
  208. } else {
  209. if (tempLeft[x]) {
  210. tempLeft[x] += y
  211. } else {
  212. tempLeft[x] = y
  213. }
  214. }
  215. dataRight[x] = (tempLeft[x]?tempRight[x]/Math.abs(tempLeft[x]):0)
  216. if (dataRight[x] > 10) dataRight[x] = 10
  217. if (dataRight[x] < 0.5) dataRight[x] = 0.5
  218. }
  219. }
  220. }
  221. let NOOBS = ["ZRX_USDT","OGN_USDT","UMA_USDT","ACA_USDT","PROS_USDT","VANRY_USDT","SC_USDT","MKR_USDT","ASR_USDT","ACM_USDT","RAD_USDT","UFT_USDT","FDUSD_USDT","AEUR_USDT","PAXG_USDT","COS_USDT","CVC_USDT","RSR_USDT","SUN_USDT","BETA_USDT","ALPINE_USDT","REI_USDT","DODO_USDT","RARE_USDT","VGX_USDT","NULS_USDT","MTL_USDT","LSK_USDT","SUSHI_USDT","UNI_USDT","CTK_USDT","CELO_USDT","FIS_USDT","SLP_USDT","QNT_USDT","REQ_USDT","WAXP_USDT","DYDX_USDT","AGLD_USDT","PEOPLE_USDT","GAS_USDT","ID_USDT","FLOKI_USDT","MEME_USDT","XAI_USDT","PROM_USDT","CTXC_USDT","TKO_USDT","JUP_USDT","RONIN_USDT","VTHO_USDT","STRK_USDT","PDA_USDT","AXL_USDT","BOME_USDT","DREP_USDT","MOB_USDT","PNT_USDT"]
  222. // let NOOBS = []
  223. async function main() {
  224. let kLinesMap = await readData()
  225. const BUY_LIMIT_RATE = 0 // 从什么比例入场
  226. const FIRST_FEW_DAYS = 0 // 第几K的数据,0表示今K,1表示昨K,2表示前K,以此类推
  227. const BAKE_TEST_DAYS = 24 // 一共回测多少K
  228. // const FIRST_FEW_DAYS = 710 // 第几K的数据,0表示今K,1表示昨K,2表示前K,以此类推
  229. // const BAKE_TEST_DAYS = 1 // 一共回测多少K
  230. let profitList = []
  231. let initProfit = 1
  232. let profitCount = []
  233. let btcKLines = kLinesMap['BTC_USDT']
  234. let totalProfit = 0
  235. for (let day_count = FIRST_FEW_DAYS + BAKE_TEST_DAYS - 1; day_count > FIRST_FEW_DAYS; day_count--) {
  236. let btcKline = btcKLines[btcKLines.length - (day_count + 1)]
  237. // let btcKlineDownRate = 100 * (btcKline.Open - btcKline.Low) / btcKline.Open // 可能的回撤计算
  238. let btcKlineUpRate = 100 * (btcKline.High - btcKline.Close) / btcKline.High // 可能的回撤计算
  239. // 赚钱榜
  240. // logger.info("----------------赚钱榜数据分析----------------")
  241. let realDragonProfit = 0
  242. let realDragonMap = getRealDragonMap(btcKLines, kLinesMap, day_count, BUY_LIMIT_RATE)
  243. for (let symbol in realDragonMap) {
  244. realDragonProfit += realDragonMap[symbol].Profit
  245. // logger.info(`${realDragonMap[symbol].Symbol}, ${realDragonMap[symbol].Profit}%`)
  246. }
  247. dragonAnalysis(btcKLines, kLinesMap, realDragonMap, day_count)
  248. // logger.info("----------------亏钱榜数据分析----------------")
  249. // 亏钱榜
  250. let fakeDragonProfit = 0
  251. let fakeDragonMap = getFakeDragonMap(btcKLines, kLinesMap, day_count, BUY_LIMIT_RATE)
  252. for (let symbol in fakeDragonMap) {
  253. fakeDragonProfit += fakeDragonMap[symbol].Profit
  254. // logger.info(`${fakeDragonMap[symbol].Symbol}, ${fakeDragonMap[symbol].Profit}%`)
  255. }
  256. dragonAnalysis(btcKLines, kLinesMap, fakeDragonMap, day_count)
  257. realDragonProfit = NumKit.getSubFloat(realDragonProfit, 2)
  258. fakeDragonProfit = NumKit.getSubFloat(fakeDragonProfit, 2)
  259. let realLength = Object.keys(realDragonMap).length
  260. let fakeLength = Object.keys(fakeDragonMap).length
  261. let avgProfit = NumKit.getSubFloat((fakeDragonProfit + realDragonProfit) / (realLength + fakeLength), 2)
  262. let isStopLoss = false
  263. if ((Object.keys(fakeDragonMap).length !== 0 || Object.keys(realDragonMap).length !== 0)) {
  264. if (btcKlineUpRate > 3.4) {
  265. avgProfit -= 6.8
  266. if (avgProfit < -6.8) avgProfit = -6.8
  267. isStopLoss = true
  268. }
  269. avgProfit = NumKit.getSubFloat(avgProfit - 0.1, 2) // 手续费算进去
  270. initProfit *= (100 + avgProfit) / 100
  271. profitList.push(initProfit)
  272. profitCount.push(profitList.length)
  273. } else {
  274. avgProfit = 0
  275. }
  276. let index = kLinesMap['BTC_USDT'].length - (day_count + 1)
  277. let btcK = kLinesMap['BTC_USDT'][index]
  278. let dateStr = TimeKit.getTimeByMillisecond(btcK.Time)
  279. // let _btcUpRate = NumKit.getSubFloat(100 * (btcK.Close - btcK.Open) / btcK.Open, 2)
  280. logger.info(`${day_count}根(${dateStr}), ${realLength + fakeLength}只, 平均每只利润${avgProfit}% ${isStopLoss ? '(止损)' : ''},`
  281. + `赚钱(${realLength}只)利润${realDragonProfit}%,亏钱(${fakeLength}只)利润${fakeDragonProfit}%`
  282. // + `,BTC涨幅${btcUpRate}%`
  283. )
  284. logger.info('')
  285. logger.info('--------------------------------------------------------------------------------------------')
  286. totalProfit += avgProfit
  287. totalProfit = NumKit.getSubFloat(totalProfit, 2)
  288. }
  289. let dayProfit = NumKit.getSubFloat(totalProfit / BAKE_TEST_DAYS, 2)
  290. logger.info(JSON.stringify(profitList))
  291. logger.info(JSON.stringify(profitCount))
  292. logger.info(`利润期望值总和:${totalProfit}%,平均根化${dayProfit}%。`)
  293. // let lastData = []
  294. // for (let x = 0; x < dataRight.length; x++) {
  295. // lastData.push([x, dataRight[x]])
  296. // }
  297. // 这里可以生成noobs
  298. dataY = Object.keys(dataRight)
  299. let noobs = []
  300. for (let symbol in dataLeft) {
  301. if (dataLeft[symbol] + dataRight[symbol] < 0) {
  302. noobs.push(symbol)
  303. }
  304. }
  305. logger.info(JSON.stringify(noobs))
  306. // 如果key是字符串,就要加这些
  307. dataLeft = Object.values(dataLeft)
  308. dataRight = Object.values(dataRight)
  309. let rst = 'option=' + JSON.stringify(ChartKit.printBar(dataY, dataLeft, dataRight), null, 2)
  310. // let rst = 'option=' + JSON.stringify(ChartKit.printPointChart(lastData), null, 2)
  311. require('fs').writeFile('./data/option.txt', rst, 'utf8', (err) => {
  312. if (err) {
  313. logger.error('写入文件时发生错误:', err);
  314. } else {
  315. logger.info('分析结果已写入到 ./data/option.txt');
  316. }
  317. });
  318. }
  319. main().catch((error) => {
  320. logger.error(error.stack);
  321. process.exitCode = 1;
  322. })