OKCoin韭菜收割机
原文地址:https://www.botvs.com/strategy/34388
为了学习方便,在自己理解的基础上加上了中文注释。
文件地址:okcoin韭菜收割机(注释).js
function LeeksReaper() {
var self = {}
self.numTick = 0
self.lastTradeId = 0
self.vol = 0
self.askPrice = 0
self.bidPrice = 0
self.orderBook = {Asks:[], Bids:[]}
self.prices = []
self.tradeOrderId = 0
self.p = 0.5
self.account = null
self.preCalc = 0
self.preNet = 0
// 将价格存在self.prices中,交易量self.vol
self.updateTrades = function() {
// _C,重试函数, 会一直调用指定函数到成功返回,这里就是获取交易所交易信息
// 获取交易所交易历史,历史成交订单
var trades = _C(exchange.GetTrades)
// length:将参数的数量返回回来,在这里就是将prices数组中存的个数返回回来。
if (self.prices.length == 0) {
while (trades.length == 0) {
// concat()方法能将两个数组合并,并不改变原来的数组,返回一个新数组
trades = trades.concat(_C(exchange.GetTrades))
}
for (var i = 0; i < 15; i++) {
// 将trades数组中最后一个的Price价格信息存储到prices数组中
self.prices[i] = trades[trades.length - 1].Price
}
}
// reduce
// function(mem, trade) 做两件事情。
// 1:将trades中的Amount累加起来,保存在mem中返回回来。
// 2:取trades中的最大Id保存在Id
self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {
// Huobi not support trade.Id
// self.lastTradeId 存储最大交易的ID
// 如果trade.Id > self.lastTradeId
if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
mem += trade.Amount
}
return mem
}, 0)
}
// 设置self.bidPrice,self.askPrice,根据买单,卖单价格计算出一个价格填入到self.prices中
self.updateOrderBook = function() {
// 获取交易所订单薄
var orderBook = _C(exchange.GetDepth)
self.orderBook = orderBook
// bid 是出价的意思,是卖单。 ask是买单
// 如果买单或卖单单子不足3比则取消。意思就是深度不够停止交易
if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
return
}
//设置卖单价格,最低的卖单价格*0.618+最高的买单价格*0.382 算出来的价格会比最低的卖单价格低
self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
// 设置买单单价 算出来的价格会比最高的买单价格高
self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
// shift()移除数组中的第一个元素,并将移除的这个元素返回。
self.prices.shift()
// push,在数组末尾添加一个元素,并将数组的大小返回
// _N 控制小数点精度。
// 根据买单,卖单计算出一个价格,存在self.prices中
self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
(orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
(orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
}
//
self.balanceAccount = function() {
// 将返回交易所账户信息
var account = exchange.GetAccount()
if (!account) {
return
}
self.account = account
// 获取当前时间
var now = new Date().getTime()
// 卖单数量大于0 并且 当前时间与上次平衡仓位间隔超过的固定的时间
if (self.orderBook.Bids.length > 0 && now - self.preCalc > (CalcNetInterval * 1000)) {
// 当前时间更新给self.preCalc
self.preCalc = now
// Balance 余额(定价货币余额, ETH_BTC的话BTC为定价货币)
// FrozenBalance 冻结的余额
// Stocks 交易货币的可用数量, 数字货币现货为当前可操作币的余额(去掉冻结的币), 数字货币期货的话为合约当前可用保证金(传统期货无此属性) 。
// FrozenStocks 冻结的交易货币的可用数量(传统期货无此属性)
// 账号里面所有资产转换成以BTC计价,计算出的总资产,如果是ETH_BTC交易对就用,BTC计价
var net = _N(account.Balance + account.FrozenBalance + self.orderBook.Bids[0].Price * (account.Stocks + account.FrozenStocks))
// 如果算出来的新的资产总数,不等于self.preNet,将net更新到self.preNet
if (net != self.preNet) {
self.preNet = net
LogProfit(net)
}
}
//
self.btc = account.Stocks
self.cny = account.Balance
// 如果是ETH_BTC交易对,那么BTC为定价货币,self.p计算的就是手中ETH占总资产的比例
self.p = self.btc * self.prices[self.prices.length-1] / (self.btc * self.prices[self.prices.length-1] + self.cny)
var balanced = false
// 比例低于48%,开始买入
if (self.p < 0.48) {
Log("开始平衡", self.p)
self.cny -= 300
if (self.orderBook.Bids.length >0) {
// exchange.Buy(Price, Amount), price为订单价格,Amount为订单数量
// 如果是ETH_BTC交易对,exchange.Buy(1, 0.1),用1CNY买入0.1个BTC
// exchange.Buy(-1, 0.1) // 下市价单 买入,买入 0.1 个 BTC。
// 比最低的卖单价格高一点,买入0.01个BTC
exchange.Buy(self.orderBook.Bids[0].Price + 0.00, 0.01)
exchange.Buy(self.orderBook.Bids[0].Price + 0.01, 0.01)
exchange.Buy(self.orderBook.Bids[0].Price + 0.02, 0.01)
}
} else if (self.p > 0.52) {
// 比例低高于52%,开始卖出
Log("开始平衡", self.p)
self.btc -= 0.03
if (self.orderBook.Asks.length >0) {
// 如果是ETH_BTC交易对,exchange.Sell(1, 1),用1ETH买入0.1个BTC
// exchange.Sell(-1, 0.2) // 注意: 下市价单 卖出,卖出 0.2 个 BTC
// 比最高的买单价低卖出
exchange.Sell(self.orderBook.Asks[0].Price - 0.00, 0.01)
exchange.Sell(self.orderBook.Asks[0].Price - 0.01, 0.01)
exchange.Sell(self.orderBook.Asks[0].Price - 0.02, 0.01)
}
}
// Sleep 休眠函数,使程序暂停一段时间
Sleep(BalanceTimeout)
// 获取所有未完成的订单
var orders = exchange.GetOrders()
if (orders) {
for (var i = 0; i < orders.length; i++) {
// 取消未交易的订单
if (orders[i].Id != self.tradeOrderId) {
exchange.CancelOrder(orders[i].Id)
}
}
}
}
self.poll = function() {
self.numTick++
// 更新交易信息,最新的成交价,历史成交量
self.updateTrades()
// 更新订单簿
self.updateOrderBook()
// 控制,平衡仓位
self.balanceAccount()
var burstPrice = self.prices[self.prices.length-1] * BurstThresholdPct
var bull = false
var bear = false
var tradeAmount = 0
// 打印账号信息
if (self.account) {
LogStatus(self.account, 'Tick:', self.numTick, ', lastPrice:', self.prices[self.prices.length-1], ', burstPrice: ', burstPrice)
}
// 判断熊牛
if (self.numTick > 2 && (
// self.prices[self.prices.length-1]取数组中最后一个价格数据
// self.prices.slice(-6, -1) 取self.prices数组中倒数第6个数
// self.prices.slice(-6, -2) 取self.prices数组中倒数第6,7个数,两个数
self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -1)) > burstPrice ||
self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -2)) > burstPrice &&
self.prices[self.prices.length-1] > self.prices[self.prices.length-2]
)) {
bull = true
tradeAmount = self.cny / self.bidPrice * 0.99
} else if (self.numTick > 2 && (
self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -1)) < -burstPrice ||
self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -2)) < -burstPrice && self.prices[self.prices.length-1] < self.prices[self.prices.length-2]
)) {
bear = true
tradeAmount = self.btc
}
// 设置交易量
if (self.vol < BurstThresholdVol) {
tradeAmount *= self.vol / BurstThresholdVol
}
if (self.numTick < 5) {
tradeAmount *= 0.8
}
if (self.numTick < 10) {
tradeAmount *= 0.8
}
// 不是牛也不是熊,返回,交易数量小于最小交易量,返回
if ((!bull && !bear) || tradeAmount < MinStock) {
return
}
// 如果是牛,交易价格是:self.bidPrice
var tradePrice = bull ? self.bidPrice : self.askPrice
while (tradeAmount >= MinStock) {
// 如果牛执行买单,如果熊执行卖单
var orderId = bull ? exchange.Buy(self.bidPrice, tradeAmount) : exchange.Sell(self.askPrice, tradeAmount)
// 参数为毫秒数,如Sleep(1000)为休眠一秒
Sleep(200)
if (orderId) {
self.tradeOrderId = orderId
var order = null
while (true) {
order = exchange.GetOrder(orderId)
if (order) {
// 如果订单还没执行,就取消
if (order.Status == ORDER_STATE_PENDING) {
exchange.CancelOrder(orderId)
Sleep(200)
} else {
break
}
}
}
self.tradeOrderId = 0
// 减去成交数量,order.DealAmount:成交数量
tradeAmount -= order.DealAmount
// 减少交易量
tradeAmount *= 0.9
if (order.Status == ORDER_STATE_CANCELED) {
// 更新买单,卖单簿
self.updateOrderBook()
// 下面的两个while,目的是确保在牛市时,我的出价比最低的卖价低0.1,在熊市时,确保我的
// 如果是是牛市,并且最低卖单价格-交易价格>0.1
while (bull && self.bidPrice - tradePrice > 0.1) {
// 减少交易数量*0.99
tradeAmount *= 0.99
// 因为最低的卖单价格比交易价格高,并且高出的金额大于0.1,所以我的交易价格+0.1,
tradePrice += 0.1
}
// 如果是熊市,并且卖单的价格比我的交易价格低,我就要降价,降0.1
while (bear && self.askPrice - tradePrice < -0.1) {
tradeAmount *= 0.99
tradePrice -= 0.1
}
}
}
}
self.numTick = 0
}
return self
}
function main() {
var reaper = LeeksReaper()
while (true) {
reaper.poll()
Sleep(TickInterval)
}
}