const logger = require("./utils/logger"); const NumKit = require('./utils/num-kit') const TimeKit = require('./utils/time-kit') const ChartKit = require('./utils/chart-kit'); const fs = require('fs').promises; async function readData() { let data = await fs.readFile('./data/k_lines.json', 'utf8') return JSON.parse(data) } /* 概率学说 */ // 统计过去N根累计涨幅,【有一定关系】 function statisticA(kLines, index) { let startK = kLines[index - 12] let endK = kLines[index - 1] if (!startK || !endK) { return 0 } return parseInt(100 * (endK.Close - startK.Open) / startK.Open) + 100 } // 返回过去一共NK涨幅超过M%【这个有关的】 function statisticB(kLines, index) { let count = 0 for (let i = index - 1; i >= index - 2; i--) { let kLine = kLines[i] if (!kLines[i - 1]) break if (100 * (kLine.Close - kLine.Open) / kLine.Open < 1) continue count += 1 } return count } /* 指标学说 */ // 一个新的指标,前N根共有M根与BTC走势一致 function statisticC(kLines, index, btcKLines, btcIndex) { let count = 0 for (let i = 6; i >= 1; i--) { let kLine = kLines[index - i] let btcKLine = btcKLines[btcIndex - i] if (!kLines[index - i - 1] || !btcKLines[btcIndex - i - 1]) break if (kLine.Open > kLine.Close && btcKLine.Open > btcKLine.Close) count += 1 if (kLine.Open < kLine.Close && btcKLine.Open < btcKLine.Close) count += 1 } return count } /* 形态学说 */ // 如果前一根k线是阴线,并且收了影线,影线涨幅>=实体跌幅*0.732【有用,能过滤60%的交易,但是过滤的交易很多都是能盈利的,所以这个因子只能说保留一下】 // function statisticD(_kLines, _index) { // let k = kLines[index - 1] // // if (!k) return 0 // // // 计算实体的跌幅 // const bodyDecline = k.Open - k.Close; // // // 如果跌幅小于5%,不适用这个过滤 // if (100 * (k.Low - k.Open) / k.Open > -10) return 1 // // // 计算下影线的涨幅 // const lowerShadowGain = Math.min(k.Open, k.Close) - k.Low; // // // 判断下影线的涨幅是否大于或等于实体的跌幅 // const hasLongLowerShadow = (lowerShadowGain / k.Low) >= (bodyDecline / k.Open) * 1; // // // 阴线并且下影线涨幅大于或等于实体跌幅 // return hasLongLowerShadow ? 1 : 0; // // return 0 // } // function statisticE(kLines, index) { // let kLine = kLines[index] // let date = new Date(kLine.Time) // // // date.getDay() * 100 // return date.getHours() // } // 过去24小时主动买入比例 // function statisticF(kLines, index) { // let sum = 0 // let buySum = 0 // for (let i = index - 1; i >= index - 24; i--) { // let kLine = kLines[i] // // if (!kLines[i - 1]) break // // sum += kLine.Turnover // buySum += kLine.BuyTurnover // } // // return parseInt(100 * buySum / sum) // } // 指标过滤 function filter(btcKLines, kLines, index, btcIndex, symbol) { // 过去N根K的累计涨幅 let upRateN = statisticA(kLines, index) if (upRateN >= 90) { return false } // 一共NK涨幅超过M% if (statisticB(kLines, index) > 0) { return false } // 过去N根k线一共有M根与btc走势一致 if (statisticC(kLines, index, btcKLines, btcIndex) > 4) { return false } return NOOBS.indexOf(symbol) === -1; } let realCountMap = {} function getRealDragonMap(btcKLines, kLinesMap, dayCount, BUY_LIMIT_RATE) { let realDragonMap = {} for (let symbol in kLinesMap) { let kLines = kLinesMap[symbol] let index = kLines.length - (dayCount + 1) let btcIndex = btcKLines.length - (dayCount + 1) let kLine = kLines[index] let prevKline = kLines[index - 1] // 开盘第一K不计算 if (!kLine || !prevKline) continue // 注意btc的时间戳要和计算的k的时间戳一样 if (btcKLines[btcIndex].Time !== kLine.Time) continue // 指标过滤 if (!filter(btcKLines, kLines, index, btcIndex, symbol)) continue let rate = 100 * (kLine.Close - kLine.Open) / kLine.Open // let upRate = 100 * (kLine.High - kLine.Open) / kLine.Open if (rate > BUY_LIMIT_RATE) { kLine.Rate = rate kLine.Profit = NumKit.getSubFloat(rate - BUY_LIMIT_RATE, 2) // if (upRate > 150) kLine.Profit = 140 realDragonMap[symbol] = kLine if (!realCountMap[symbol]) { realCountMap[symbol] = kLine.Profit } else { realCountMap[symbol] += kLine.Profit } realCountMap[symbol] = NumKit.getSubFloat(realCountMap[symbol], 2) } } return realDragonMap } let fakeCountMap = {} function getFakeDragonMap(btcKLines, kLinesMap, dayCount, BUY_LIMIT_RATE) { let fakeDragonMap = {} for (let symbol in kLinesMap) { let kLines = kLinesMap[symbol] let index = kLines.length - (dayCount + 1) let btcIndex = btcKLines.length - (dayCount + 1) let kLine = kLines[index] let prevKline = kLines[index - 1] // 开盘第一K不计算 if (!kLine || (!prevKline)) continue // 注意btc的时间戳要和计算的k的时间戳一样 if (btcKLines[btcIndex].Time !== kLine.Time) continue // 指标过滤 if (!filter(btcKLines, kLines, index, btcIndex, symbol)) continue let rate = 100 * (kLine.Close - kLine.Open) / kLine.Open if (rate < BUY_LIMIT_RATE) { kLine.Rate = rate // kLine.Profit = NumKit.getSubFloat(rate > 0 ? rate - BUY_LIMIT_RATE : -BUY_LIMIT_RATE, 2) kLine.Profit = NumKit.getSubFloat(rate - BUY_LIMIT_RATE, 2) fakeDragonMap[symbol] = kLine if (!fakeCountMap[symbol]) { fakeCountMap[symbol] = kLine.Profit } else { fakeCountMap[symbol] += kLine.Profit } fakeCountMap[symbol] = NumKit.getSubFloat(fakeCountMap[symbol], 2) } } return fakeDragonMap } let dataLeft = {} let dataRight = {} let tempLeft = [] let tempRight = [] let dataY = [] function dragonAnalysis(btcKLines, kLinesMap, dragonMap, _dayCount) { for (let symbol in dragonMap) { // let kLines = kLinesMap[symbol] // let index = kLines.length - (dayCount + 1) // let x = statisticE(kLines, index) // 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] // let x = statisticE(btcKLines, kLines, index) let x = symbol // let x = statisticF(kLines, index) let y = dragonMap[symbol].Profit // 打印实际的量 if (1 === 1) { if (y > 0) { if (dataRight[x]) { dataRight[x] += y } else { dataRight[x] = y } } else { if (dataLeft[x]) { dataLeft[x] += y } else { dataLeft[x] = y } } } else { // 打印倍数 if (y > 0) { if (tempRight[x]) { tempRight[x] += y } else { tempRight[x] = y } } else { if (tempLeft[x]) { tempLeft[x] += y } else { tempLeft[x] = y } } dataRight[x] = (tempLeft[x]?tempRight[x]/Math.abs(tempLeft[x]):0) if (dataRight[x] > 10) dataRight[x] = 10 if (dataRight[x] < 0.5) dataRight[x] = 0.5 } } } let NOOBS = ["BTC_USDT","BNB_USDT","ATOM_USDT","DUSK_USDT","TROY_USDT","LRC_USDT","MKR_USDT","NMR_USDT","DIA_USDT","1INCH_USDT","OM_USDT","ICP_USDT","GNO_USDT","ADX_USDT","CITY_USDT","API3_USDT","LDO_USDT","STG_USDT","MAGIC_USDT","PROS_USDT","GNS_USDT","WBTC_USDT","FLOKI_USDT","ARKM_USDT","WLD_USDT","FDUSD_USDT","1000SATS_USDT","PYTH_USDT","TAO_USDT","RENDER_USDT","FET_USDT","FTM_USDT","COS_USDT","CVC_USDT","VITE_USDT","SOL_USDT","STMX_USDT","SXP_USDT","YFI_USDT","RSR_USDT","PAXG_USDT","RUNE_USDT","SFP_USDT","CAKE_USDT","POND_USDT","CFX_USDT","LPT_USDT","FLOW_USDT","ENS_USDT","JASMY_USDT","SANTOS_USDT","VOXEL_USDT","GMT_USDT","KDA_USDT","PHB_USDT","RPL_USDT","SSV_USDT","GAS_USDT","OAX_USDT","SNT_USDT","TIA_USDT","ZRO_USDT","BNSOL_USDT","SYN_USDT","1MBABYDOGE_USDT","OP_USDT","AI_USDT","LUMIA_USDT","USDC_USDT","PNUT_USDT"] // let NOOBS = [] async function main() { let kLinesMap = await readData() const BUY_LIMIT_RATE = 0 // 从什么比例入场 const FIRST_FEW_DAYS = 0 // 第几K的数据,0表示今K,1表示昨K,2表示前K,以此类推 const BAKE_TEST_DAYS = 144 // 一共回测多少K // const FIRST_FEW_DAYS = 710 // 第几K的数据,0表示今K,1表示昨K,2表示前K,以此类推 // const BAKE_TEST_DAYS = 1 // 一共回测多少K let profitList = [] let initProfit = 1 let profitCount = [] let btcKLines = kLinesMap['BTC_USDT'] let totalProfit = 0 for (let day_count = FIRST_FEW_DAYS + BAKE_TEST_DAYS - 1; day_count > FIRST_FEW_DAYS; day_count--) { let btcKline = btcKLines[btcKLines.length - (day_count + 1)] // let btcKlineDownRate = 100 * (btcKline.Open - btcKline.Low) / btcKline.Open // 可能的回撤计算 let btcKlineUpRate = 100 * (btcKline.High - btcKline.Close) / btcKline.High // 可能的回撤计算 // 赚钱榜 // logger.info("----------------赚钱榜数据分析----------------") let realDragonProfit = 0 let realDragonMap = getRealDragonMap(btcKLines, kLinesMap, day_count, BUY_LIMIT_RATE) for (let symbol in realDragonMap) { realDragonProfit += realDragonMap[symbol].Profit // logger.info(`${realDragonMap[symbol].Symbol}, ${realDragonMap[symbol].Profit}%`) } dragonAnalysis(btcKLines, kLinesMap, realDragonMap, day_count) // logger.info("----------------亏钱榜数据分析----------------") // 亏钱榜 let fakeDragonProfit = 0 let fakeDragonMap = getFakeDragonMap(btcKLines, kLinesMap, day_count, BUY_LIMIT_RATE) for (let symbol in fakeDragonMap) { fakeDragonProfit += fakeDragonMap[symbol].Profit // logger.info(`${fakeDragonMap[symbol].Symbol}, ${fakeDragonMap[symbol].Profit}%`) } dragonAnalysis(btcKLines, kLinesMap, fakeDragonMap, day_count) realDragonProfit = NumKit.getSubFloat(realDragonProfit, 2) fakeDragonProfit = NumKit.getSubFloat(fakeDragonProfit, 2) let realLength = Object.keys(realDragonMap).length let fakeLength = Object.keys(fakeDragonMap).length let avgProfit = NumKit.getSubFloat((fakeDragonProfit + realDragonProfit) / (realLength + fakeLength), 2) let isStopLoss = false if ((Object.keys(fakeDragonMap).length !== 0 || Object.keys(realDragonMap).length !== 0)) { if (btcKlineUpRate > 3.4) { avgProfit -= 6.8 if (avgProfit < -6.8) avgProfit = -6.8 isStopLoss = true } avgProfit = NumKit.getSubFloat(avgProfit - 0.1, 2) // 手续费算进去 initProfit *= (100 + avgProfit) / 100 profitList.push(initProfit) profitCount.push(profitList.length) } else { avgProfit = 0 } let index = kLinesMap['BTC_USDT'].length - (day_count + 1) let btcK = kLinesMap['BTC_USDT'][index] let dateStr = TimeKit.getTimeByMillisecond(btcK.Time) // let _btcUpRate = NumKit.getSubFloat(100 * (btcK.Close - btcK.Open) / btcK.Open, 2) logger.info(`${day_count}根(${dateStr}), ${realLength + fakeLength}只, 平均每只利润${avgProfit}% ${isStopLoss ? '(止损)' : ''},` + `赚钱(${realLength}只)利润${realDragonProfit}%,亏钱(${fakeLength}只)利润${fakeDragonProfit}%` // + `,BTC涨幅${btcUpRate}%` ) logger.info('') logger.info('--------------------------------------------------------------------------------------------') totalProfit += avgProfit totalProfit = NumKit.getSubFloat(totalProfit, 2) } let dayProfit = NumKit.getSubFloat(totalProfit / BAKE_TEST_DAYS, 2) logger.info(JSON.stringify(profitList)) logger.info(JSON.stringify(profitCount)) logger.info(`利润期望值总和:${totalProfit}%,平均根化${dayProfit}%。`) // let lastData = [] // for (let x = 0; x < dataRight.length; x++) { // lastData.push([x, dataRight[x]]) // } // 这里可以生成noobs dataY = Object.keys(dataRight) let noobs = [] for (let symbol in dataLeft) { if (dataLeft[symbol] + dataRight[symbol] < 0) { noobs.push(symbol) } } logger.info(JSON.stringify(noobs)) // 如果key是字符串,就要加这些 dataLeft = Object.values(dataLeft) dataRight = Object.values(dataRight) let rst = 'option=' + JSON.stringify(ChartKit.printBar(dataY, dataLeft, dataRight), null, 2) // let rst = 'option=' + JSON.stringify(ChartKit.printPointChart(lastData), null, 2) require('fs').writeFile('./data/option.txt', rst, 'utf8', (err) => { if (err) { logger.error('写入文件时发生错误:', err); } else { logger.info('分析结果已写入到 ./data/option.txt'); } }); } main().catch((error) => { logger.error(error.stack); process.exitCode = 1; })