策略功能: 实现跨交易所的现现对冲 支持无限个交易所同时搬砖 支持任意交易对搬砖 详细报表支持(完整的状态信息, 可实时看到策略观察到的各个交易所的差价信息, 持仓信息,资金净值,策略运行状况) 24小时无人值守, 全自动运行
策略参数设置: 请访问 https://www.pcclean.io/9wbl
/*
现现对冲(搬砖)
注意:
1.确保设置了交易所的手续费和最小买入量
版本历史:
21:33 2018/10/25 处理挂单不成交的情况,不再使用市价单
7:28 2018/10/26 支持永久记录收益
8:00 2018/10/27 支持显示币数变化,搬砖函数每次更新getaccount
10:14 2018/10/27 修复不显示profit chart的问题
*/
//搬砖参数设置
var handfee= {FCoin:0.0 , CoinEx:0.0004,Huobi:0.002,OKEX:0.002,Binance:0.001,ZB:0.002 };//手续费
var minestbuy={FCoin:0.01, CoinEx:0.01, Huobi:0.01, OKEX:0.01, Binance:0.04, ZB:0.01 }; //最小买入量
var want_profit=0.0001;//期望收益
var price_step=0.00001;//定价单调整价格的单位
var maxbuy=5; //最大买入
var warning_stocks=10;//余额预警
var price_n=6; //价格精度
var num_n=3; //数量精度
var max_wait_order=15000;//订单等待时间
var buypercent=1.0; //买入百分比
var wait_ms=3000;//重试等待间隔时间
var enable_traders_recorder=true;//记录所有交易
//全局变量
var limit_orders=[];//限价单
var total_banzhuan=0;//搬砖次数
var total_op=0;//总搬砖机会
var exchange_banzhuan={};//记录交易所搬砖次数
var total_passed=0;//因为余额不足错过了多少次搬砖机会
var trades=[];//所有交易
var approximate_profit=0;//盈亏近似值
var dealamount_change=0;//币数变化
function get_exchange_banzhuan(exname){
var name = exname.replace(" ", "_");
if (isNaN(exchange_banzhuan[name])){
return 0;
}else{
return exchange_banzhuan[name];
}
}
function put_exchange_banzhuan(exname,value){
var name = exname.replace(" ", "_");
exchange_banzhuan[name]=value;
}
//在交易所ex1和ex2之间搬砖
function banzhuan(exchange1,exchange2)
{
//read price from exchange 1
var account1=_C(exchange1.GetAccount);
var exname1=exchange1.GetName();
var depth1 = _C(exchange1.GetDepth);
var askprice1=depth1.Asks[0].Price;
var askamount1=depth1.Asks[0].Amount;
//read price from exchange 2
var account2=_C(exchange2.GetAccount);
var exname2=exchange2.GetName();
var depth2 = _C(exchange2.GetDepth);
var bidprice2=depth2.Bids[0].Price;
var bidamount2=depth2.Bids[0].Amount;
var threshold=handfee[exname1]+handfee[exname2]+want_profit; //交易阀值
var minbuy=Math.max(minestbuy[exname1],minestbuy[exname2]); //最小购买数量
//buy and sell if its prifitable
var diff=(bidprice2-askprice1)/askprice1;
var canbuy=Math.min(askamount1,bidamount2);
canbuy=canbuy*buypercent;
var min=Math.min(canbuy,maxbuy); //the minimum number for buy/sell
if (Math.abs(diff)>=threshold && min>=minbuy) //for bch
{
if (diff>0)//exchange 1 is cheap
{
total_op=total_op+2;
put_exchange_banzhuan(exname1+"_op",get_exchange_banzhuan(exname1+"_op")+1);
put_exchange_banzhuan(exname2+"_op",get_exchange_banzhuan(exname2+"_op")+1);
put_exchange_banzhuan(exname1+"_canbuy",get_exchange_banzhuan(exname1+"_canbuy")+min);
put_exchange_banzhuan(exname2+"_cansell",get_exchange_banzhuan(exname2+"_cansell")+min);
var needmoney=min*askprice1;
if (account2.Stocks>min && account1.Balance>needmoney ){
//buy from exchange 1
var price_1=askprice1;
var buyID=retryBuy(exchange1,price_1,min);
//sell from exchange 2
var price_2=bidprice2;
var sellID=retrySell(exchange2,price_2,min);
//add orders to pre-processing list
var order1={
exchange:exchange1,
ID:buyID,
create_time:new Date(),
utcdate:get_ChinaTimeString()
};
limit_orders.push(order1);
var order2={
exchange:exchange2,
ID:sellID,
create_time:new Date(),
utcdate:get_ChinaTimeString()
};
limit_orders.push(order2);
total_banzhuan=total_banzhuan+2;
put_exchange_banzhuan(exname1,get_exchange_banzhuan(exname1)+1);
put_exchange_banzhuan(exname2,get_exchange_banzhuan(exname2)+1);
}else{
if (account2.Stocks<=min){
Log(exchange2.GetName()+"没币了,无法搬砖。请及时充值。"+"#ff0000");
}
if (account1.Balance<=needmoney){
Log(exchange1.GetName()+"没钱了,无法搬砖。请及时充值。"+"#ff0000");
}
total_passed=total_passed+2;
}
}
}
}
//检查订单是否执行成功,如果未完成,执行市价下单
function process_limiteorders(){
var cur_time=new Date();
var limit_orders_new=[];
for (var i=0; i<limit_orders.length; ++i){
var create_time=limit_orders[i].create_time;
var passedtime=cur_time-create_time;
if (passedtime>max_wait_order){
var exchange_c=limit_orders[i].exchange;
var order_ID=limit_orders[i].ID;
var exname=exchange_c.GetName();
var account=_C(exchange_c.GetAccount);
var ticker=_C(exchange_c.GetTicker);
var last=ticker.Last;
var orderdata=_C(exchange_c.GetOrder,order_ID);
var type=orderdata.Type;
if (orderdata.Status!=ORDER_STATE_CLOSED){
var notcompleted=orderdata.Amount-orderdata.DealAmount;
exchange_c.CancelOrder(order_ID);
if (type===ORDER_TYPE_BUY){
var allowbuy=Math.min(account.Balance/last,notcompleted);
if (allowbuy>minestbuy[exname]){
var limited_order_ID=retryBuy(exchange_c,orderdata.Price+price_step,allowbuy);
Log("加价买入。"+orderdata.Price+"|"+(orderdata.Price+price_step));
var limited_order_data={
exchange:exchange_c,
ID:limited_order_ID,
create_time:new Date(),
utcdate:get_ChinaTimeString()
};
limit_orders_new.push(limited_order_data);
}
approximate_profit-=(orderdata.Price*orderdata.DealAmount*(1+handfee[exname]));
dealamount_change+=(orderdata.DealAmount);
}else if (type===ORDER_TYPE_SELL){
var allowsell=Math.min(account.Stocks,notcompleted);
if (allowsell>minestbuy[exname]){
var limited_order_ID=retrySell(exchange_c,orderdata.Price-price_step,allowsell);
Log("降价卖出。"+orderdata.Price+"|"+(orderdata.Price-price_step));
var limited_order_data={
exchange:exchange_c,
ID:limited_order_ID,
create_time:new Date(),
utcdate:get_ChinaTimeString()
};
limit_orders_new.push(limited_order_data);
}
approximate_profit+=(orderdata.Price*orderdata.DealAmount*(1-handfee[exname]));
dealamount_change-=(orderdata.DealAmount);
}
//保存交易信息
if (type===ORDER_TYPE_BUY){
var details={
type:"限价买",
time:limit_orders[i].utcdate,
RealAmount:orderdata.DealAmount,
WantAmount:orderdata.Amount,
RealPrice:orderdata.Price,
WantPrice:orderdata.Price,
Memo:"部分完成",
exname:exname
};
if (enable_traders_recorder){
trades.push(details);
}
}else if (type===ORDER_TYPE_SELL){
var details={
type:"限价卖",
time:limit_orders[i].utcdate,
RealAmount:orderdata.DealAmount,
WantAmount:orderdata.Amount,
RealPrice:orderdata.Price,
WantPrice:orderdata.Price,
Memo:"部分完成",
exname:exname
};
if (enable_traders_recorder){
trades.push(details);
}
}
}else{
//保存交易信息
if (type===ORDER_TYPE_BUY){
approximate_profit-=(orderdata.Price*orderdata.DealAmount*(1+handfee[exname]));
dealamount_change+=(orderdata.DealAmount);
var details={
type:"限价买",
time:limit_orders[i].utcdate,
RealAmount:orderdata.DealAmount,
WantAmount:orderdata.Amount,
RealPrice:orderdata.Price,
WantPrice:orderdata.Price,
Memo:"已完成",
exname:exname
};
if (enable_traders_recorder){
trades.push(details);
}
}else if (type===ORDER_TYPE_SELL){
approximate_profit+=(orderdata.Price*orderdata.DealAmount*(1-handfee[exname]));
dealamount_change-=(orderdata.DealAmount);
var details={
type:"限价卖",
time:limit_orders[i].utcdate,
RealAmount:orderdata.DealAmount,
WantAmount:orderdata.Amount,
RealPrice:orderdata.Price,
WantPrice:orderdata.Price,
Memo:"已完成",
exname:exname
};
if (enable_traders_recorder){
trades.push(details);
}
}
}
}else{
limit_orders_new.push(limit_orders[i]);
}
}
limit_orders=limit_orders_new;
}
//重试购买,直到成功返回
function retryBuy(ex,price,num)
{
var r=ex.Buy(_N(price,price_n), _N(num,num_n));
while (!r){
Log("Buy失败,正在retry。");
Sleep(wait_ms);
r=ex.Buy(_N(price,price_n), _N(num,num_n));
}
return r;
}
//重试卖出,直到成功返回
function retrySell(ex,price,num)
{
var r=ex.Sell(_N(price,price_n), _N(num,num_n));
while (!r){
Log("Sell失败,正在retry。");
Sleep(wait_ms);
r=ex.Sell(_N(price,price_n), _N(num,num_n));
}
return r;
}
//get utc+8 date string
function get_ChinaTimeString()
{
var date = new Date();
var now_utc = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(),date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
var cdate=new Date(now_utc);
cdate.setHours(cdate.getHours()+8);
var localstring=cdate.getFullYear()+'/'+(cdate.getMonth()+1)+'/'+cdate.getDate()+' '+cdate.getHours()+':'+cdate.getMinutes()+':'+cdate.getSeconds();
return localstring;
}
//主逻辑
function main(){
_CDelay(wait_ms);
var exchange_num=exchanges.length;
var pre_time = new Date();
var start_time=new Date();
var last_profit=0;
var pre_profit=Number(_G("pre_profit"));
Log('之前收入累计:'+pre_profit);
while(true){
var cur_time = new Date();
var passedtime=cur_time-pre_time;
pre_time=cur_time;
for (var i=0; i<exchange_num-1; ++i)
{
for (var j=i+1;j<exchange_num; ++j)
{
banzhuan(exchanges[i],exchanges[j]);
banzhuan(exchanges[j],exchanges[i]);
}
}
process_limiteorders();
var total_orders_notcomplete=0;
var total_balance=0;
var table1 = {type: 'table', title: '账户信息', cols: ['交易所', '最新价','可用币','可用余额','资产','手续费率','最小购买数量','未完成订单','实搬/搬砖机会','可买/可卖'], rows: []};
var table2 = {type: 'table', title: '状态', cols: ['轮询时间','实际搬砖次数','错过搬砖次数','每天搬砖机会','当前总资产','【近似收益】','币数变化','未完成订单总数'], rows: []};
var table3 = {type: 'table', title: '交易历史', cols: ['交易所','日期','类型', '成交数量','发单数量','成交价','发单价','备注'], rows: []};
for (i=0; i<exchange_num; ++i){
var exname=exchanges[i].GetName();
var account=_C(exchanges[i].GetAccount);
var ticker=_C(exchanges[i].GetTicker);
var last=ticker.Last;
var orders_not_complete=_C(exchanges[i].GetOrders);
total_orders_notcomplete+=orders_not_complete.length;
var balance=(account.Balance+account.FrozenBalance)+(account.Stocks+account.FrozenStocks)*last;
total_balance+=balance;
table1.rows.push([exname,last,
account.Stocks+(account.Stocks<=warning_stocks?"#ff0000":"#00ff00"),
account.Balance+(account.Balance<=warning_stocks*last?"#ff0000":"#00ff00"),
balance,handfee[exname],minestbuy[exname],orders_not_complete.length,
get_exchange_banzhuan(exname)+'/'+get_exchange_banzhuan(exname+"_op"),
get_exchange_banzhuan(exname+"_canbuy")+'/'+get_exchange_banzhuan(exname+"_cansell")
]);
}
table2.rows.push([passedtime+'ms',total_banzhuan,total_passed,
total_op/((new Date()-start_time)/1000/60/60/24),
total_balance+' '+exchanges[0].GetQuoteCurrency(),
approximate_profit+' '+exchanges[0].GetQuoteCurrency(),
dealamount_change, total_orders_notcomplete
]);
for (i=0; i < trades.length; i++){
table3.rows.push([trades[i].exname,trades[i].time,trades[i].type,trades[i].RealAmount,trades[i].WantAmount,trades[i].RealPrice,trades[i].WantPrice,trades[i].Memo]);
}
//show tables
LogStatus('`' + JSON.stringify([table1,table2,table3])+'`'+'\n'+
' *提示:红色代表余额可能不足。'+'\n'+
'定制策略请联系微信: alinwo (验证消息: botvs)#0000ff'+'\n');
//show profit chart
if (last_profit!==approximate_profit && total_orders_notcomplete===0 && limit_orders.length===0){
LogProfit(pre_profit+approximate_profit);
_G("pre_profit", pre_profit+approximate_profit);
last_profit=approximate_profit;
}
}
}