common

Author: linsilence, Date: 2021-01-08 16:38:10
Tags:




function GetCurrencyEx(ex, baseOrQuote) {
  let curr = ex.GetCurrency()
  let s = curr.split('_')
  return s[baseOrQuote ? 0 : 1]
}

function cancelOrdersPending(ex, orderType, orderOffset) {
  while (true) {
    const orders = ex.GetOrders()
    if (!orders) {
      Sleep(retryDelay)
      continue
    }
    let processed = 0
    for (let i = 0; i < orders.length; i++) {
      if (typeof orderType === 'number' && orders[i].Type !== orderType) {
        continue
      }
      if (typeof orderOffset === 'number' && orders[i].Offset !== orderOffset) {
        continue
      }

      ex.CancelOrder(orders[i].Id)
      if (isDebug) Log('cancelOrdersPending - ', orders[i])

      processed++
      Sleep(retryDelay)
    }
    if (processed === 0) {
      break
    }
  }
}

function isDepthBid(depthSide) {
  return depthSide[0].Price > depthSide[1].Price
}

/**
 *
 * @param {*} ex
 * @param {*} orderType ORDER_TYPE_BUY | ORDER_TYPE_SELL
 * @param {*} price
 * @param {*} quantity 交易额
 * @param {*} precision 交易额的精度
 * @param {*} minQuantity 最小交易额
 * @param {*} orderOffset 期货需要传这个参数, ORDER_OFFSET_OPEN | ORDER_OFFSET_CLOSE
 * @param {*} errorCancel true - 直接返回null , false - 取消所有方向相同的pending order,重新挂单,直到成功挂单为止
 * @returns order id
 */
function trade(ex, orderType, price, quantity, precision, minQuantity, orderOffset, errorCancel) {
  let tradeFunc = orderType === ORDER_TYPE_BUY ? ex.Buy : ex.Sell
  let tradeDirection
  if (orderOffset === ORDER_OFFSET_OPEN) {
    tradeDirection = orderType === ORDER_TYPE_BUY ? 'buy' : 'sell'
  } else if (orderOffset === ORDER_OFFSET_CLOSE) {
    tradeDirection = orderType === ORDER_TYPE_BUY ? 'closesell' : 'closebuy'
  }
  if (tradeDirection) {
    ex.SetDirection(tradeDirection)
  }

  if (isDebug) Log('trade 1 - ', 'orderType: ', orderType, ', orderOffset: ', orderOffset, ', tradeDirection: ', tradeDirection)

  const qty = _N(quantity, precision)
  if (qty === 0) {
    Log(`trade quantity in precision error. ${quantity} ${precision} ${qty}`, '#FF0000')
    return null
  }
  if (qty < minQuantity) {
    Log(`trade quantity less than min quantity. ${quantity} ${minQuantity} ${qty}`, '#FF0000')
    return null
  }

  let orderID
  while (true) {
    const id = tradeFunc(price, qty)
    if (!id) {
      if (errorCancel) {
        Log('trade id is null. maybe trade has sent!', '#FF0000')
        return null
      }

      Sleep(retryDelay)
      cancelOrdersPending(ex, orderType, orderOffset)
      continue
    }
    if (isDebug) Log('trade 2 - ', 'id: ', id, ', price: ', price, ', quantity: ', quantity)

    orderID = id
    break
  }

  return orderID
}

$.AverageSmooth = function (values) {
  // 剔除偏差较大的量
  let mean = math.mean(values)
  let stde = math.std(values)

  // 过滤平均值±2*标准差
  let ml = mean - 2 * stde
  let mh = mean + 2 * stde

  if (isDebug) Log('AverageSmooth 1 - ', mean, stde, ml, mh)

  let vs = values.filter((e) => {
    let res = e > ml && e < mh
    if (isDebug && !res) {
      Log('AverageSmooth 2 - filter: ', e)
    }
    return res
  })
  if (!vs || vs.length === 0) {
    return 0
  }
  return math.mean(vs)
}

$.CountDecimals = function (num) {
  const numTrim = parseFloat(num)
  return String(numTrim).includes('.') ? String(numTrim).split('.')[1].length : 0
}

$.LiquidationRate = function (open, last, liq) {
  return 1 - math.abs(last - liq) / math.abs(open - liq)
}

$.GetCurrencyBase = function (ex) {
  return GetCurrencyEx(ex, true)
}

$.GetCurrencyQuote = function (ex) {
  return GetCurrencyEx(ex, false)
}

$.CurrencyExist = function (ex, symbol) {
  const old = ex.GetCurrency()
  ex.SetCurrency(symbol)
  let exist = Boolean(ex.GetTicker())
  ex.SetCurrency(old)
  return exist
}

$.PriceRate = function (last, open) {
  return _N((last - open) / open, 4)
}

$.GetDepthTotal = function (depthSide, pricePercent) {
  if (pricePercent <= 0) {
    Log('pricePercent 大于 0')
    return 0
  }
  let isBid = isDepthBid(depthSide)
  let priceTarget = depthSide[0].Price * (1 + pricePercent * (isBid ? -1 : 1))
  let depthStage = depthSide.filter((e) => (isBid ? e.Price >= priceTarget : e.Price <= priceTarget))
  let total = depthStage.map((e) => e.Amount).reduce((prev, curr) => prev + curr)

  if (isDebug) {
    Log('isBid: ', isBid)
    Log('price: ', depthSide[0].Price, ', priceTarget: ', priceTarget)
    Log('depthStage: ', depthStage[depthStage.length - 1])
    Log('total: ', total)
  }

  return total
}

/**
 * 查询操作 amountAccumate 将会对价格产生多大的影响
 *
 * @param {*} depthSide
 * @param {*} amountAccumate
 *
 * @returns 影响的价格
 */
$.GetDepthTotalPrice = function (depthSide, amountAccumulate) {
  let priceAccumulate = -1

  let am = 0
  for (let i = 0; i < depthSide.length; ++i) {
    const e = depthSide[i]
    am += e.Amount

    if (isDebug) Log('GetDepthTotalPrice - ', 'index: ', i, ', depth: ', e, ', amountAcc: ', am)

    if (am >= amountAccumulate) {
      priceAccumulate = e.Price
      break
    }
  }

  return priceAccumulate
}

$.GetDepthAmounts = function (depthSide) {
  return depthSide.map((e) => e.Amount)
}

$.GetRecordsVolume = function (records) {
  return records.map((e) => e.Volume)
}

$.GetRecordsVolumeAmount = function (records) {
  return records.map((e) => e.Volume * e.Close)
}

$.GetRecordsAmplitude = function (records) {
  return records.map((e) => (e.High - e.Low) / e.Open)
}

$.GetRecordsClose = function (records) {
  return records.map((e) => e.Close)
}

$.GetRecordsHigh = function (records) {
  return records.map((e) => e.High)
}

$.GetRecordsLow = function (records) {
  return records.map((e) => e.Low)
}

$.IsFuture = function (ex) {
  return /(F|f)utures.*/.test(ex.GetName())
}

/**
 * 取消所有未完成的订单
 *
 * @param {*} ex
 * @param {*} orderType null 取消所有的
 * @param {*} orderOffset 期货才需要传参数,null 取消所有的
 */
$.CancelOrdersPending = function (ex, orderType, orderOffset) {
  cancelOrdersPending(ex, orderType, orderOffset)
}

/**
 * trade时,未获取到order id时,
 * 等待一会儿后,检查余额是否发生变化,如果发生变化了,说明交易订单已经发出(交易成功或挂单成功),但是order id无法获取
 * 否则,重新trade,直到订单发出为止
 *
 * @param {*} ex
 * @param {*} orderType ORDER_TYPE_BUY | ORDER_TYPE_SELL
 * @param {*} price
 * @param {*} quantity 交易额
 * @param {*} precision 交易额的精度
 * @param {*} minQuantity 最小交易额
 * @param {*} orderOffset 期货需要传这个参数, ORDER_OFFSET_OPEN | ORDER_OFFSET_CLOSE
 * @returns order id
 */
$.TradeNormal = function (ex, orderType, price, quantity, precision, minQuantity, orderOffset) {
  const accPre = _C(ex.GetAccount)

  let orderID = null
  while (true) {
    orderID = trade(ex, orderType, price, quantity, precision, minQuantity, orderOffset, true)
    if (orderID) {
      break
    }
    isDebug && Log(`TradeNormal - orderID获取失败 - price: ${price}, quantity: ${quantity}`, '#FF0000')

    Sleep(retryDelay * 2)
    const accAft = _C(ex.GetAccount)
    if (accAft.Balance !== accPre.Balance) {
      isDebug && Log(`TradeNormal - Balance 前后发生变化,交易已经发生 - price: ${price}, quantity: ${quantity}`, '#FF0000')
      break
    }
  }

  return orderID
}

/**
 * 规定时间内完成交易,否则撤销,部分成交的也会撤销
 *
 * @param {*} ex
 * @param {*} orderType ORDER_TYPE_BUY | ORDER_TYPE_SELL
 * @param {*} price
 * @param {*} quantity 交易额
 * @param {*} precision 交易额的精度
 * @param {*} minQuantity 最小交易额
 * @param {*} waitTime
 * @param {*} orderOffset 期货需要传这个参数, ORDER_OFFSET_OPEN | ORDER_OFFSET_CLOSE
 *
 * @returns order,状态只能是:ORDER_STATE_CLOSED | ORDER_STATE_CANCELED
 */
$.TradeCancel = function (ex, orderType, price, quantity, precision, minQuantity, waitTime, orderOffset) {
  let orderID = trade(ex, orderType, price, quantity, precision, minQuantity, orderOffset)
  if (!orderID) {
    return null
  }

  let waiting = 0
  let orderFinal
  while (true) {
    Sleep(retryDelay)

    const order = _C(ex.GetOrder, orderID)
    if (!order) {
      break
    }
    if (isDebug) Log('TradeCancel 1 - ', order)

    orderFinal = order
    if (order.Status !== ORDER_STATE_PENDING) {
      break
    }

    // 有可能成交了一部分
    waiting += retryDelay
    if (waiting > waitTime) {
      // 应该撤销这个订单
      ex.CancelOrder(orderID)

      if (isDebug) Log('TradeCancel 2 cancel')
    }
  }

  if (isDebug) Log('TradeCancel 3 - ', orderFinal, orderFinal.Status === ORDER_STATE_UNKNOWN ? '#FF0000' : '#000000')

  return orderFinal
}

/**
 * 规定时间内完成交易,否则剩余部分市价成交
 *
 * @param {*} ex
 * @param {*} orderType
 * @param {*} price
 * @param {*} quantity 交易额
 * @param {*} precision 交易额的精度
 * @param {*} minQuantity 最小交易额
 * @param {*} waitTime
 * @param {*} orderOffset
 *
 * @returns order
 */
$.TradeCertain = function (ex, orderType, price, quantity, precision, minQuantity, waitTime, orderOffset) {
  const order = $.TradeCancel(ex, orderType, price, quantity, precision, minQuantity, waitTime, orderOffset)
  if (!order) {
    return null
  }
  if (order.Status !== ORDER_STATE_CANCELED) {
    return order
  }

  // 剩余量以市价快速成交
  const qtyRest = quantity - order.DealAmount
  if (qtyRest < minQuantity) {
    return order
  }
  if (isDebug) Log('TradeCertain market - qtyRest: ', qtyRest, 'order: ', order)

  return $.TradeCertain(ex, orderType, -1, qtyRest, precision, minQuantity, math.min(waitTime / 2, 1000), orderOffset)
}

/**
 * 等待waitTime以最优价格交易,否则取消,然后重试retryTimes次后,再市价成交
 *
 * @param {*} ex
 * @param {*} orderType
 * @param {*} quantity 交易额
 * @param {*} quantityPrecision 交易额的精度
 * @param {*} pricePrecision 价格的精度
 * @param {*} minQuantity 最小交易额
 * @param {*} waitTime
 * @param {*} orderOffset
 * @param {*} retryTimes -1, 一直循环下去
 */
$.TradeRetry = function (ex, orderType, quantity, quantityPrecision, pricePrecision, minQuantity, waitTime, orderOffset, retryTimes) {
  let qtyRest = quantity
  let retry = 0
  while (true) {
    const ticker = _C(ex.GetTicker)
    const price = orderType === ORDER_TYPE_BUY ? ticker.Buy : ticker.Sell
    const order = $.TradeCancel(ex, orderType, price, qtyRest, quantityPrecision, minQuantity, waitTime, orderOffset)
    if (!order) {
      return null
    }
    if (order.Status !== ORDER_STATE_CANCELED) {
      return order
    }

    qtyRest -= order.DealAmount
    if (qtyRest < minQuantity) {
      return order
    }

    if (isDebug) Log('TradeRetry market - retry:', retry, 'qtyRest: ', qtyRest)
    if (retryTimes === -1) {
      continue
    }
    if (++retry > retryTimes) {
      break
    }
  }
  if (isDebug) Log('TradeRetry market - qtyRest: ', qtyRest)

  // FIXME: fmz未对市价Buy做适配,导致限价时使用交易量quantity做参数,而市价使用交易额做参数,而且只有Buy,Sell都是正常使用交易额的
  // return $.TradeCertain(ex, orderType, -1, qtyRest, precision, minQuantity, math.min(waitTime / 2, 1000), orderOffset)

  // so the priceFix use the 1% up/down the ticker.Last
  const ticker = _C(ex.GetTicker)
  const priceFix = _N(ticker.Last * (orderType === ORDER_TYPE_BUY ? 1.01 : 0.99), pricePrecision)
  return $.TradeCertain(ex, orderType, priceFix, qtyRest, quantityPrecision, minQuantity, math.min(waitTime / 2, 1000), orderOffset)
}

$.TradeRetrySlightly = function (ex, orderType, quantityTotal, quantityPrecision, pricePrecision, minQuantity, waitTime, orderOffset, retryTimes) {
  // 最佳成交量
  const depth = ex.GetDepth()
  const quantity_divide = math.min($.AverageSmooth($.GetDepthAmounts(depth.Bids)), $.AverageSmooth($.GetDepthAmounts(depth.Asks)))
  isDebug && Log(`TradeRetrySlightly - quantity_divide: ${quantity_divide}`)

  let qtyRest = quantityTotal
  while (true) {
    const qty = math.min(quantity_divide, qtyRest)
    if (qty < minQuantity) {
      break
    }

    const order = $.TradeRetry(ex, orderType, qty, quantityPrecision, pricePrecision, minQuantity, waitTime, orderOffset, retryTimes)
    if (order == null) {
      break
    }
    if (order.Status === ORDER_STATE_CLOSED) {
      qtyRest -= qty
    } else {
      throw new Error(`order.Status: ${order.Status}`)
    }

    Sleep(retryDelay)
  }
}

// 定义一个接口
const CountDown = {
  label: '',
  total: 0,
  accum: 0,
  overtime: 0,
  lasttime: 0,
}

var CountDownMap = new Map()

$.CountDownEnd = function (label, accumulate, total, overtime = 5 * 60) {
  let cd = CountDownMap.get(label)
  if (!cd) {
    cd = {
      label: label,
      total: total,
      accum: 0,
      overtime: overtime,
      lasttime: 0,
    }
    CountDownMap.set(label, cd)
  }

  // 超时后,需要重新计数
  const t = Unix()
  if (t - cd.lasttime > cd.overtime) {
    cd.accum = 0
    cd.lasttime = t

    // 先执行一次
    return true
  }
  cd.lasttime = t

  // 修改total后,需要重新计数
  if (cd.total !== total) {
    cd.total = total
    cd.accum = 0
  }

  cd.accum += accumulate
  const isEnd = cd.accum >= total
  if (isEnd) {
    cd.accum = 0
  }

  if (isDebug) Log('CountDownEnd - ', Array.from(CountDownMap))

  return isEnd
}

$.LoopStatusUpdate = (loop) => {
  let statusOld
  return (dt) => {
    const status = loop(dt)
    if (!status) {
      return statusOld
    }

    const statusNew = JSON.stringify(status)
    if (statusNew !== statusOld) {
      statusOld = statusNew
    }

    return statusOld
  }
}

$.LoopWaitFunc = (func, interval, firstRun = true) => {
  let acc = 0
  return (dt) => {
    if (firstRun) {
      firstRun = false
      return func(0)
    }

    acc += dt
    if (acc < interval) {
      return
    }
    acc = 0

    return func(interval)
  }
}

let balanceInit = 0

$.ProfitInit = (ex) => {
  const acc = _C(ex.GetAccount)
  balanceInit = acc.Balance
  LogProfitReset()
}

$.ProfitLog = (ex) => {
  const acc = _C(ex.GetAccount)
  const bal = acc.Balance
  LogProfit(bal - balanceInit)
}

function main() {
  exchange.SetContractType('swap')

  Log('GetCurrencyQuote: ', $.GetCurrencyQuote(exchange))
  Log('PriceRate: ', $.PriceRate(35058.79, 34675))
  Log('IsFuture: ', $.IsFuture(exchange))
  Log('GetDepthAmounts: ', $.GetDepthAmounts(exchange.GetDepth().Bids))

  let arr = [3, 6, 12, 2, 200, 1000]
  Log('AverageSmooth: ', arr, ' - ', $.AverageSmooth(arr))

  Log('GetDepthTotal: ', $.GetDepthTotal(exchange.GetDepth().Bids, 0.01))
  Log('GetDepthTotalPrice: ', $.GetDepthTotalPrice(exchange.GetDepth().Bids, 2100))

  Log('TradeCancel buy: ', $.TradeCancel(exchange, ORDER_TYPE_BUY, -1, 0.1, 3, 3000, ORDER_OFFSET_OPEN))

  Log('CountDecimals: ', $.CountDecimals(1), $.CountDecimals(0.001))
}


More