Browse Source

做空版本研究

skyffire 11 tháng trước cách đây
mục cha
commit
8d0a00eadf
2 tập tin đã thay đổi với 382 bổ sung1 xóa
  1. 1 1
      十面埋伏_LONG.js
  2. 381 0
      十面埋伏_SHORT.js

+ 1 - 1
十面埋伏_LONG.js

@@ -317,7 +317,7 @@ async function main() {
         isStopLoss = true
       }
 
-      avgProfit = NumKit.getSubFloat(avgProfit - 0.1, 2)    // 手续费算进去
+      avgProfit = NumKit.getSubFloat(avgProfit - 0.05, 2)    // 手续费算进去
       initProfit *= (100 + avgProfit) / 100
       profitList.push(initProfit)
       profitCount.push(profitList.length)

+ 381 - 0
十面埋伏_SHORT.js

@@ -0,0 +1,381 @@
+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.Open - kLine.Close) / 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.Open - kLine.Close) / 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.Open - kLine.Close) / 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 = []
+// 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.05, 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;
+})