| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- 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小时成交额度之和。大于1B用1000表示,小于10M用0表示,没啥意义
- // function statisticF(kLines, index) {
- // let sum = 0
- // for (let i = index - 1; i >= index - 12; i--) {
- // let kLine = kLines[i]
- //
- // if (!kLines[i - 1]) break
- //
- // sum += kLine.Turnover
- // }
- //
- // let rst = parseInt(sum / 1e7)
- // return rst > 100 ? 100 : rst
- // }
- // 指标过滤
- function filter(btcKLines, kLines, index, btcIndex, symbol) {
- if (symbol === 'PDA_USDT') {
- logger.info(kLines[index], btcKLines[index], index, btcIndex)
- }
- // 过去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 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 = []
- 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 = 720 // 一共回测多少K
- // const FIRST_FEW_DAYS = 710 // 第几K的数据,0表示今K,1表示昨K,2表示前K,以此类推
- // const BAKE_TEST_DAYS = 1 // 一共回测多少K
- let btcKLines = kLinesMap['BTC_USDT']
- let totalProfit = 0
- for (let day_count = FIRST_FEW_DAYS; day_count < FIRST_FEW_DAYS + BAKE_TEST_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 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 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
- } else if (avgProfit < -16.8) {
- avgProfit = -16.8
- }
- } 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}%`
- + `,赚钱榜期望利润${expRealProfit}%,亏钱榜期望利润${expFakeProfit}%,综合利润${synProfit}%`
- // + `,BTC涨幅${btcUpRate}%`
- )
- logger.info('')
- logger.info('--------------------------------------------------------------------------------------------')
- totalProfit += avgProfit
- totalProfit = NumKit.getSubFloat(totalProfit, 2)
- }
- let dayProfit = NumKit.getSubFloat(totalProfit / BAKE_TEST_DAYS, 2)
- 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;
- })
|