策略功能: 实现跨交易所的现现对冲 支持无限个交易所同时搬砖 支持任意交易对搬砖 详细报表支持(完整的状态信息, 可实时看到策略观察到的各个交易所的差价信息, 持仓信息,资金净值,策略运行状况) 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; } } }