one-pro.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. const OneTask = require('../libs/one-task')
  2. const OneInch = require('../libs/web3/1inch')
  3. const BinanceSpot = require("../libs/binance/binance-spot");
  4. const BinanceKit = require("../libs/binance/binance-kit");
  5. const NumKit = require('../kit/num-kit')
  6. const TimeKit = require('../kit/time-kit')
  7. const TableKit = require('../kit/table-kit')
  8. const Context = require("../libs/context")
  9. const assert = require("assert");
  10. const TickKit = require("../kit/tick-kit");
  11. const holdingHandler = async function(context, task, token, pair) {
  12. const config = context.config
  13. const fileLogger = task.fileLogger
  14. const binanceSpot = context.binanceSpot
  15. const accountAssetMap = context.accountAssetMap
  16. const nowTimestamp = new Date().getTime()
  17. // 这两个逻辑只有可能满足一个
  18. const isHardStopWin = token.BinancePrice > (token.orderPrice * (100 + config.hardStopWinPercentage) / 100)
  19. const isTimeOverStopWin = token.BinancePrice > token.orderPrice && nowTimestamp > token.orderTime + config.stopWinTimeOverHours * TimeKit.ONE_HOUR
  20. const isHardStopLoss = token.BinancePrice < (token.orderPrice * (100 - config.hardStopLossPercentage) / 100)
  21. const isTimeOverStopLoss = token.BinancePrice < token.orderPrice && nowTimestamp > token.orderTime + config.stopLossTimeOverHours * TimeKit.ONE_HOUR
  22. const isStopWin = isHardStopWin || isTimeOverStopWin
  23. const isStopLoss = isHardStopLoss || isTimeOverStopLoss
  24. const isTimeOverStop = isTimeOverStopWin || isTimeOverStopLoss
  25. assert.notEqual(isStopWin && isStopLoss, true, '止盈止损逻辑错误,请认真检查。')
  26. if (isStopWin || isStopLoss) {
  27. const orderPrice = token.orderPrice
  28. const orderAmount = token.orderAmount
  29. token.orderPrice = undefined
  30. token.orderAmount = undefined
  31. token.nextHandleTime = nowTimestamp + config.stopLossHandleSpaceHours * TimeKit.ONE_HOUR
  32. try {
  33. const sellMarketRst = await binanceSpot.sellMarket(pair, orderAmount)
  34. const price = NumKit.getSubFloat(sellMarketRst.avgPrice, token.exchange.priceTick)
  35. const cummulativeQuoteQty = NumKit.getSubFloat(sellMarketRst.cummulativeQuoteQty, 6)
  36. // 交易数据持久化
  37. await token.save()
  38. const log = `[${isTimeOverStop ? '时' : '强'}${isStopWin ? '盈+' : '损-'}]${pair}, `
  39. + `成交额${cummulativeQuoteQty}, 均价${orderPrice}->${price}, 卖出${orderAmount}个.`
  40. fileLogger.info(log)
  41. } catch (e) {
  42. const log = `[${isTimeOverStop ? '时' : '强'}止${isStopWin ? '盈' : '损'}失败]${pair}, ${e}`
  43. fileLogger.error(log)
  44. }
  45. }
  46. }
  47. const noHoldingHandler = async function(context, task, token, pair) {
  48. const config = context.config
  49. const fileLogger = task.fileLogger
  50. const binanceSpot = context.binanceSpot
  51. const accountAssetMap = context.accountAssetMap
  52. const nowTimestamp = new Date().getTime()
  53. // 1. 获取base资产数量
  54. const baseAssetAmount = accountAssetMap[config.baseIerc20Token.symbol]
  55. // 2. 判断余额是否够下单
  56. if (config.baseTokenAmount > baseAssetAmount) {
  57. fileLogger.info(`[余额不足]${pair}, 需要: ${config.baseTokenAmount}, 剩余: ${baseAssetAmount}.`)
  58. token.nextHandleTime = nowTimestamp + config.stopLossHandleSpaceHours * TimeKit.ONE_HOUR
  59. return
  60. }
  61. // 3. 下单获取成交信息
  62. try {
  63. const buyMarketRst = await binanceSpot.buyMarket(pair, config.baseTokenAmount)
  64. token.cummulativeQuoteQty = NumKit.getSubFloat(buyMarketRst.cummulativeQuoteQty, 6)
  65. token.orderAmount = NumKit.getSubFloat(buyMarketRst.executedQty, token.exchange.lotSize)
  66. token.orderPrice = NumKit.getSubFloat(buyMarketRst.avgPrice, token.exchange.priceTick)
  67. token.orderTime = nowTimestamp
  68. // 关键数据持久化
  69. await token.save()
  70. fileLogger.info(`[单]${pair}, 成交额${token.cummulativeQuoteQty}, 买入${token.orderAmount}个, 均价${token.orderPrice}.`)
  71. accountAssetMap[config.baseIerc20Token.symbol] -= token.cummulativeQuoteQty
  72. } catch (e) {
  73. fileLogger.error(`[下单失败]${pair}, ${e}`)
  74. }
  75. }
  76. const tradeLogic = async function(context, task) {
  77. const tokenMap = context.tokenMap
  78. // 逐对处理交易信息
  79. for (const tokenHash of Object.keys(tokenMap)) {
  80. const token = tokenMap[tokenHash]
  81. const pair = token.exchange.pair
  82. // 价格非法判定
  83. if (!token.BinancePrice || !token.OneInchPrice) continue;
  84. // 更新token的实际余额
  85. // token.orderAmount = NumKit.getSubFloat(accountAssetMap[token.exchange.symbol], token.exchange.lotSize)
  86. // 卖出逻辑判定
  87. if (token.orderPrice && token.orderAmount > Math.pow(10, -token.exchange.lotSize)) {
  88. await holdingHandler(context, task, token, pair)
  89. } else if (token.isLong) {
  90. await noHoldingHandler(context, task, token, pair)
  91. }
  92. }
  93. }
  94. const table = new TableKit(['pair', '1inch', 'binance', 'order price', 'profit(%)', 'next time'], 20)
  95. const showInfo = function(context, task) {
  96. const config = context.config
  97. const logger = task.logger
  98. const tokenMap = context.tokenMap
  99. const accountAssetMap = context.accountAssetMap
  100. table.printTitles()
  101. Object.keys(tokenMap).forEach((tokenHash) => {
  102. const token = tokenMap[tokenHash]
  103. const pair = token.exchange.pair
  104. const OneInchPrice = token.OneInchPrice ? token.OneInchPrice : NaN
  105. const BinancePrice = token.BinancePrice ? token.BinancePrice : NaN
  106. const DiffPrice = token.DiffPrice
  107. const mas = TickKit.MA(token.ticks, config.maPeriod)
  108. const maValue = mas[mas.length - 1]
  109. const percentage = NumKit.getSubFloat((DiffPrice / OneInchPrice) * 100, 2)
  110. const orderPrice = token.orderPrice ? token.orderPrice : ''
  111. const orderPercentage = orderPrice ? NumKit.getSubFloat(((BinancePrice - orderPrice) / orderPrice) * 100, 2) : ''
  112. const nextTimeStr = token.nextHandleTime ? TimeKit.getTimeByMillisecond(token.nextHandleTime) : ''
  113. // 价格非法判定
  114. const priceCondition = (() => {
  115. return OneInchPrice && BinancePrice
  116. })()
  117. const rows = [pair.toString(), OneInchPrice.toString(), BinancePrice.toString(),
  118. orderPrice.toString(), orderPercentage.toString(), nextTimeStr]
  119. // 入场信号
  120. token.isLong = (() => {
  121. const percentageCondition = percentage > config.percentageLimit
  122. const isNoPrevHandleTime = !token.nextHandleTime
  123. const isTimeCover = new Date().getTime() > token.nextHandleTime
  124. const timeCondition = isNoPrevHandleTime || isTimeCover
  125. const priceCondition = BinancePrice > maValue
  126. return priceCondition && percentageCondition && timeCondition
  127. })()
  128. // 打印表格
  129. table.showLine(rows, undefined)
  130. })
  131. table.printEndLine(logger)
  132. logger.info(`${config.baseIerc20Token.symbol} asset amount: ${accountAssetMap[config.baseIerc20Token.symbol]}.`)
  133. logger.info('')
  134. }
  135. const priceHandler = async function(context, task) {
  136. const config = context.config
  137. const tokenMap = context.tokenMap
  138. const tokenContractAddressList = Object.keys(tokenMap)
  139. for (const tokenHash of tokenContractAddressList) {
  140. const toToken = tokenMap[tokenHash]
  141. const priceTick = toToken.exchange.priceTick
  142. const fromIerc20Token = config.baseIerc20Token
  143. const amount = config.baseTokenAmount
  144. const OneInchPrice = NumKit.getSubFloat(await OneInch.price(fromIerc20Token.contract, tokenHash, amount, fromIerc20Token.decimals), priceTick)
  145. const BinancePrice = NumKit.getSubFloat(await BinanceSpot.realPrice(toToken.exchange.pair), priceTick)
  146. const ticks = BinanceKit.parseCloseTicks(await BinanceSpot.klines(toToken.exchange.pair))
  147. toToken.OneInchPrice = OneInchPrice
  148. toToken.BinancePrice = BinancePrice
  149. toToken.DiffPrice = BinancePrice - OneInchPrice
  150. toToken.ticks = ticks
  151. if ((() => {
  152. return Math.abs(toToken.DiffPrice) < Math.pow(10, -priceTick)
  153. })()) {
  154. toToken.DiffPrice = 0
  155. } {
  156. toToken.DiffPrice = NumKit.getSubFloat(toToken.DiffPrice, priceTick)
  157. }
  158. }
  159. }
  160. const accountHandler = async function(context, task) {
  161. const accountInfoRst = await context.binanceSpot.accountInfo()
  162. context.accountAssetMap = BinanceKit.parseBalancesToAccountAssetMap(accountInfoRst.balances)
  163. }
  164. const onTickFun = async function() {
  165. const task = this
  166. const context = task.context
  167. // 搜集所有价格数据
  168. await priceHandler(context, task)
  169. // 获取账户数据
  170. await accountHandler(context, task)
  171. // 展示关键信息
  172. showInfo(context, task)
  173. // 交易逻辑
  174. await tradeLogic(context, task)
  175. }
  176. const context = new Context()
  177. const onePro = new OneTask('OnePro', context, OneTask.baseInit, onTickFun)
  178. onePro.setDelayTime(onePro.context.config.delay)
  179. onePro.Start().then(() => {})