Name
多平台对冲稳定套利
Author
John。
Strategy Description
程序自动换算汇率,手续费,下单参考买一卖一的价,注意这个不是搬砖,是对冲! 不需要人工转钱或者币,一个存钱,一个存币,一个买,一个卖,高级对冲模式. 这种对冲模式不管币的价格是涨还是跌,只赚差价。 建仓需要一个放钱,一个放币,比如BTC现在价格6000吧,一个交易所放3000刀,一个放0.45左右得币就行了,或者全部加倍 MaxDiff指最低平台差价 SlidePrice指下单时的滑动价, 买单就加上这个价, 卖单就减去这个价 TickIntervalS 指检测周期 跌停值指币跌到这个价格了就不操作,涨停值指币高于这个价格也不操作,防止平台恶意给机器人下套.
Strategy Arguments
Argument | Default | Description |
---|---|---|
Interval | 500 | 出错重试间隔(毫秒) |
TickInterval | 300 | 检测频率(毫秒) |
MaxDiff | 3 | 最低差价(元) |
SlidePrice | 0.1 | 止损滑动价(元) |
SlideRatio | 2 | 对冲滑动价系数 |
StopPriceL | 1000 | 跌停值(元) |
StopPriceH | 9000 | 涨停值(元) |
optFeeTimeout | 30 | 手续费更新周期(分) |
AmountOnce | 0.2 | 单笔交易数量 |
UseMarketOrder | false | 使用市价单止损 |
StopWhenLoss | false | 亏损时停止 |
MaxLoss | 50 | 最大亏损限度(元) |
SMSAPI | http:// | 短信通知接口 |
Source (javascript)
var initState;
var isBalance = true;
var feeCache = new Array();
var feeTimeout = optFeeTimeout * 60000;
var lastProfit = 0;
var lastAvgPrice = 0;
var lastSpread = 0;
var lastOpAmount = 0;
function adjustFloat(v) {
return Math.floor(v * 1000) / 1000;
}
function isPriceNormal(v) {
return (v >= StopPriceL) && (v <= StopPriceH);
}
function stripTicker(t) {
return 'Buy: ' + adjustFloat(t.Buy) + ' Sell: ' + adjustFloat(t.Sell);
}
function updateStatePrice(state) {
var now = (new Date()).getTime();
for (var i = 0; i < state.details.length; i++) {
var ticker = null;
var key = state.details[i].exchange.GetName() + state.details[i].exchange.GetCurrency();
var fee = null;
while (!(ticker = state.details[i].exchange.GetTicker())) {
Sleep(Interval);
}
if (key in feeCache) {
var v = feeCache[key];
if ((now - v.time) > feeTimeout) {
delete feeCache[key];
} else {
fee = v.fee;
}
}
if (!fee) {
while (!(fee = state.details[i].exchange.GetFee())) {
Sleep(Interval);
}
feeCache[key] = {
fee: fee,
time: now
};
}
// Buy-=fee Sell+=fee
state.details[i].ticker = {
Buy: ticker.Buy * (1 - (fee.Sell / 100)),
Sell: ticker.Sell * (1 + (fee.Buy / 100))
};
state.details[i].realTicker = ticker;
state.details[i].fee = fee;
}
}
function getProfit(stateInit, stateNow, coinPrice) {
var netNow = stateNow.allBalance + (stateNow.allStocks * coinPrice);
var netInit = stateInit.allBalance + (stateInit.allStocks * coinPrice);
return adjustFloat(netNow - netInit);
}
function getExchangesState() {
var allStocks = 0;
var allBalance = 0;
var minStock = 0;
var details = [];
for (var i = 0; i < exchanges.length; i++) {
var account = null;
while (!(account = exchanges[i].GetAccount())) {
Sleep(Interval);
}
allStocks += account.Stocks + account.FrozenStocks;
allBalance += account.Balance + account.FrozenBalance;
minStock = Math.max(minStock, exchanges[i].GetMinStock());
details.push({
exchange: exchanges[i],
account: account
});
}
return {
allStocks: adjustFloat(allStocks),
allBalance: adjustFloat(allBalance),
minStock: minStock,
details: details
};
}
function cancelAllOrders() {
for (var i = 0; i < exchanges.length; i++) {
while (true) {
var orders = null;
while (!(orders = exchanges[i].GetOrders())) {
Sleep(Interval);
}
if (orders.length == 0) {
break;
}
for (var j = 0; j < orders.length; j++) {
exchanges[i].CancelOrder(orders[j].Id, orders[j]);
}
}
}
}
function balanceAccounts() {
// already balance
if (isBalance) {
return;
}
cancelAllOrders();
var state = getExchangesState();
var diff = state.allStocks - initState.allStocks;
var adjustDiff = adjustFloat(Math.abs(diff));
if (adjustDiff < state.minStock) {
isBalance = true;
} else {
Log('初始币总数量:', initState.allStocks, '现在币总数量: ', state.allStocks, '差额:', adjustDiff);
// other ways, diff is 0.012, bug A only has 0.006 B only has 0.006, all less then minstock
// we try to statistical orders count to recognition this situation
updateStatePrice(state);
var details = state.details;
var ordersCount = 0;
if (diff > 0) {
var attr = 'Sell';
if (UseMarketOrder) {
attr = 'Buy';
}
// Sell adjustDiff, sort by price high to low
details.sort(function(a, b) {
return b.ticker[attr] - a.ticker[attr];
});
for (var i = 0; i < details.length && adjustDiff >= state.minStock; i++) {
if (isPriceNormal(details[i].ticker[attr]) && (details[i].account.Stocks >= state.minStock)) {
var orderAmount = adjustFloat(Math.min(AmountOnce, adjustDiff, details[i].account.Stocks));
var orderPrice = details[i].realTicker[attr] - SlidePrice;
if ((orderPrice * orderAmount) < details[i].exchange.GetMinPrice()) {
continue;
}
ordersCount++;
if (details[i].exchange.Sell(orderPrice, orderAmount, stripTicker(details[i].ticker))) {
adjustDiff = adjustFloat(adjustDiff - orderAmount);
}
// only operate one platform
break;
}
}
} else {
var attr = 'Buy';
if (UseMarketOrder) {
attr = 'Sell';
}
// Buy adjustDiff, sort by sell-price low to high
details.sort(function(a, b) {
return a.ticker[attr] - b.ticker[attr];
});
for (var i = 0; i < details.length && adjustDiff >= state.minStock; i++) {
if (isPriceNormal(details[i].ticker[attr])) {
var canRealBuy = adjustFloat(details[i].account.Balance / (details[i].ticker[attr] + SlidePrice));
var needRealBuy = Math.min(AmountOnce, adjustDiff, canRealBuy);
var orderAmount = adjustFloat(needRealBuy * (1 + (details[i].fee.Buy / 100)));
var orderPrice = details[i].realTicker[attr] + SlidePrice;
if ((orderAmount < details[i].exchange.GetMinStock()) ||
((orderPrice * orderAmount) < details[i].exchange.GetMinPrice())) {
continue;
}
ordersCount++;
if (details[i].exchange.Buy(orderPrice, orderAmount, stripTicker(details[i].ticker))) {
adjustDiff = adjustFloat(adjustDiff - needRealBuy);
}
// only operate one platform
break;
}
}
}
isBalance = (ordersCount == 0);
}
if (isBalance) {
var currentProfit = getProfit(initState, state, lastAvgPrice);
LogProfit(currentProfit, "Spread: ", adjustFloat((currentProfit - lastProfit) / lastOpAmount), "Balance: ", adjustFloat(state.allBalance), "Stocks: ", adjustFloat(state.allStocks));
if (StopWhenLoss && currentProfit < 0 && Math.abs(currentProfit) > MaxLoss) {
Log('交易亏损超过最大限度, 程序取消所有订单后退出.');
cancelAllOrders();
if (SMSAPI.length > 10 && SMSAPI.indexOf('http') == 0) {
HttpQuery(SMSAPI);
Log('已经短信通知');
}
throw '已停止';
}
lastProfit = currentProfit;
}
}
function onTick() {
if (!isBalance) {
balanceAccounts();
return;
}
var state = getExchangesState();
// We also need details of price
updateStatePrice(state);
var details = state.details;
var maxPair = null;
var minPair = null;
for (var i = 0; i < details.length; i++) {
var sellOrderPrice = details[i].account.Stocks * (details[i].realTicker.Buy - SlidePrice);
if (((!maxPair) || (details[i].ticker.Buy > maxPair.ticker.Buy)) && (details[i].account.Stocks >= state.minStock) &&
(sellOrderPrice > details[i].exchange.GetMinPrice())) {
details[i].canSell = details[i].account.Stocks;
maxPair = details[i];
}
var canBuy = adjustFloat(details[i].account.Balance / (details[i].realTicker.Sell + SlidePrice));
var buyOrderPrice = canBuy * (details[i].realTicker.Sell + SlidePrice);
if (((!minPair) || (details[i].ticker.Sell < minPair.ticker.Sell)) && (canBuy >= state.minStock) &&
(buyOrderPrice > details[i].exchange.GetMinPrice())) {
details[i].canBuy = canBuy;
// how much coins we real got with fee
details[i].realBuy = adjustFloat(details[i].account.Balance / (details[i].ticker.Sell + SlidePrice));
minPair = details[i];
}
}
if ((!maxPair) || (!minPair) || ((maxPair.ticker.Buy - minPair.ticker.Sell) < MaxDiff) ||
!isPriceNormal(maxPair.ticker.Buy) || !isPriceNormal(minPair.ticker.Sell)) {
return;
}
// filter invalid price
if (minPair.realTicker.Sell <= minPair.realTicker.Buy || maxPair.realTicker.Sell <= maxPair.realTicker.Buy) {
return;
}
// what a fuck...
if (maxPair.exchange.GetName() == minPair.exchange.GetName()) {
return;
}
lastAvgPrice = adjustFloat((minPair.realTicker.Buy + maxPair.realTicker.Buy) / 2);
lastSpread = adjustFloat((maxPair.realTicker.Sell - minPair.realTicker.Buy) / 2);
// compute amount
var amount = Math.min(AmountOnce, maxPair.canSell, minPair.realBuy);
lastOpAmount = amount;
var hedgePrice = adjustFloat((maxPair.realTicker.Buy - minPair.realTicker.Sell) / Math.max(SlideRatio, 2))
if (minPair.exchange.Buy(minPair.realTicker.Sell + hedgePrice, amount * (1 + (minPair.fee.Buy / 100)), stripTicker(minPair.realTicker))) {
maxPair.exchange.Sell(maxPair.realTicker.Buy - hedgePrice, amount, stripTicker(maxPair.realTicker));
}
isBalance = false;
}
function main() {
if (exchanges.length < 2) {
throw "交易所数量最少得两个才能完成对冲";
}
TickInterval = Math.max(TickInterval, 50);
Interval = Math.max(Interval, 50);
cancelAllOrders();
initState = getExchangesState();
if (initState.allStocks == 0) {
throw "所有交易所货币数量总和为空, 必须先在任一交易所建仓才可以完成对冲";
}
if (initState.allBalance == 0) {
throw "所有交易所CNY数量总和为空, 无法继续对冲";
}
for (var i = 0; i < initState.details.length; i++) {
var e = initState.details[i];
Log(e.exchange.GetName(), e.exchange.GetCurrency(), e.account);
}
Log("ALL: Balance: ", initState.allBalance, "Stocks: ", initState.allStocks, "Ver:", Version());
while (true) {
onTick();
Sleep(parseInt(TickInterval));
}
}
Detail
https://www.fmz.com/strategy/194150
Last Modified
2020-04-15 14:13:49