海龟交易策略是趋势策略中非常经典的交易策略,能在交易风险,仓位,资金利用率各方面做到很精细的控制。只要市场出现较大趋势(不管基于分钟K线,小时K线还是日K),海龟都会捕获到趋势并让利润最大化。此策略基于原版海龟交易法则实现,在2017年大牛市下取得了700%,远远高于大盘的收益。
策略特点: 支持任意级别的趋势跟踪(分钟K,小时K,日K,周K等) 支持任意交易对(ETH/BTC, BSV/BTC等) 详细的策略报表(包括策略状态,交易历史记录等) 支持10几个自定义个性化参数
参数说明: 请查看 https://www.pcclean.io/z6zl
/*
海龟策略
用途:分散风险,平抑波动
17:34 2018/9/12 retrySell后加sleep,解决清仓余额不足的问题
17:58 2018/9/12 fixed Sell(-1, 0.00033): Less than minimum requirement
8:58 2018/9/13 fixed 无限仓位问题
11:12 2018/9/13 符合海龟交易法则
20:23 2018/9/23 修改清仓时不更新account.stock的问题
11:14 2018/10/18 retrysell函数支持 对最小数量的自动修正
11:16 2018/10/19 支持utc+8时间和logprofit
15:13 2018/10/19 支持对象化和管理多个交易对
23:28 2018/10/20 支持统计手续费损失
9:05 2018/10/22 retryBuy支持自动修正买入量
22:10 2018/12/22 支持统计市价单盈亏
11:40 2019/04/25 支持收益曲线连续
*/
var ExchangProcessor={
createNew: function(exc_obj){
//策略参数
var manage_assets=1;//bch
var max_positions=4;//max=4N
var price_n={BTC_BCH:8,ETH_BCH:8,XRP_BCH:8,EOS_BCH:8,LTC_BCH:8,DASH_BCH:8,CET_BCH:8}; //价格精度
var num_n={BTC_BCH:8,ETH_BCH:8,XRP_BCH:8,EOS_BCH:8,LTC_BCH:8,DASH_BCH:8,CET_BCH:8}; //数量精度
var minest_buy={BTC_BCH:0.001,ETH_BCH:0.01,XRP_BCH:1,EOS_BCH:1,LTC_BCH:0.1,DASH_BCH:0.01,CET_BCH:1};//最小买入量
var minest_sell={BTC_BCH:0.001,ETH_BCH:0.01,XRP_BCH:1,EOS_BCH:1,LTC_BCH:0.1,DASH_BCH:0.01,CET_BCH:1};//最小卖出量
var order_wait_secs=120000; //订单的最长等待时间 毫秒
var wait_ms=1000;//默认等待毫秒
var sxf=0.0005;//用来计算手续费消耗
//全局状态变量
var positions=[];//记录仓位
var init_asset=0; //初始资产
var trades=[];//所有交易
var trades_recorder=true;//记录所有交易
var pre_time=null; //记录轮询间隔时间
var approximate_profit=0;//盈亏近似值
var add_already=0;//已经加仓次数
var processor={};
//重试购买,直到成功返回
processor.retryBuy=function(ex,price,num)
{
var currency=ex.GetCurrency();
var r=ex.Buy(_N(price,price_n[currency]), _N(num,num_n[currency]));
while (!r){
Log("Buy失败,正在retry。");
Sleep(wait_ms);
var account=_C(ex.GetAccount);
var ticker=_C(ex.GetTicker);
var last=ticker.Last;
var fixedAmount=(price===-1?Math.min(account.Balance*0.95,num):Math.min(account.Balance/last*0.95,num));
r=ex.Buy(_N(price,price_n[currency]), _N(fixedAmount,num_n[currency]));
}
return r;
}
//重试卖出,直到成功返回
processor.retrySell=function(ex,price,num){
var currency=ex.GetCurrency();
var r=ex.Sell(_N(price,price_n[currency]), _N(num,num_n[currency]));
while (!r){
Log("Sell失败,正在retry。");
Sleep(wait_ms);
var account=_C(ex.GetAccount);
var fixedAmount=Math.min(account.Stocks,num);
r=ex.Sell(_N(price,price_n[currency]), _N(fixedAmount,num_n[currency]));
}
return r;
}
processor.get_ChinaTimeString=function(){
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;
}
processor.init_obj=function(){
_CDelay(wait_ms);
pre_time = new Date();
//init
{
var account=_C(exc_obj.GetAccount);
var ticker=_C(exc_obj.GetTicker);
var last=ticker.Last;
init_asset=(account.Balance+account.FrozenBalance)+(account.Stocks+account.FrozenStocks)*last;
Sleep(wait_ms);
}
}
processor.work=function(){
var cur_time = new Date();
var passedtime=cur_time-pre_time;
pre_time=cur_time;
//计算n,头寸
var exname=exc_obj.GetName();
var currency=exc_obj.GetCurrency();
var account=_C(exc_obj.GetAccount);
var ticker=_C(exc_obj.GetTicker);
var depth = _C(exc_obj.GetDepth);
var last=ticker.Last;
var ask1=depth.Asks[0].Price;
var bid1=depth.Bids[0].Price;
var bestprice=bid1+(Math.abs(ask1-bid1)/2);
var records = _C(exc_obj.GetRecords);
if (records.length<=50){
Log("records.length is not valid.");
Sleep(wait_ms);
return;
}
var atr = TA.ATR(records, 20);
if (atr.length<=1){
Log("atr.length is not valid.");
Sleep(wait_ms);
return;
}
var N=atr[atr.length-1];
var position_unit=Math.min(manage_assets*0.01/N,account.Balance/last*0.95);//cet
//Log("N="+N+", 头寸单位="+position_unit+"CET");
var highest=TA.Highest(records, 20, 'High');
var Lowest=TA.Lowest(records, 10, 'Low');
var cur_asset=(account.Balance+account.FrozenBalance)+(account.Stocks+account.FrozenStocks)*last;
var rsi6 = TA.RSI(records, 6);
var rsi12 = TA.RSI(records, 12);
if (rsi6.length<=5 || rsi12.length<=5){
Log("rsi is not valid.");
Sleep(wait_ms);
return;
}
var rsi_in=false;
if (rsi6[rsi6.length-1]-rsi6[rsi6.length-2]>5 &&
rsi6[rsi6.length-3]-rsi6[rsi6.length-2]>5 &&
rsi6[rsi6.length-2]<=55 &&
rsi6[rsi6.length-1]>rsi12[rsi12.length-1]){
//Log("rsi_in=true");
rsi_in=true;
}
var rsi_out=false;
if (rsi6[rsi6.length-2]-rsi6[rsi6.length-1]>5 &&
rsi6[rsi6.length-2]>=70){
//Log("rsi_out=true");
rsi_out=true;
}
//建仓
if (positions.length==0 && position_unit>minest_buy[currency]){
if (last>=highest)
{
var buyID = processor.retryBuy(exc_obj,last,position_unit);
Sleep(order_wait_secs);
var buyOrder=_C(exc_obj.GetOrder,buyID);
if (buyOrder.Status!=ORDER_STATE_CLOSED){
exc_obj.CancelOrder(buyID);
}
if (buyOrder.DealAmount>0){
var postion = {
amount:buyOrder.DealAmount,
buy_price:buyOrder.AvgPrice,
stoploss_price:buyOrder.AvgPrice-2*N};
positions.push(postion);
var details={
type:"建仓",
time:processor.get_ChinaTimeString(),
RealAmount:buyOrder.DealAmount,
WantAmount:position_unit,
RealPrice:buyOrder.AvgPrice,
WantPrice:buyOrder.Price,
Memo:""
};
if (trades_recorder){
trades.push(details);
}
add_already=1;
}
}
}
//加仓
if (positions.length>0 && position_unit>minest_buy[currency]){
var last_buy_price=positions[positions.length-1].buy_price;
if (add_already<max_positions){//max = 4N
if (last-last_buy_price>=0.5*N){
var buyID = processor.retryBuy(exc_obj,last,position_unit);
Sleep(order_wait_secs);
var buyOrder=_C(exc_obj.GetOrder,buyID);
if (buyOrder.Status!=ORDER_STATE_CLOSED){
exc_obj.CancelOrder(buyID);
}
if (buyOrder.DealAmount>0){
var postion = {
amount:buyOrder.DealAmount,
buy_price:buyOrder.AvgPrice,
stoploss_price:buyOrder.AvgPrice-2*N};
positions.push(postion);
var details={
type:"加仓",
time:processor.get_ChinaTimeString(),
RealAmount:buyOrder.DealAmount,
WantAmount:position_unit,
RealPrice:buyOrder.AvgPrice,
WantPrice:last,
Memo:""
};
if (trades_recorder){
trades.push(details);
}
add_already=add_already+1;
}
}
}
}
//止损
if (positions.length>0){
var positions_new=[];
for (var i=0; i < positions.length; i++){
if (last<=positions[i].stoploss_price){
account=_C(exc_obj.GetAccount);
var fixedAmount=Math.min(account.Stocks,positions[i].amount);
if (fixedAmount>minest_sell[currency]){
var sellID = processor.retrySell(exc_obj, last, fixedAmount);
Sleep(order_wait_secs);
var sellOrder=_C(exc_obj.GetOrder,sellID);
approximate_profit+=(sellOrder.AvgPrice*sellOrder.DealAmount*(1-sxf)-positions[i].buy_price*sellOrder.DealAmount*(1+sxf));
Log("定价卖出: 数量-"+sellOrder.DealAmount+",approximate_profit="+approximate_profit);
if (sellOrder.Status!=ORDER_STATE_CLOSED){
exc_obj.CancelOrder(sellID);
if (Math.min(account.Stocks,fixedAmount-sellOrder.DealAmount)>minest_sell[currency]){
var marketsellOrderID=processor.retrySell(exc_obj, -1, fixedAmount-sellOrder.DealAmount);
Sleep(order_wait_secs);
var marketsellOrderData=_C(exc_obj.GetOrder,marketsellOrderID);
approximate_profit+=(marketsellOrderData.AvgPrice*marketsellOrderData.DealAmount*(1-sxf)-positions[i].buy_price*marketsellOrderData.DealAmount*(1+sxf));
Log("市价卖出: 数量-"+marketsellOrderData.DealAmount+",approximate_profit="+approximate_profit);
}
}
var details={
type:"止损",
time:processor.get_ChinaTimeString(),
RealAmount:-1,
WantAmount:fixedAmount,
RealPrice:-1,
WantPrice:last,
Memo:(last>positions[i].buy_price?"盈利":"亏损")
};
if (trades_recorder){
trades.push(details);
}
}
}else{
positions_new.push(positions[i]);
}
}
positions=positions_new;
}
//清仓
if (positions.length>0){
if (last<=Lowest){
var positions_new=[];
for (var i=0; i < positions.length; i++){
account=_C(exc_obj.GetAccount);
var fixedAmount=Math.min(account.Stocks,positions[i].amount);
if (fixedAmount>minest_sell[currency]){
var sellID = processor.retrySell(exc_obj, last, fixedAmount);
Sleep(order_wait_secs);
var sellOrder=_C(exc_obj.GetOrder,sellID);
approximate_profit+=(sellOrder.AvgPrice*sellOrder.DealAmount*(1-sxf)-positions[i].buy_price*sellOrder.DealAmount*(1+sxf));
Log("定价卖出: 数量-"+sellOrder.DealAmount+",approximate_profit="+approximate_profit);
if (sellOrder.Status!=ORDER_STATE_CLOSED){
exc_obj.CancelOrder(sellID);
if (Math.min(account.Stocks,fixedAmount-sellOrder.DealAmount)>minest_sell[currency]){
var marketsellOrderID=processor.retrySell(exc_obj, -1, fixedAmount-sellOrder.DealAmount);
Sleep(order_wait_secs);
var marketsellOrderData=_C(exc_obj.GetOrder,marketsellOrderID);
approximate_profit+=(marketsellOrderData.AvgPrice*marketsellOrderData.DealAmount*(1-sxf)-positions[i].buy_price*marketsellOrderData.DealAmount*(1+sxf));
Log("市价卖出: 数量-"+marketsellOrderData.DealAmount+",approximate_profit="+approximate_profit);
}
}
var details={
type:"清仓",
time:processor.get_ChinaTimeString(),
RealAmount:-1,
WantAmount:fixedAmount,
RealPrice:-1,
WantPrice:last,
Memo:(last>positions[i].buy_price?"盈利":"亏损")
};
if (trades_recorder){
trades.push(details);
}
}
}
positions=positions_new;
}
}
//显示状态
var table1 = {type: 'table', title: '仓位-'+exname+'('+currency+')', cols: ['数量', '成交价','止损价'], rows: []};
var table2 = {type: 'table', title: '状态-'+exname+'('+currency+')', cols: ['平均真实波幅(N)','头寸单位','初始资产','当前资产','轮询时间','最新价','Highest','Lowest','加仓次数','【近似盈亏】'], rows: []};
var table3 = {type: 'table', title: '交易历史-'+exname+'('+currency+')', cols: ['日期','类型', '成交数量','发单数量','成交价','发单价','备注'], rows: []};
for (var i=0; i < positions.length; i++){
table1.rows.push([positions[i].amount,positions[i].buy_price,positions[i].stoploss_price]);
}
table2.rows.push([N,position_unit,init_asset,cur_asset,passedtime+'ms',last,highest,Lowest,add_already,approximate_profit]);
for (i=0; i < trades.length; i++){
table3.rows.push([trades[i].time,trades[i].type,trades[i].RealAmount,trades[i].WantAmount,trades[i].RealPrice,trades[i].WantPrice,trades[i].Memo]);
}
processor.logstatus=('`' + JSON.stringify([table1, table2, table3])+'`'+'\n');
//记录盈利
processor.logprofit=approximate_profit;
//rest
Sleep(wait_ms);
}
return processor;
}
};
//主函数
function main(){
var exchange_num=exchanges.length;
var processors=[];
for (var i=0; i<exchange_num; ++i){
var p=ExchangProcessor.createNew(exchanges[i]);
processors.push(p);
}
for (i=0; i<exchange_num; ++i){
processors[i].init_obj();
}
var pre_profit=Number(_G("pre_profit"));
Log('之前收入累计:'+pre_profit);
var lastprofit=0;
while(true){
var allstatus='策略仅作为学习使用,实盘风险自担。#0000ff'+'\n';
var allprofit=0;
for (i=0; i<exchange_num; ++i){
processors[i].work();
allstatus+=processors[i].logstatus;
allprofit+=processors[i].logprofit;
}
allstatus+=('微信:alinwo (验证消息: botvs)'+'\n');
LogStatus(allstatus);
if (lastprofit!==allprofit){
LogProfit(pre_profit+allprofit);
_G("pre_profit", pre_profit+allprofit);
lastprofit=allprofit;
}
}
}