十面埋伏分析.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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日累计涨幅,【在-1%~-20%容易有亏损,有一定关系】
  12. function statisticA(kLines, index) {
  13. let startK = kLines[index - 10]
  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. // 返回过去一共N天涨幅超过M%【这个有关的】
  21. function statisticB(kLines, index) {
  22. let count = 0
  23. for (let i = index - 1; i >= index - 3; i--) {
  24. let kLine = kLines[i]
  25. if (!kLines[i - 1]) break
  26. if (100 * (kLine.Close - kLine.Open) / kLine.Open < 3) continue
  27. count += 1
  28. }
  29. return count
  30. }
  31. /* 形态学说 */
  32. // 如果前一根k线是阴线,并且收了影线,影线涨幅>=实体跌幅*0.732【有用,能过滤60%的交易,但是过滤的交易很多都是能盈利的,所以这个因子只能说保留一下】
  33. function statisticD(_kLines, _index) {
  34. // let k = kLines[index - 1]
  35. //
  36. // if (!k) return 0
  37. //
  38. // // 计算实体的跌幅
  39. // const bodyDecline = k.Open - k.Close;
  40. //
  41. // // 如果跌幅小于5%,不适用这个过滤
  42. // if (100 * (k.Low - k.Open) / k.Open > -10) return 1
  43. //
  44. // // 计算下影线的涨幅
  45. // const lowerShadowGain = Math.min(k.Open, k.Close) - k.Low;
  46. //
  47. // // 判断下影线的涨幅是否大于或等于实体的跌幅
  48. // const hasLongLowerShadow = (lowerShadowGain / k.Low) >= (bodyDecline / k.Open) * 1;
  49. //
  50. // // 阴线并且下影线涨幅大于或等于实体跌幅
  51. // return hasLongLowerShadow ? 1 : 0;
  52. return 0
  53. }
  54. // 现在应该想一个形态,能过滤一下熊市时的亏损,牛市时的回撤无所谓
  55. // function statisticC(kLines, index) {
  56. //
  57. // }
  58. // 指标过滤
  59. function filter(kLines, index) {
  60. // 过去N日累计涨幅,85表示亏15%,115表示赚15%,100是分界线
  61. let upRateN = statisticA(kLines, index)
  62. if (upRateN >= 85 && upRateN <= 100) {
  63. return false
  64. }
  65. // 一共N天涨幅超过M%
  66. if (statisticB(kLines, index) !== 0) {
  67. return false
  68. }
  69. return true
  70. }
  71. let realCountMap = {}
  72. function getRealDragonMap(kLinesMap, dayCount, BUY_LIMIT_RATE) {
  73. let realDragonMap = {}
  74. for (let symbol in kLinesMap) {
  75. let kLines = kLinesMap[symbol]
  76. let index = kLines.length - (dayCount + 1)
  77. let kLine = kLines[index]
  78. let prevKline = kLines[index - 1]
  79. // 开盘第一天不计算
  80. if (!kLine || !prevKline) continue
  81. // 指标过滤
  82. if (!filter(kLines, index)) continue
  83. let rate = 100 * (kLine.Close - kLine.Open) / kLine.Open
  84. // let upRate = 100 * (kLine.High - kLine.Open) / kLine.Open
  85. if (rate > BUY_LIMIT_RATE) {
  86. kLine.Rate = rate
  87. kLine.Profit = NumKit.getSubFloat(rate - BUY_LIMIT_RATE, 2)
  88. // if (upRate > 150) kLine.Profit = 140
  89. realDragonMap[symbol] = kLine
  90. if (!realCountMap[symbol]) {
  91. realCountMap[symbol] = kLine.Profit
  92. } else {
  93. realCountMap[symbol] += kLine.Profit
  94. }
  95. realCountMap[symbol] = NumKit.getSubFloat(realCountMap[symbol], 2)
  96. }
  97. }
  98. return realDragonMap
  99. }
  100. let fakeCountMap = {}
  101. function getFakeDragonMap(kLinesMap, dayCount, BUY_LIMIT_RATE) {
  102. let fakeDragonMap = {}
  103. for (let symbol in kLinesMap) {
  104. let kLines = kLinesMap[symbol]
  105. let index = kLines.length - (dayCount + 1)
  106. let kLine = kLines[index]
  107. let prevKline = kLines[index - 1]
  108. // 开盘第一天不计算
  109. if (!kLine || (!prevKline)) continue
  110. // 指标过滤
  111. if (!filter(kLines, index)) continue
  112. let rate = 100 * (kLine.Close - kLine.Open) / kLine.Open
  113. if (rate < BUY_LIMIT_RATE) {
  114. kLine.Rate = rate
  115. // kLine.Profit = NumKit.getSubFloat(rate > 0 ? rate - BUY_LIMIT_RATE : -BUY_LIMIT_RATE, 2)
  116. kLine.Profit = NumKit.getSubFloat(rate - BUY_LIMIT_RATE, 2)
  117. fakeDragonMap[symbol] = kLine
  118. if (!fakeCountMap[symbol]) {
  119. fakeCountMap[symbol] = kLine.Profit
  120. } else {
  121. fakeCountMap[symbol] += kLine.Profit
  122. }
  123. fakeCountMap[symbol] = NumKit.getSubFloat(fakeCountMap[symbol], 2)
  124. }
  125. }
  126. return fakeDragonMap
  127. }
  128. let dataLeft = []
  129. let dataRight = []
  130. let tempLeft = []
  131. let tempRight = []
  132. let dataY = [...Array(10).keys()]
  133. function dragonAnalysis(btcKLines, kLinesMap, dragonMap, dayCount) {
  134. for (let symbol in dragonMap) {
  135. let kLines = kLinesMap[symbol]
  136. let index = kLines.length - (dayCount + 1)
  137. let x = statisticD(kLines, index)
  138. let y = dragonMap[symbol].Profit
  139. // logger.info(
  140. // `${symbol}的分析(${y}%)`
  141. // // + `前7日累计涨幅${statisticA(kLines, index)}%。`
  142. // // + `前30日平均振幅${statisticB(kLines, index)}%。`
  143. // + `前30日平均(High - Close)${x}%。`
  144. // )
  145. // 打印实际的量
  146. // if (y > 0) {
  147. // if (dataRight[x]) {
  148. // dataRight[x] += y
  149. // } else {
  150. // dataRight[x] = y
  151. // }
  152. // } else {
  153. // if (dataLeft[x]) {
  154. // dataLeft[x] += y
  155. // } else {
  156. // dataLeft[x] = y
  157. // }
  158. // }
  159. // 打印倍数
  160. if (y > 0) {
  161. if (tempRight[x]) {
  162. tempRight[x] += y
  163. } else {
  164. tempRight[x] = y
  165. }
  166. } else {
  167. if (tempLeft[x]) {
  168. tempLeft[x] += y
  169. } else {
  170. tempLeft[x] = y
  171. }
  172. }
  173. dataRight[x] = (tempLeft[x]?tempRight[x]/Math.abs(tempLeft[x]):0)
  174. if (dataRight[x] > 10) dataRight[x] = 10
  175. if (dataRight[x] < 0.5) dataRight[x] = 0.5
  176. }
  177. }
  178. async function main() {
  179. let kLinesMap = await readData()
  180. const FIRST_FEW_DAYS = 1 // 第几天的数据,0表示今天,1表示昨天,2表示前天,以此类推
  181. const BUY_LIMIT_RATE = 0 // 从什么比例入场
  182. const BAKE_TEST_DAYS = 980 // 一共回测多少天, 150天是熊市最没有交易量的时候
  183. let btcKLines = kLinesMap['BTC_USDT']
  184. let totalProfit = 0
  185. for (let day_count = FIRST_FEW_DAYS; day_count < FIRST_FEW_DAYS + BAKE_TEST_DAYS; day_count++) {
  186. let btcKline = btcKLines[btcKLines.length - (day_count + 1)]
  187. let btcKlineDownRate = 100 * (btcKline.Close > btcKline.Open ? ((btcKline.Open - btcKline.Low) / btcKline.Open) : 0) // 计算如果是阳线时的下影线
  188. // 赚钱榜
  189. // logger.info("----------------赚钱榜数据分析----------------")
  190. let realDragonProfit = 0
  191. let realDragonMap = getRealDragonMap(kLinesMap, day_count, BUY_LIMIT_RATE)
  192. for (let symbol in realDragonMap) {
  193. realDragonProfit += realDragonMap[symbol].Profit
  194. // logger.info(`${realDragonMap[symbol].Symbol}, ${realDragonMap[symbol].Profit}%`)
  195. }
  196. dragonAnalysis(btcKLines, kLinesMap, realDragonMap, day_count)
  197. // logger.info("----------------亏钱榜数据分析----------------")
  198. // 亏钱榜
  199. let fakeDragonProfit = 0
  200. let fakeDragonMap = getFakeDragonMap(kLinesMap, day_count, BUY_LIMIT_RATE)
  201. for (let symbol in fakeDragonMap) {
  202. fakeDragonProfit += fakeDragonMap[symbol].Profit
  203. // logger.info(`${fakeDragonMap[symbol].Symbol}, ${fakeDragonMap[symbol].Profit}%`)
  204. }
  205. dragonAnalysis(btcKLines, kLinesMap, fakeDragonMap, day_count)
  206. realDragonProfit = NumKit.getSubFloat(realDragonProfit, 2)
  207. fakeDragonProfit = NumKit.getSubFloat(fakeDragonProfit, 2)
  208. let realLength = Object.keys(realDragonMap).length
  209. let fakeLength = Object.keys(fakeDragonMap).length
  210. let realRate = NumKit.getSubFloat(realLength / (realLength + fakeLength), 2)
  211. let fakeRate = NumKit.getSubFloat(1 - realRate, 2)
  212. let expRealProfit = NumKit.getSubFloat(realRate * realDragonProfit, 2)
  213. let expFakeProfit = NumKit.getSubFloat(fakeRate * fakeDragonProfit, 2)
  214. let synProfit = NumKit.getSubFloat(expFakeProfit + expRealProfit, 2)
  215. let avgProfit = NumKit.getSubFloat(synProfit / (realLength + fakeLength), 2)
  216. let isStopLoss = false
  217. if (avgProfit < -0.5 || btcKlineDownRate > 0.5) {
  218. avgProfit = -0.5
  219. isStopLoss = true
  220. }
  221. let index = kLinesMap['BTC_USDT'].length - (day_count + 1)
  222. let btcK = kLinesMap['BTC_USDT'][index]
  223. let dateStr = TimeKit.getDateByMillisecond(btcK.Time)
  224. let btcUpRate = NumKit.getSubFloat(100 * (btcK.Close - btcK.Open) / btcK.Open, 2)
  225. logger.info(`${day_count}日(${dateStr}, ${realLength + fakeLength}只),赚钱榜(${realLength}只)利润${realDragonProfit}%`
  226. + `,亏钱榜(${fakeLength}只)利润${fakeDragonProfit}%`
  227. + `,赚钱榜期望利润${expRealProfit}%,亏钱榜期望利润${expFakeProfit}%,综合利润${synProfit}%,平均每只利润${avgProfit}% ${isStopLoss ? '(止损)' : ''}`
  228. + `,BTC涨幅${btcUpRate}%`
  229. )
  230. totalProfit += avgProfit
  231. totalProfit = NumKit.getSubFloat(totalProfit, 2)
  232. }
  233. let dayProfit = NumKit.getSubFloat(totalProfit / BAKE_TEST_DAYS, 2)
  234. logger.info(`利润期望值总和:${totalProfit}%,平均日化${dayProfit}%。`)
  235. // let lastData = []
  236. // for (let x = 0; x < dataRight.length; x++) {
  237. // lastData.push([x, dataRight[x]])
  238. // }
  239. let rst = 'option=' + JSON.stringify(ChartKit.printBar(dataY, dataLeft, dataRight), null, 2)
  240. // let rst = 'option=' + JSON.stringify(ChartKit.printPointChart(lastData), null, 2)
  241. require('fs').writeFile('./data/option.txt', rst, 'utf8', (err) => {
  242. if (err) {
  243. logger.error('写入文件时发生错误:', err);
  244. } else {
  245. logger.info('分析结果已写入到 ./data/option.txt');
  246. }
  247. });
  248. }
  249. main().catch((error) => {
  250. logger.error(error.stack);
  251. process.exitCode = 1;
  252. })