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) } let realCountMap = {} function getRealDragonMap(kLinesMap, dayCount, BUY_LIMIT_RATE) { let realDragonMap = {} for (let symbol in kLinesMap) { let kLines = kLinesMap[symbol] let index = kLines.length - (dayCount + 1) let kLine = kLines[index] let prevKline = kLines[index - 1] // 开盘第一天不计算 if (!kLine || !prevKline) continue let rate = 100 * (kLine.Close - kLine.Open) / kLine.Open if (rate > BUY_LIMIT_RATE) { kLine.Rate = rate kLine.Profit = NumKit.getSubFloat(rate - BUY_LIMIT_RATE, 2) 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(kLinesMap, dayCount, BUY_LIMIT_RATE) { let fakeDragonMap = {} for (let symbol in kLinesMap) { let kLines = kLinesMap[symbol] let index = kLines.length - (dayCount + 1) let kLine = kLines[index] let prevKline = kLines[index - 1] // 开盘第一天不计算 if (!kLine || (!prevKline)) continue let rate = 100 * (kLine.Close - kLine.Open) / kLine.Open let maxRate = 100 * (kLine.High - kLine.Open) / kLine.Open if (maxRate > BUY_LIMIT_RATE && rate < BUY_LIMIT_RATE) { kLine.Rate = rate kLine.MaxRate = maxRate // 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 } // 统计过去N日累计涨幅,跟这个无关 function statisticA(kLines, index) { let startK = kLines[index - 2] let endK = kLines[index - 1] if (!startK || !endK) { return 0 } let rst = NumKit.getSubFloat(100 * (endK.Close - startK.Open) / startK.Open, 2) if (rst > 30 || rst < -30) { return 0 } return rst } // 过去30天的平均振幅,跟这个无关 function statisticB(kLines, index) { let kCount = 0 let totalRiseAndFall = 0 for (let i = index - 1; i >= index - 30; i--) { let kLine = kLines[i] if (!kLines[i - 1]) break kCount += 1 totalRiseAndFall += (100 * (kLine.High - kLine.Low) / kLine.Low) } if (kCount === 0) { return 0 } return NumKit.getSubFloat(totalRiseAndFall / kCount, 2) } // 过去150天的平均(High - Close),跟这个无关 function statisticC(kLines, index) { let kCount = 0 let totalRiseAndFall = 0 for (let i = index - 1; i >= index - 150; i--) { let kLine = kLines[i] if (!kLines[i - 1]) break kCount += 1 totalRiseAndFall += (100 * (kLine.High - kLine.Close) / kLine.Close) } if (kCount === 0) { return 0 } return NumKit.getSubFloat(totalRiseAndFall / kCount, 2) } // 过去30天的平均上影线,跟这个无关 function statisticD(kLines, index) { let kCount = 0 let totalRiseAndFall = 0 for (let i = index - 1; i >= index - 30; i--) { let kLine = kLines[i] if (!kLines[i - 1]) break kCount += 1 let target = kLine.Close > kLine.Open ? kLine.Close : kLine.Open totalRiseAndFall += (100 * (kLine.High - target) / target) } if (kCount === 0) { return 0 } return NumKit.getSubFloat(totalRiseAndFall / kCount, 2) } // 过去30天的上影线幅度大于2.5%的次数,跟这个无关 function statisticE(kLines, index) { let kCount = 0 let count = 0 for (let i = index - 1; i >= index - 30; i--) { let kLine = kLines[i] if (!kLines[i - 1]) break kCount += 1 let target = kLine.Close > kLine.Open ? kLine.Close : kLine.Open let rise = (100 * (kLine.High - target) / target) count += (rise > 2.5 ? 1 : 0) } if (kCount === 0) { return 0 } return count } // 过去30天的平均交易额(百万),跟这个无关 function statisticF(kLines, index) { let kCount = 0 let rst = 0 for (let i = index - 1; i >= index - 30; i--) { let kLine = kLines[i] if (!kLines[i - 1]) break kCount += 1 rst += (kLine.Turnover / 1e6) } if (kCount === 0) { return 0 } return NumKit.getSubFloat(rst / kCount, 2) } // 过去30天的平均买入交易额(百万) function statisticG(kLines, index) { let kCount = 0 let rst = 0 for (let i = index - 1; i >= index - 30; i--) { let kLine = kLines[i] if (!kLines[i - 1]) break let rise = (kLine.BuyTurnover / 1e6) if (rise > 100) continue kCount += 1 rst += rise } if (kCount === 0) { return 0 } return NumKit.getSubFloat(rst / kCount, 2) } // 过去N天的平均买入交易额的比例 function statisticH(kLines, index) { let kCount = 0 let rst = 0 for (let i = index - 1; i >= index - 7; i--) { let kLine = kLines[i] if (!kLines[i - 1]) break let rise = 100 * (kLine.BuyTurnover / kLine.Turnover) kCount += 1 rst += rise } if (kCount === 0) { return 0 } return NumKit.getSubFloat(rst / kCount, 2) } let dataList = [] function dragonAnalysis(kLinesMap, dragonMap, dayCount) { for (let symbol in dragonMap) { let kLines = kLinesMap[symbol] let index = kLines.length - (dayCount + 1) let x = statisticA(kLines, index) let y = dragonMap[symbol].Profit // logger.info( // `${symbol}的分析(${y}%)` // // + `前7日累计涨幅${statisticA(kLines, index)}%。` // // + `前30日平均振幅${statisticB(kLines, index)}%。` // + `前30日平均(High - Close)${x}%。` // ) dataList.push([x, y]) } } async function main() { let kLinesMap = await readData() const FIRST_FEW_DAYS = 10 // 第几天的数据,0表示今天,1表示昨天,2表示前天,以此类推 const BUY_LIMIT_RATE = 2.5 // 从什么比例入场 const BAKE_TEST_DAYS = 10 // 一共回测多少天 let totalProfit = 0 for (let day_count = FIRST_FEW_DAYS; day_count < FIRST_FEW_DAYS + BAKE_TEST_DAYS; day_count++) { // 赚钱榜 let realDragonProfit = 0 let realDragonMap = getRealDragonMap(kLinesMap, day_count, BUY_LIMIT_RATE) for (let symbol in realDragonMap) { realDragonProfit += realDragonMap[symbol].Profit // logger.info(realDragonMap[symbol].Symbol, realDragonMap[symbol].Profit, realDragonMap[symbol].Rate) } logger.info("----------------赚钱榜数据分析----------------") dragonAnalysis(kLinesMap, realDragonMap, day_count) // 亏钱榜 let fakeDragonProfit = 0 let fakeDragonMap = getFakeDragonMap(kLinesMap, day_count, BUY_LIMIT_RATE) for (let symbol in fakeDragonMap) { fakeDragonProfit += fakeDragonMap[symbol].Profit // logger.info(fakeDragonMap[symbol].Symbol, fakeDragonMap[symbol].Profit, fakeDragonMap[symbol].MaxRate, fakeDragonMap[symbol].Rate) } logger.info("----------------亏钱榜数据分析----------------") dragonAnalysis(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 realRate = NumKit.getSubFloat(realLength / (realLength + fakeLength), 2) let fakeRate = NumKit.getSubFloat(1 - realRate, 2) let expRealProfit = NumKit.getSubFloat(realRate * realDragonProfit, 2) let expFakeProfit = NumKit.getSubFloat(fakeRate * fakeDragonProfit, 2) let synProfit = NumKit.getSubFloat(expFakeProfit + expRealProfit, 2) let avgProfit = NumKit.getSubFloat(synProfit / (realLength + fakeLength), 2) let index = kLinesMap['BTC_USDT'].length - (day_count + 1) let btcK = kLinesMap['BTC_USDT'][index] let dateStr = TimeKit.getDateByMillisecond(btcK.Time) let btcUpRate = NumKit.getSubFloat(100 * (btcK.Close - btcK.Open) / btcK.Open, 2) logger.info(`${day_count}日(${dateStr}, ${realLength + fakeLength}只),赚钱榜(${realLength}只)利润${realDragonProfit}%` + `,亏钱榜(${fakeLength}只)利润${fakeDragonProfit}%` + `,赚钱榜期望利润${expRealProfit}%,亏钱榜期望利润${expFakeProfit}%,综合利润${synProfit}%,平均每只利润${avgProfit}%,BTC涨幅${btcUpRate}%` ) totalProfit += avgProfit totalProfit = NumKit.getSubFloat(totalProfit, 2) } logger.info(`${dataList.length}`) ChartKit.printChart(dataList) // let dayProfit = NumKit.getSubFloat(totalProfit / BAKE_TEST_DAYS, 2) // logger.info(`利润期望值总和:${totalProfit}%,平均日化${dayProfit}%。`) // for (let symbol in fakeCountMap) { // let kLines = kLinesMap[symbol] // let startIndex = kLines.length - BAKE_TEST_DAYS // let endIndex = kLines.length - 1 // // logger.info(`${symbol} 盈${realCountMap[symbol]},亏${fakeCountMap[symbol]},大于5%涨幅的共有${statisticA(kLines, startIndex, endIndex)}日。`) // } } main().catch((error) => { logger.error(error.stack); process.exitCode = 1; })