三角套利(赚取小币种盘口价差)

Author: @cqz, Date: 2022-05-24 17:40:35
Tags:


function getAccounts() {
	var accounts = [];
	while (true) {
		for (var i = 0; i < exchanges.length; i++) {
			if (accounts[i] == null) {
				// 创建异步操作
				accounts[i] = exchanges[i].Go("GetAccount");
			}
		}
		var failed = 0;
		for (var i = 0; i < exchanges.length; i++) {
			if (typeof(accounts[i].wait) != "undefined") {
				// 等待结果
				var account = accounts[i].wait(5);
				if (typeof(account) != "undefined") {
					if (account) {
						accounts[i] = account;
					} else {
						// 重试
						accounts[i] = null;
						failed++;
					}
				} else {
					failed++;
				}
			}
		}
		if (failed == 0) {
			break;
		}
	}
	return accounts;
}

function getDepthInfo() {
	var beginTime = new Date().getTime();
	var depths = [];
	var elapses = [];
	// for (var i = 0; i < exchanges.length; i++) {
	// 	var startTime = new Date().getTime();
	// 	depths[i] = _C(exchanges[i].GetDepth);
	// 	var endTime = new Date().getTime();
	// 	elapses[i] = endTime - startTime;
	// }
	while (true) {
		for (var i = 0; i < exchanges.length; i++) {
			if (depths[i] == null && (elapses[i] == null || elapses[i] <= maxElapse)) {
				// 创建异步操作
				depths[i] = exchanges[i].Go("GetDepth");
			}
		}
		var failed = 0;
		for (var i = 0; i < exchanges.length; i++) {
			if (depths[i] == null) {
				continue;
			}
			if (typeof(depths[i].wait) != "undefined") {
				var waitStartTime = new Date().getTime();
				// 等待结果
				var depth = depths[i].wait(5);
				if (typeof(depth) != "undefined") {
					if (depth) {
						depths[i] = depth;
					} else {
						// 重试
						depths[i] = null;
						failed++;
					}
				} else {
					failed++;
				}
				var waitEndTime = new Date().getTime();
				if (elapses[i] == null) {
					elapses[i] = waitEndTime - waitStartTime;
				} else {
					elapses[i] = elapses[i] + waitEndTime - waitStartTime;
				}
			}
		}
		if (failed == 0) {
			break;
		}
	}
	var finishTime = new Date().getTime();
	return {
		"totalElapse": finishTime - beginTime,
		"elapses": elapses,
		"depths": depths
	};
}

function getExchangeTariff(index) {
	var exchangeName = exchanges[index].GetName();
	if (exchangeName == "Huobi") {
		return 0.09 / 100;
	} else if (exchangeName == "Binance") {
		return 0.075 / 100;
	} else if (exchangeName == "GateIO") {
		return 0.03 / 100;
	} else if (exchangeName == "OKEX") {
		return 0.15 / 100;
	} else {
		return 0.1 / 100;
	}
}
var pricesChart = Chart([{ // 这个 chart 在JS 语言中 是对象, 在使用Chart 函数之前我们需要声明一个配置图表的对象变量chart。
	__isStock: true, // 标记是否为一般图表,有兴趣的可以改成 false 运行看看。
	tooltip: {
		xDateFormat: '%Y-%m-%d %H:%M:%S, %A'
	}, // 缩放工具
	title: {
		text: '差价分析图'
	}, // 标题
	rangeSelector: { // 选择范围
		buttons: [{
			type: 'hour',
			count: 1,
			text: '1h'
		}, {
			type: 'hour',
			count: 3,
			text: '3h'
		}, {
			type: 'hour',
			count: 8,
			text: '8h'
		}, {
			type: 'all',
			text: 'All'
		}],
		selected: 0,
		inputEnabled: false
	},
	xAxis: {
		type: 'datetime'
	}, // 坐标轴横轴 即:x轴, 当前设置的类型是 :时间
	yAxis: { // 坐标轴纵轴 即:y轴, 默认数值随数据大小调整。
		title: {
			text: '差价'
		}, // 标题
		opposite: false, // 是否启用右边纵轴
	},
	series: [ // 数据系列,该属性保存的是 各个 数据系列(线, K线图, 标签等..)
		{
			name: "委托买价",
			id: "委托买价,bidPrice",
			data: []
		}, {
			name: "委托卖价",
			id: "委托卖价,askPrice",
			data: []
		}, {
			name: "套利买价",
			id: "套利买价,arbitrageBidPrice",
			data: []
		}, {
			name: "套利卖价",
			id: "套利卖价,arbitrageAskPrice",
			data: []
		}, {
			name: "买入下单价格",
			id: "买入下单价格,placeOrderPrice",
			dashStyle: 'shortdash',
			data: []
		}, {
			name: "买入撤单价格",
			id: "买入撤单价格,cancelOrderPrice",
			dashStyle: 'shortdash',
			data: []
		},
	]
}]);

function updatePriceChart(bidPrice, askPrice, arbitrageBidRealPrice, arbitrageAskRealPrice, placeOrderDiffPriceRatio, cancelOrderDiffPriceRatio) {
	var nowTime = new Date().getTime();
	pricesChart.add([0, [nowTime, bidPrice.RealPrice]]);
	pricesChart.add([1, [nowTime, askPrice.RealPrice]]);
	pricesChart.add([2, [nowTime, arbitrageBidRealPrice]]);
	pricesChart.add([3, [nowTime, arbitrageAskRealPrice]]);
	pricesChart.add([4, [nowTime, bidPrice.RealPrice * (1 + placeOrderDiffPriceRatio)]]);
	pricesChart.add([5, [nowTime, bidPrice.RealPrice * (1 + cancelOrderDiffPriceRatio)]]);
}

var CurrencyOverturn = false;

var BidOrder = {
	"DealAmount": 0,
	"Amount": 0,
	"Price": 0,
	"RealPrice": 0,
	"OrderId": 0,
}
var AskOrder = {
	"DealAmount": 0,
	"Amount": 0,
	"Price": 0,
	"RealPrice": 0,
	"OrderId": 0,
}

var RunningInfo = {
	"OpenOrders": [],
	"Profit": 0,
	"FeeCost": 0,
	"CoinProfit": 0,
	"CoinFeeCost": 0,
	"Profit_hedge": 0,
	"FeeCost_hedge": 0,
	"Changed": false
};

var AccountInfo = {
	"CurBalance": 0,
	"CurStocks": 0,
	"LastUpdateTime": 0,
}

function updateAccountInfo(flush) {
	if (!flush && new Date().getTime() - AccountInfo.LastUpdateTime < 60 * 1000) {
		return
	}
	var exAccount = _C(exchanges[0].GetAccount);
	AccountInfo.CurBalance = exAccount.Balance;
	AccountInfo.CurStocks = exAccount.Stocks;
	AccountInfo.LastUpdateTime = new Date().getTime();
}

function getOrder(ex, orderId) {
	order = ex.GetOrder(orderId);
	if (order != null) {
		fixOrder(ex, order);
	}
	return order;
}

function fixOrder(ex, order) {
	var exName = ex.GetName();
	if (exName == "GateIO") {
		order.DealAmount = parseFloat(order.Info.filledAmount);
		order.AvgPrice = parseFloat(order.Info.filledRate);
	} else if (exName == "Binance") {
		order.AvgPrice = parseFloat(order.Info.cummulativeQuoteQty) / order.DealAmount;
	}
}

function mergeDepth(askOrBids, mergeAmount) {
	var askOrBid = {
		"Amout": 0,
		"Price": 0
	};
	for (var i = 0; i < askOrBids.length; i++) {
		if (askOrBid.Amout > mergeAmount) {
			break;
		}
		askOrBid.Amout += askOrBids[i].Amount;
		askOrBid.Price = askOrBids[i].Price;
	}
	return askOrBid;
}

function getArbitrageBidRealPrice(depths) {
	var askPrice1 = mergeDepth(depths[1].Asks, maxTakerAmount).Price
	var askMoney1 = askPrice1 * maxTakerAmount;
	if (CurrencyOverturn) {
		return (askPrice1 * (1 + getExchangeTariff(1))) * (mergeDepth(depths[2].Asks, askMoney1).Price * (1 + getExchangeTariff(2)));
	} else {
		return (askPrice1 * (1 + getExchangeTariff(1))) / (mergeDepth(depths[2].Bids, askMoney1 / depths[2].Bids[0].Price).Price * (1 - getExchangeTariff(2)));
	}
}

function getArbitrageAskRealPrice(depths) {
	var bidPrice1 = mergeDepth(depths[1].Bids, maxTakerAmount).Price;
	var bidMoney1 = bidPrice1 * maxTakerAmount;
	if (CurrencyOverturn) {
		return (bidPrice1 * (1 - getExchangeTariff(1))) * (mergeDepth(depths[2].Bids, bidMoney1).Price * (1 - getExchangeTariff(2)));
	} else {
		return (bidPrice1 * (1 - getExchangeTariff(1))) / (mergeDepth(depths[2].Asks, bidMoney1 / depths[2].Asks[0].Price).Price * (1 + getExchangeTariff(2)));
	}
}

function getOpenOrdersAmount() {
	var amount = 0;
	for (var i = 0; i < RunningInfo.OpenOrders.length; i++) {
		amount += RunningInfo.OpenOrders[i].Amout;
	}
	return amount;
}

function getOpenOrdersAvgRealPrice() {
	var amount = 0;
	var realMoney = 0;
	for (var i = 0; i < RunningInfo.OpenOrders.length; i++) {
		amount += RunningInfo.OpenOrders[i].Amout;
		realMoney += RunningInfo.OpenOrders[i].RealPrice * RunningInfo.OpenOrders[i].Amout;
	}
	if (amount > 0) {
		return realMoney / amount;
	} else {
		return 0;
	}
}

function mergeOpenOrders() {
	var buyOpenOrders = [];
	var sellOpenOrders = [];
	for (var i = 0; i < RunningInfo.OpenOrders.length; i++) {
		if (RunningInfo.OpenOrders[i].Amout > 0) {
			var contain = false;
			for (var j = 0; j < buyOpenOrders.length; j++) {
				if (buyOpenOrders[j].Price == RunningInfo.OpenOrders[i].Price) {
					buyOpenOrders[j].Amout += RunningInfo.OpenOrders[i].Amout;
					contain = true;
					break;
				}
			}
			if (!contain && RunningInfo.OpenOrders[i].Amout != 0) {
				buyOpenOrders.push(RunningInfo.OpenOrders[i]);
			}
		} else {
			var contain = false;
			for (var j = 0; j < sellOpenOrders.length; j++) {
				if (sellOpenOrders[j].Price == RunningInfo.OpenOrders[i].Price) {
					sellOpenOrders[j].Amout += RunningInfo.OpenOrders[i].Amout;
					contain = true;
					break;
				}
			}
			if (!contain && RunningInfo.OpenOrders[i].Amout != 0) {
				sellOpenOrders.push(RunningInfo.OpenOrders[i]);
			}
		}
	}

	var profitChange = false;
	for (var i = 0; i < buyOpenOrders.length; i++) {
		var buyOpenOrder = buyOpenOrders[i];
		if (buyOpenOrder.Amout == 0) {
			continue;
		}
		for (var j = 0; j < sellOpenOrders.length; j++) {
			var sellOpenOrder = sellOpenOrders[j];
			if (sellOpenOrder.Amout == 0) {
				continue;
			}
			if (RunningInfo.Profit_hedge == null) {
				RunningInfo.Profit_hedge = 0;
			}
			if (RunningInfo.FeeCost_hedge == null) {
				RunningInfo.FeeCost_hedge = 0;
			}
			if (buyOpenOrder.Amout + sellOpenOrder.Amout > 0) {
				RunningInfo.Profit_hedge += (-sellOpenOrder.Amout) * (sellOpenOrder.RealPrice - buyOpenOrder.RealPrice);
				RunningInfo.FeeCost_hedge += (-sellOpenOrder.Amout * sellOpenOrder.Price * getExchangeTariff(0)) + (-sellOpenOrder.Amout * buyOpenOrder.Price * getExchangeTariff(0))
				buyOpenOrder.Amout += sellOpenOrder.Amout;
				sellOpenOrder.Amout = 0;
			} else {
				RunningInfo.Profit_hedge += (buyOpenOrder.Amout) * (sellOpenOrder.RealPrice - buyOpenOrder.RealPrice);
				RunningInfo.FeeCost_hedge += (buyOpenOrder.Amout * sellOpenOrder.Price * getExchangeTariff(0)) + (buyOpenOrder.Amout * buyOpenOrder.Price * getExchangeTariff(0))
				sellOpenOrder.Amout += buyOpenOrder.Amout;
				buyOpenOrder.Amout = 0;
			}
			profitChange = true;
		}
	}
	if (profitChange) {
		RunningInfo.Changed = true;
		// LogProfit(RunningInfo.Profit + RunningInfo.CoinProfit * RunningInfo.OpenOrders[i].Price);
	}
	var nowOpenOrders = [];
	var fixOpenOrders = [];
	for (var i = 0; i < buyOpenOrders.length; i++) {
		if (buyOpenOrders[i].Amout == 0) {
			continue;
		}
		if (buyOpenOrders[i].TransactionTime != null && new Date().getTime() - buyOpenOrders[i].TransactionTime < 5 * 60 * 1000) {
			nowOpenOrders.push(buyOpenOrders[i])
			continue;
		}
		if (fixOpenOrders.length == 0 || fixOpenOrders[fixOpenOrders.length - 1].Amout < 0) {
			fixOpenOrders.push(buyOpenOrders[i]);
			continue;
		}
		var mergeOpenOrder = fixOpenOrders[fixOpenOrders.length - 1];
		var mergeAmount = buyOpenOrders[i].Amout + mergeOpenOrder.Amout;
		var mergeMoney = buyOpenOrders[i].Amout * buyOpenOrders[i].Price + mergeOpenOrder.Amout * mergeOpenOrder.Price;
		var mergeRealMoney = buyOpenOrders[i].Amout * buyOpenOrders[i].RealPrice + mergeOpenOrder.Amout * mergeOpenOrder.RealPrice;
		mergeOpenOrder.Amout = mergeAmount;
		mergeOpenOrder.Price = mergeMoney / mergeAmount;
		mergeOpenOrder.RealPrice = mergeRealMoney / mergeAmount;
	}
	for (var i = 0; i < sellOpenOrders.length; i++) {
		if (sellOpenOrders[i].Amout == 0) {
			continue;
		}
		if (sellOpenOrders[i].TransactionTime != null && new Date().getTime() - sellOpenOrders[i].TransactionTime < 5 * 60 * 1000) {
			nowOpenOrders.push(sellOpenOrders[i]);
			continue;
		}
		if (fixOpenOrders.length == 0 || fixOpenOrders[fixOpenOrders.length - 1].Amout > 0) {
			fixOpenOrders.push(sellOpenOrders[i]);
			continue;
		}
		var mergeOpenOrder = fixOpenOrders[fixOpenOrders.length - 1];
		var mergeAmount = sellOpenOrders[i].Amout + mergeOpenOrder.Amout;
		var mergeMoney = sellOpenOrders[i].Amout * sellOpenOrders[i].Price + mergeOpenOrder.Amout * mergeOpenOrder.Price;
		var mergeRealMoney = sellOpenOrders[i].Amout * sellOpenOrders[i].RealPrice + mergeOpenOrder.Amout * mergeOpenOrder.RealPrice;
		mergeOpenOrder.Amout = mergeAmount;
		mergeOpenOrder.Price = mergeMoney / mergeAmount;
		mergeOpenOrder.RealPrice = mergeRealMoney / mergeAmount;

	}
	for (var i = 0; i < nowOpenOrders.length; i++) {
		fixOpenOrders.push(nowOpenOrders[i]);
	}
	RunningInfo.OpenOrders = fixOpenOrders;
}

function marketSell(exIndex, amount, bidPrice) {
	var orderId = null;
	var ex = exchanges[exIndex];
	if (ex.GetName() == "GateIO") {
		orderId = ex.Sell(bidPrice * 0.8, amount, ex.GetCurrency());
		if (orderId == null) {
			Sleep(500);
			orderId = ex.Sell(bidPrice * 0.8, amount, ex.GetCurrency());
		}
	} else {
		orderId = ex.Sell(-1, amount, ex.GetCurrency());
		if (orderId == null) {
			Sleep(500);
			orderId = ex.Sell(-1, amount, ex.GetCurrency());
		}
	}
	if (orderId == null) {
		return null;
	}
	var order = null;
	while (true) {
		order = getOrder(ex, orderId);
		if (order != null && order.Status != ORDER_STATE_PENDING) {
			break;
		} else {
			Sleep(50);
		}
	}
	return order
}

function marketBuy(exIndex, amount, askPrice) {
	var orderId = null;
	var ex = exchanges[exIndex];
	if (ex.GetName() == "GateIO") {
		orderId = ex.Buy(askPrice * 1.2, amount, ex.GetCurrency())
		if (orderId == null) {
			Sleep(500);
			orderId = ex.Buy(askPrice * 1.2, amount, ex.GetCurrency())
		}
	} else {
		orderId = ex.Buy(-1, amount, ex.GetCurrency())
		if (orderId == null) {
			Sleep(500);
			orderId = ex.Buy(-1, amount, ex.GetCurrency())
		}
	}
	if (orderId == null) {
		return null;
	}
	var order = null;
	while (true) {
		order = getOrder(ex, orderId);
		if (order != null && order.Status != ORDER_STATE_PENDING) {
			break;
		} else {
			Sleep(50);
		}
	}
	return order
}

function isBuyAmount(exIndex) {
	return exchanges[exIndex].GetName() == "Binance" || exchanges[exIndex].GetName() == "GateIO";
}


function handleOpenOrders(arbitrageBidRealPrice, arbitrageAskRealPrice, cancelOrderDiffPriceRatio, depths) {
	if (RunningInfo.OpenOrders.length == 0) {
		return false;
	}
	mergeOpenOrders();
	for (var i = 0; i < RunningInfo.OpenOrders.length; i++) {
		var doTransaction = false;
		if (RunningInfo.OpenOrders[i].Amout >= minTakerAmount && arbitrageAskRealPrice > RunningInfo.OpenOrders[i].RealPrice * (1 + cancelOrderDiffPriceRatio)) {
			if (runMode == 1 && (AskOrder.OrderId != 0 && AskOrder.OrderId != null) && getOpenOrdersAmount() - (AskOrder.Amount + AskOrder.DealAmount) < math.min(maxTakerAmount, RunningInfo.OpenOrders[i].Amout)) {
				exchanges[0].CancelOrder(AskOrder.OrderId, "卖出:", AskOrder, "当前单边数量:", getOpenOrdersAmount());
				return false;
			}
			Log("套利路径: 【买入->卖出】, 买入价" + RunningInfo.OpenOrders[i].Price + ", 开始======");
			//买进的小币种
			var takerAmount = math.min(maxTakerAmount, RunningInfo.OpenOrders[i].Amout); //小币种
			var takerMoney = takerAmount * RunningInfo.OpenOrders[i].Price; //大币种
			var takerFeeCost = takerMoney * getExchangeTariff(0); //大币种手续费
			//卖出小币种得到中间币种
			var order = marketSell(1, takerAmount, mergeDepth(depths[1].Bids, takerAmount).Price);
			if (order == null) {
				continue;
			}
			//获得的中间币种
			var swapCoinPrice = order.AvgPrice;
			var swapCoinAmount = order.DealAmount * order.AvgPrice;
			var swapCoinFeeCost = swapCoinAmount * getExchangeTariff(1); //中间币种手续费
			if (CurrencyOverturn) {
				//第三个交易所翻转: CoinC/CoinB, CoinC是中间币种
				order = marketSell(2, swapCoinAmount, mergeDepth(depths[2].Bids, swapCoinAmount).Price)
			} else {
				//通过中间币种买进大币种: CoinB/CoinC
				if (isBuyAmount(2)) {
					//币安设置买进数量
					var hedgeMoney = (RunningInfo.Profit_hedge + RunningInfo.FeeCost_hedge)
					var compensateMoney = 0;
					if (hedgeMoney < -takerMoney) {
						compensateMoney = takerMoney / 4
					} else if (hedgeMoney < 0) {
						compensateMoney = -hedgeMoney
					}
					order = marketBuy(2, takerMoney + compensateMoney, mergeDepth(depths[2].Asks, takerMoney + compensateMoney).Price)
				} else {
					//其他设置买进金额
					order = marketBuy(2, swapCoinAmount, 0)
				}
			}

			if (order == null) {
				RunningInfo.OpenOrders[i].Amout -= takerAmount;
				return true;
			}
			var feeCost = 0;
			var profit = 0;
			if (CurrencyOverturn) {
				feeCost = takerFeeCost + swapCoinFeeCost * order.AvgPrice + order.DealAmount * order.AvgPrice * getExchangeTariff(2);
				profit = order.DealAmount * order.AvgPrice - takerMoney - feeCost;
			} else {
				if (isBuyAmount(2)) { //币安赚取的是中间币种
					feeCost = takerFeeCost * order.AvgPrice + swapCoinFeeCost + order.DealAmount * order.AvgPrice * getExchangeTariff(2);
					profit = swapCoinAmount - order.DealAmount * order.AvgPrice - feeCost;
					RunningInfo.Profit_hedge += (order.DealAmount - takerMoney)
					if (RunningInfo.FeeCost_hedge > 0) {
						feeCost += RunningInfo.FeeCost_hedge * order.AvgPrice
						profit -= RunningInfo.FeeCost_hedge * order.AvgPrice
						RunningInfo.Profit_hedge += RunningInfo.FeeCost_hedge;
						RunningInfo.FeeCost_hedge = 0;
					}
				} else { //其他赚取的是大币种
					feeCost = takerFeeCost + swapCoinFeeCost / order.AvgPrice + order.DealAmount * getExchangeTariff(2);
					profit = order.DealAmount - takerMoney - feeCost;
				}
			}
			RunningInfo.Profit += profit;
			RunningInfo.FeeCost += feeCost;
			RunningInfo.Changed = true;
			Log("套利途径: 【买入->卖出】, 卖出价", CurrencyOverturn ? swapCoinPrice * order.AvgPrice : swapCoinPrice / order.AvgPrice, ", 数量", takerAmount, ", 手续费", feeCost, ", 盈利", profit);
			LogProfit(RunningInfo.Profit + (RunningInfo.CoinProfit - RunningInfo.CoinFeeCost) * RunningInfo.OpenOrders[i].Price);
			RunningInfo.OpenOrders[i].Amout -= takerAmount;
			doTransaction = true;
		} else if (RunningInfo.OpenOrders[i].Amout <= -minTakerAmount && arbitrageBidRealPrice < RunningInfo.OpenOrders[i].RealPrice * (1 - cancelOrderDiffPriceRatio)) {
			if (runMode == 2 && (BidOrder.OrderId != 0 && BidOrder.OrderId != null) && getOpenOrdersAmount() + (BidOrder.Amount - BidOrder.DealAmount) > -math.min(maxTakerAmount, -RunningInfo.OpenOrders[i].Amout)) {
				exchanges[0].CancelOrder(BidOrder.OrderId, "买入:", BidOrder, "当前单边数量:", getOpenOrdersAmount());
				return false;
			}
			Log("套利路径: 【卖出->买入】, 卖出价" + RunningInfo.OpenOrders[i].Price + ", 开始======");
			//卖出的小币种
			var takerAmount = math.min(maxTakerAmount, -RunningInfo.OpenOrders[i].Amout);
			var takerMoney = takerAmount * RunningInfo.OpenOrders[i].Price; //大币种
			var takerFeeCost = takerMoney * getExchangeTariff(0); //大币种手续费
			//卖出大币种得到中间币种
			//todo: 第三个交易对翻转没处理
			var order = marketSell(2, takerMoney, mergeDepth(depths[2].Bids, takerMoney).Price)
			if (order == null) {
				continue;
			}
			//获得的中间币种
			var swapCoinPrice = order.AvgPrice;
			var swapCoinAmount = order.DealAmount * order.AvgPrice;
			var swapCoinFeeCost = swapCoinAmount * getExchangeTariff(2); //中间币种手续费
			var bigSwapPrice = order.AvgPrice;
			//通过中间币种买进小币种
			if (isBuyAmount(1)) {
				//币安设置买进数量
				order = marketBuy(1, takerAmount, mergeDepth(depths[1].Asks, takerAmount).Price)
			} else {
				//其他设置买进金额
				order = marketBuy(1, swapCoinAmount, 0)
			}
			if (order == null) {
				RunningInfo.OpenOrders[i].Amout += takerAmount;
				return true;
			}
			var feeCost = 0;
			var profit = 0;
			var coinProfit = 0;
			var coinFeeCost = 0;
			if (isBuyAmount(1)) { //币安赚取的是中间币种
				feeCost = takerFeeCost * bigSwapPrice + swapCoinFeeCost + order.DealAmount * order.AvgPrice * getExchangeTariff(2);
				profit = swapCoinAmount - order.DealAmount * order.AvgPrice - feeCost;
			} else { //其他赚取的是小币种
				feeCost = takerFeeCost + swapCoinFeeCost / bigSwapPrice + order.DealAmount * order.AvgPrice / bigSwapPrice * getExchangeTariff(2);
				coinProfit = order.DealAmount - takerAmount;
				coinFeeCost = order.DealAmount * getExchangeTariff(1);
			}
			RunningInfo.FeeCost += feeCost;
			RunningInfo.Profit += profit;
			RunningInfo.CoinProfit += coinProfit;
			RunningInfo.CoinFeeCost += coinFeeCost;
			RunningInfo.Changed = true;
			if (isBuyAmount(1)) {
				Log("套利途径: 【卖出->买入】, 买入价", CurrencyOverturn ? swapCoinPrice * order.AvgPrice : order.AvgPrice / swapCoinPrice, ", 数量", takerAmount, ", 手续费", feeCost, ", 盈利", profit);
			} else {
				Log("套利途径: 【卖出->买入】, 买入价", CurrencyOverturn ? swapCoinPrice * order.AvgPrice : order.AvgPrice / swapCoinPrice, ", 数量", takerAmount, ", 手续费", feeCost, ", 获取货币", coinProfit);
			}
			LogProfit(RunningInfo.Profit + (RunningInfo.CoinProfit - RunningInfo.CoinFeeCost) * RunningInfo.OpenOrders[i].Price);
			RunningInfo.OpenOrders[i].Amout += takerAmount;
			doTransaction = true;
		}
		if (doTransaction) {
			return true;
		}
	}
	return false;
}

function loop(bidPrice, askPrice, arbitrageBidRealPrice, arbitrageAskRealPrice, placeOrderDiffPriceRatio, cancelOrderDiffPriceRatio) {
	if ((arbitrageAskRealPrice > bidPrice.RealPrice * (1 + placeOrderDiffPriceRatio) && (runMode == 3 || runMode == 1)) ||
		(runMode == 2 && getOpenOrdersAmount() < -minTakerAmount &&
			(getOpenOrdersAvgRealPrice() > bidPrice.RealPrice ||
				(arbitrageAskRealPrice > bidPrice.RealPrice * (1 - cancelOrderDiffPriceRatio) &&
					arbitrageBidRealPrice > bidPrice.RealPrice * (1 + (placeOrderDiffPriceRatio + cancelOrderDiffPriceRatio) * 1.5)
				)
			)
		)
	) {
		if (BidOrder.OrderId == 0 || BidOrder.OrderId == null) {
			var buyAmount = math.min(AccountInfo.CurBalance / bidPrice.Price, buyMaxTakerAmount);
			if (buyAmount > minTakerAmount) {
				BidOrder.OrderId = exchanges[0].Buy(bidPrice.Price, buyAmount);
				BidOrder.Price = bidPrice.Price;
				BidOrder.RealPrice = bidPrice.RealPrice;
				BidOrder.Amount = buyAmount;
			}
		} else {
			var order = _C(exchanges[0].GetOrder, BidOrder.OrderId);
			fixOrder(exchanges[0], order);
			if (order.Status != ORDER_STATE_PENDING) {
				if (order.DealAmount > BidOrder.DealAmount) {
					RunningInfo.OpenOrders.push({
						"Amout": order.DealAmount - BidOrder.DealAmount,
						"Price": order.Price,
						"RealPrice": order.Price * (1 + getExchangeTariff(0)),
						"TransactionTime": new Date().getTime(),
					});
					if (order.DealAmount - BidOrder.DealAmount > minTakerAmount) {
						Log("买入成交, 数量", order.DealAmount - BidOrder.DealAmount, ", 价格", order.Price, "#ff0000@");
					}
				}
				BidOrder.OrderId = 0;
				BidOrder.DealAmount = 0;
			} else if (bidPrice.Price > BidOrder.Price) {
				exchanges[0].CancelOrder(BidOrder.OrderId, "买入:", BidOrder);
			} else {
				if (order.DealAmount > BidOrder.DealAmount) {
					RunningInfo.OpenOrders.push({
						"Amout": order.DealAmount - BidOrder.DealAmount,
						"Price": order.Price,
						"RealPrice": order.Price * (1 + getExchangeTariff(0)),
						"TransactionTime": new Date().getTime(),
					});
					if (order.DealAmount - BidOrder.DealAmount > minTakerAmount) {
						Log("买入成交, 数量", order.DealAmount - BidOrder.DealAmount, ", 价格", order.Price, "#ff0000@");
					}
					BidOrder.DealAmount = order.DealAmount;
				}
			}
		}
	} else if (BidOrder.OrderId != 0 && BidOrder.OrderId != null) {
		var order = _C(exchanges[0].GetOrder, BidOrder.OrderId);
		fixOrder(exchanges[0], order);
		if (order.Status != ORDER_STATE_PENDING) {
			if (order.DealAmount > BidOrder.DealAmount) {
				RunningInfo.OpenOrders.push({
					"Amout": order.DealAmount - BidOrder.DealAmount,
					"Price": order.Price,
					"RealPrice": order.Price * (1 + getExchangeTariff(0)),
					"TransactionTime": new Date().getTime(),
				});
				if (order.DealAmount - BidOrder.DealAmount > minTakerAmount) {
					Log("买入成交, 数量", order.DealAmount - BidOrder.DealAmount, ", 价格", order.Price, "#ff0000@");
				}
			}
			BidOrder.OrderId = 0;
			BidOrder.DealAmount = 0;
		} else if ((arbitrageAskRealPrice < bidPrice.RealPrice * (1 + cancelOrderDiffPriceRatio) && bidPrice.Price <= BidOrder.Price && (runMode == 3 || runMode == 1)) ||
			(runMode == 2 && getOpenOrdersAvgRealPrice() <= BidOrder.RealPrice && bidPrice.Price <= BidOrder.Price &&
				(arbitrageAskRealPrice < bidPrice.RealPrice * (1 - placeOrderDiffPriceRatio) ||
					arbitrageBidRealPrice < bidPrice.RealPrice * (1 + (placeOrderDiffPriceRatio + cancelOrderDiffPriceRatio))
				)
			)
		) {
			exchanges[0].CancelOrder(BidOrder.OrderId, "买入:", BidOrder);
		} else {
			if (order.DealAmount > BidOrder.DealAmount) {
				RunningInfo.OpenOrders.push({
					"Amout": order.DealAmount - BidOrder.DealAmount,
					"Price": order.Price,
					"RealPrice": order.Price * (1 + getExchangeTariff(0)),
					"TransactionTime": new Date().getTime(),
				});
				if (order.DealAmount - BidOrder.DealAmount > minTakerAmount) {
					Log("买入成交, 数量", order.DealAmount - BidOrder.DealAmount, ", 价格", order.Price, "#ff0000@");
				}
				BidOrder.DealAmount = order.DealAmount;
			}
		}

	}


	if ((arbitrageBidRealPrice < askPrice.RealPrice * (1 - placeOrderDiffPriceRatio) && (runMode == 3 || runMode == 2)) ||
		(runMode == 1 && getOpenOrdersAmount() > minTakerAmount &&
			(getOpenOrdersAvgRealPrice() < askPrice.RealPrice ||
				(arbitrageBidRealPrice < askPrice.RealPrice * (1 + cancelOrderDiffPriceRatio) &&
					arbitrageAskRealPrice < askPrice.RealPrice * (1 - (placeOrderDiffPriceRatio + cancelOrderDiffPriceRatio) * 1.5)
				)
			)
		)
	) {
		if (AskOrder.OrderId == 0 || AskOrder.OrderId == null) {
			var sellAmount = math.min(AccountInfo.CurStocks, sellMaxTakerAmount);
			if (sellAmount > minTakerAmount) {
				AskOrder.OrderId = exchanges[0].Sell(askPrice.Price, sellAmount);
				AskOrder.Price = askPrice.Price;
				AskOrder.RealPrice = askPrice.RealPrice;
				AskOrder.Amount = sellAmount;
			}
		} else {
			var order = _C(exchanges[0].GetOrder, AskOrder.OrderId);
			fixOrder(exchanges[0], order);
			if (order.Status != ORDER_STATE_PENDING) {
				if (order.DealAmount > -AskOrder.DealAmount) {
					RunningInfo.OpenOrders.push({
						"Amout": -(order.DealAmount + AskOrder.DealAmount),
						"Price": order.Price,
						"RealPrice": order.Price * (1 - getExchangeTariff(0)),
						"TransactionTime": new Date().getTime(),
					});
					if (order.DealAmount + AskOrder.DealAmount > minTakerAmount) {
						Log("卖出成交, 数量", order.DealAmount + AskOrder.DealAmount, ", 价格", order.Price, "#ff0000@");
					}
				}
				AskOrder.OrderId = 0;
				AskOrder.DealAmount = 0;
			} else if (askPrice.Price < AskOrder.Price) {
				exchanges[0].CancelOrder(AskOrder.OrderId, "卖出:", AskOrder);
			} else {
				if (order.DealAmount > -AskOrder.DealAmount) {
					RunningInfo.OpenOrders.push({
						"Amout": -(order.DealAmount + AskOrder.DealAmount),
						"Price": order.Price,
						"RealPrice": order.Price * (1 - getExchangeTariff(0)),
						"TransactionTime": new Date().getTime(),
					});
					if (order.DealAmount + AskOrder.DealAmount > minTakerAmount) {
						Log("卖出成交, 数量", order.DealAmount + AskOrder.DealAmount, ", 价格", order.Price, "#ff0000@");
					}
					AskOrder.DealAmount = -order.DealAmount;
				}
			}
		}
	} else if (AskOrder.OrderId != 0 && AskOrder.OrderId != null) {
		var order = _C(exchanges[0].GetOrder, AskOrder.OrderId);
		fixOrder(exchanges[0], order);
		if (order.Status != ORDER_STATE_PENDING) {
			if (order.DealAmount > -AskOrder.DealAmount) {
				RunningInfo.OpenOrders.push({
					"Amout": -(order.DealAmount + AskOrder.DealAmount),
					"Price": order.Price,
					"RealPrice": order.Price * (1 - getExchangeTariff(0)),
					"TransactionTime": new Date().getTime(),
				});
				if (order.DealAmount + AskOrder.DealAmount > minTakerAmount) {
					Log("卖出成交, 数量", order.DealAmount + AskOrder.DealAmount, ", 价格", order.Price, "#ff0000@");
				}
			}
			AskOrder.OrderId = 0;
			AskOrder.DealAmount = 0;
		} else if ((arbitrageBidRealPrice > askPrice.RealPrice * (1 - cancelOrderDiffPriceRatio) && askPrice.Price >= AskOrder.Price && (runMode == 3 || runMode == 2)) ||
			(runMode == 1 && getOpenOrdersAvgRealPrice() >= AskOrder.RealPrice && askPrice.Price >= AskOrder.Price &&
				(arbitrageBidRealPrice > askPrice.RealPrice * (1 + placeOrderDiffPriceRatio) ||
					arbitrageAskRealPrice > askPrice.RealPrice * (1 - (placeOrderDiffPriceRatio + cancelOrderDiffPriceRatio))
				)
			)
		) {
			exchanges[0].CancelOrder(AskOrder.OrderId, "卖出:", AskOrder);
		} else {
			if (order.DealAmount > -AskOrder.DealAmount) {
				RunningInfo.OpenOrders.push({
					"Amout": -(order.DealAmount + AskOrder.DealAmount),
					"Price": order.Price,
					"RealPrice": order.Price * (1 - getExchangeTariff(0)),
					"TransactionTime": new Date().getTime(),
				});
				if (order.DealAmount + AskOrder.DealAmount > minTakerAmount) {
					Log("卖出成交, 数量", order.DealAmount + AskOrder.DealAmount, ", 价格", order.Price, "#ff0000@");
				}
				AskOrder.DealAmount = -order.DealAmount;
			}
		}

	}
}

function checkArgs() {
	Log("检测参数...");
	if (exchanges.length != 3) {
		throw "交易对必须三个才能对冲";
	}
	if (exchanges[0].GetName() != exchanges[1].GetName() || exchanges[0].GetName() != exchanges[2].GetName()) {
		throw "交易对不在同一个交易所";
	}
	var coinA = exchanges[0].GetCurrency().split("_")[0];
	var coinB = exchanges[0].GetCurrency().split("_")[1];
	if (exchanges[1].GetCurrency().split("_")[0] != coinA) {
		throw "第二个交易对" + exchanges[1].GetCurrency() + "币种不等于" + coinA;
	}
	var coinC = exchanges[1].GetCurrency().split("_")[1];
	if (exchanges[2].GetCurrency() == coinB + "_" + coinC) {
		CurrencyOverturn = false;
	} else if (exchanges[2].GetCurrency() == coinC + "_" + coinB) {
		CurrencyOverturn = true;
	} else {
		throw "第三个交易对" + exchanges[2].GetCurrency() + "无法形成对冲";
	}
	if (placePriceRatio < 0.5) {
		throw "下单价格比例:" + placePriceRatio + " 小于0.5";
	}
	if (cancelPriceRatio > placePriceRatio) {
		throw "撤单价格比例:" + cancelPriceRatio + " 大于" + placePriceRatio;
	}
	Log("开始三角套利 CoinA:", coinA, ", CoinB:", coinB, ", CoinC:", coinC, ", 第三个交易对翻转:", CurrencyOverturn);
}


function updateStatus(totalElapse) {
	var coinA = exchanges[0].GetCurrency().split("_")[0];
	var coinB = exchanges[0].GetCurrency().split("_")[1];
	var coinC = exchanges[1].GetCurrency().split("_")[1];
	var makeCoin = !CurrencyOverturn && isBuyAmount(2) ? coinC : coinB;
	var profitPrecisions = !CurrencyOverturn && isBuyAmount(2) ? parseInt(pricePrecisions.split(",")[1]) : parseInt(pricePrecisions.split(",")[0]);
	var statusTable = [{
		type: 'table',
		title: '持仓信息',
		cols: [],
		rows: []
	}, {
		type: 'table',
		title: '当前订单',
		cols: ['订单号', '数量', '完成数量', '价格'],
		rows: []
	}, {
		type: 'table',
		title: '单边成交',
		cols: ['数量', '价格'],
		rows: []
	}];
	if (makeCoin != coinB) {
		statusTable[0].cols = ['交易所', '费率%', '延迟', '当前余额', '当前币数',
			'收益' + makeCoin, '收益' + coinA, '手续费' + makeCoin, "对冲收益" + coinB, "对冲手续费" + coinB, '操作'
		];
		statusTable[0].rows[0] = [
			exchanges[0].GetName(),
			getExchangeTariff(0) * 100,
			totalElapse,
			_N(AccountInfo.CurBalance, parseInt(pricePrecisions.split(",")[0])),
			_N(AccountInfo.CurStocks, parseInt(amountPrecisions.split(",")[0])),
			_N(RunningInfo.Profit, profitPrecisions),
			_N(RunningInfo.CoinProfit, parseInt(amountPrecisions.split(",")[0])),
			_N(RunningInfo.FeeCost, profitPrecisions),
			_N(RunningInfo.Profit_hedge, parseInt(pricePrecisions.split(",")[0])),
			_N(RunningInfo.FeeCost_hedge, parseInt(pricePrecisions.split(",")[0])),
		]
	} else {
		statusTable[0].cols = ['交易所', '费率%', '延迟', '当前余额', '当前币数',
			'收益' + makeCoin, '收益' + coinA, '手续费' + makeCoin, '操作'
		];
		RunningInfo.Profit += RunningInfo.Profit_hedge;
		RunningInfo.Profit_hedge = 0;
		RunningInfo.FeeCost += RunningInfo.FeeCost_hedge;
		RunningInfo.FeeCost_hedge = 0;
		statusTable[0].rows[0] = [
			exchanges[0].GetName(),
			getExchangeTariff(0) * 100,
			totalElapse,
			_N(AccountInfo.CurBalance, parseInt(pricePrecisions.split(",")[0])),
			_N(AccountInfo.CurStocks, parseInt(amountPrecisions.split(",")[0])),
			_N(RunningInfo.Profit, profitPrecisions),
			_N(RunningInfo.CoinProfit, parseInt(amountPrecisions.split(",")[0])),
			_N(RunningInfo.FeeCost, profitPrecisions),
		]
	}
	if (AskOrder.OrderId != 0 && AskOrder.OrderId != null) {
		statusTable[1].rows[statusTable[1].rows.length] = [AskOrder.OrderId, AskOrder.Amount, AskOrder.DealAmount, AskOrder.Price];
	}
	if (BidOrder.OrderId != 0 && BidOrder.OrderId != null) {
		statusTable[1].rows[statusTable[1].rows.length] = [BidOrder.OrderId, BidOrder.Amount, BidOrder.DealAmount, BidOrder.Price];
	}
	for (var i = 0; i < RunningInfo.OpenOrders.length; i++) {
		statusTable[2].rows[i] = [RunningInfo.OpenOrders[i].Amout, RunningInfo.OpenOrders[i].Price];
	}
	LogStatus('`' + JSON.stringify(statusTable) + '`' +
		"\n更新时间: " + _D(new Date().getTime())
	);
}


function main() {
	_CDelay(100);
	checkArgs();
	if (restoreData) {
		LogProfitReset();
		LogReset(1);
		LogVacuum();
		pricesChart.reset();
		_G(null);
	}
	SetErrorFilter("500:|502:|GetAccount: Failed to load data|Unknown order sent|Incorrect order state|timeout|network is unreachable|The request could not be satisfied|Too many requests; current limit is 1200 requests per minute|Request Timeout|timeout awaiting response headers|request canceled|GetOrder[\\w\\W]*?429 Too Many Requests|GetOrder[\\w\\W]*?Order does not exist|The order \\d* cancelled or not found");
	for (var i = 0; i < exchanges.length; i++) {
		exchanges[i].SetPrecision(parseInt(pricePrecisions.split(",")[i]), parseInt(amountPrecisions.split(",")[i]));
	}
	var runningInfo = _G("runningInfo");
	if (runningInfo != null) {
		RunningInfo = runningInfo;
		if (RunningInfo.CoinFeeCost == null) {
			RunningInfo.CoinFeeCost = 0;
		}
		if (RunningInfo.FeeCost_hedge == null) {
			RunningInfo.FeeCost_hedge = 0;
		}
		if (RunningInfo.Profit_hedge == null) {
			RunningInfo.Profit_hedge = 0;
		}
	}
	updateAccountInfo(true);
	Log(AccountInfo);

	while (true) {
		var sleepMillis = 500;
		var depthInfo = getDepthInfo();
		var reloop = false;
		for (var i = 0; i < depthInfo.elapses.length; i++) {
			if (depthInfo.elapses[i] > maxElapse) {
				reloop = true;
				break
			}
		}
		if (reloop) {
			Sleep(sleepMillis);
			continue;
		}
		var depths = depthInfo.depths;
		var bidPrice = {
			"Price": depths[0].Bids[0].Price,
			"RealPrice": depths[0].Bids[0].Price * (1 + getExchangeTariff(0))
		};
		var askPrice = {
			"Price": depths[0].Asks[0].Price,
			"RealPrice": depths[0].Asks[0].Price * (1 - getExchangeTariff(0))
		};
		var arbitrageBidRealPrice = getArbitrageBidRealPrice(depths);
		var arbitrageAskRealPrice = getArbitrageAskRealPrice(depths);
		var diffPriceRatio = (askPrice.RealPrice - bidPrice.RealPrice) / askPrice.RealPrice;
		if (diffPriceRatio < 0.5 / 100) {
			throw "交易对差价比例过小"
		}
		var placeOrderDiffPriceRatio = placePriceRatio * (diffPriceRatio / 2);
		var cancelOrderDiffPriceRatio = math.max(cancelPriceRatio * (diffPriceRatio / 2), 0.1 / 100);
		var bidOrderId = BidOrder.OrderId;
		var askOrderId = AskOrder.OrderId;
		var openOrdersAmount = getOpenOrdersAmount();
		if (runMode != 0) {
			if (handleOpenOrders(arbitrageBidRealPrice, arbitrageAskRealPrice, cancelOrderDiffPriceRatio, depths)) {
				sleepMillis = 50
			} else {
				loop(bidPrice, askPrice, arbitrageBidRealPrice, arbitrageAskRealPrice, placeOrderDiffPriceRatio, cancelOrderDiffPriceRatio);
			}
			_G("runningInfo", RunningInfo);
		}
		flushAccountInfo = BidOrder.OrderId != bidOrderId || AskOrder.OrderId != askOrderId || openOrdersAmount != getOpenOrdersAmount();
		updateAccountInfo(flushAccountInfo);
		updatePriceChart(bidPrice, askPrice, arbitrageBidRealPrice, arbitrageAskRealPrice, placeOrderDiffPriceRatio, cancelOrderDiffPriceRatio);
		updateStatus(depthInfo.totalElapse);
		Sleep(sleepMillis);
	}
}

function onexit() {
	cancelAllOrders();
}

function onerror() {
	cancelAllOrders();
}

function cancelAllOrders() {
	Log("取消所有订单...")
	var orders = _C(exchanges[0].GetOrders);
	for (var i = 0; i < orders.length; i++) {
		exchanges[0].CancelOrder(orders[i].Id, orders[i]);
	}
}

More

xunfeng91 这是现

BNMBNM 大佬 请教一下 怎么老是报 交易对差价比例过小

trump 你好,有联系方式吗 想合作