Posted By: dusktrader
dt-e8 MA-crossover, built for Zorro v1.20 - 11/27/13 22:21
I'm working on rebuilding dt-e7 (aka "demo5"). I have made a number of changes to my build process and wanted to share. This tradebot is being built under Zorro 1.20 and I expect to trade it live once it's done. I'll share my build process along the way and you can feel free to add improvements or any input really.
The former iterations of this tradebot did not properly handle the Margin=0 situation, instead taking 1 Lot trades. I believe this affected the optimization too much, so I've started over from scratch on this build (note in Zorro v1.20, Margin=0 will no longer open a live trade).
I initially started with EURUSD, but was not very satisfied with the results. I then moved to NZDJPY and was happier with that basis, so I used that asset as the core to build out from. Therefore, in dt-e8 EURUSD is completely unused.
The infrastructure is constantly evolving and (hopefully) improving as well. Almost everything works now, several bugs squashed, and no more known issues as of Zorro 1.20.
I've identified the 7 pairs now that I'll use for this tradebot: USDJPY; EURAUD; EURCAD; AUDUSD; AUDCHF; AUDJPY; NZDJPY
Just for fun, I've calculated the "what if" scenario, if this tradebot were to go live today. But it's only optimized for 1 asset, not the other 6 (that's the work still left to do...) But I can use this scenario as a benchmark for comparison... to see how much better the tradebot will become when its fully optimized.
The former iterations of this tradebot did not properly handle the Margin=0 situation, instead taking 1 Lot trades. I believe this affected the optimization too much, so I've started over from scratch on this build (note in Zorro v1.20, Margin=0 will no longer open a live trade).
I initially started with EURUSD, but was not very satisfied with the results. I then moved to NZDJPY and was happier with that basis, so I used that asset as the core to build out from. Therefore, in dt-e8 EURUSD is completely unused.
The infrastructure is constantly evolving and (hopefully) improving as well. Almost everything works now, several bugs squashed, and no more known issues as of Zorro 1.20.
I've identified the 7 pairs now that I'll use for this tradebot: USDJPY; EURAUD; EURCAD; AUDUSD; AUDCHF; AUDJPY; NZDJPY
Just for fun, I've calculated the "what if" scenario, if this tradebot were to go live today. But it's only optimized for 1 asset, not the other 6 (that's the work still left to do...) But I can use this scenario as a benchmark for comparison... to see how much better the tradebot will become when its fully optimized.
Quote:
Walk-Forward Test: dt-e8 portfolio 2008..2013
Read dt-e8.fac dt-e8_1.par dt-e8_2.par dt-e8_3.par dt-e8_4.par
Profit 6565$ MI 148$ DD 764$ Capital 1069$
Trades 2768 Win 32% Avg +240.9p Bars 157
CAGR 73% PF 1.35 SR 1.65 UI 6.0% Error 17%
Read dt-e8.fac dt-e8_1.par dt-e8_2.par dt-e8_3.par dt-e8_4.par
Profit 6565$ MI 148$ DD 764$ Capital 1069$
Trades 2768 Win 32% Avg +240.9p Bars 157
CAGR 73% PF 1.35 SR 1.65 UI 6.0% Error 17%
Code:
function fridayClose(int fridayclose) { //allows Friday trading up until NYSE 3pm; close trades and don't allow after this if(fridayclose && dow() == FRIDAY && lhour(ET) >= 15) { exitLong("*"); exitShort("*"); return 1; //condition met; indicate no further trades } return 0; //condition not met; safe to take new trades } function hourOpen(int hourblockstart, int hourblockend) { //blocks new open trades between selected hours //uses NYSE time, including DST if ( (lhour(ET) >= hourblockstart) && (lhour(ET) < hourblockend) ) return 0; //between blocked hours, do not allow trade opens else return 1; //no conditions met, allow trades by default } function todayOpenCombo(var dayopencombo) { //allows optimizer to specify the best combo of days for opens //bit position 0 = Monday //bit position 1 = Tuesday //bit position 2 = Wednesday //bit position 3 = Thursday //bit position 4 = Friday //bit position 5 = Sunday //given a combination #, the function will return whether //current dow() is in the combination int dayopencombobits = dayopencombo+.5; //truncate to rounded int int today = dow() - 1; //Mon is 0 if (today == 6) today = 5; //bump Sun to 5 (no Sat, keep binary range 0-63) if (dayopencombobits & (1 << today)) return 1; //current dow() is in the combo else return 0; //current dow() not in combo, do not allow trade opens } function todayCloseCombo(var dayclosecombo) { //allows optimizer to specify the best combo of days to close by NYSE 4pm //bit position 0 = Monday //bit position 1 = Tuesday //bit position 2 = Wednesday //bit position 3 = Thursday //bit position 4 = Friday //bit position 5 = Sunday //given a combination #, the function will determine if we are beyond //a combo close time, close all trades if necessary, and return 1 //if no further trades allowed today int dayclosecombobits = dayclosecombo+.5; //truncate to rounded int int today = dow() - 1; //Mon is 0 if (today == 6) today = 5; //bump Sun to 5 (no Sat, keep binary range 0-63) if ((dayclosecombobits & (1 << today)) && lhour(ET) >= 16) { exitLong("*"); exitShort("*"); return 1; //current dow() is in the combo; indicate no further trades } else return 0; //current dow() not in combo, safe to take new trades } function marketOpenCombo(var marketopencombo) { //allows optimizer to specify best markets to initiate trades //bit position 0 = New York 8am-5pm Eastern //bit position 1 = Sydney 5pm-2am Eastern //bit position 2 = Tokyo 7pm-4am Eastern //bit position 3 = London 3am-12pm Eastern //given a combination #, the function will determine if current time is within //a market part of the combination (returns 1 to allow trading if true) int marketcombobits = marketopencombo+.5; //truncate to rounded int if ( (lhour(ET) >=8) && (lhour(ET) <17) && (marketcombobits & (1 << 0)) ) return 1; //inside New York if ( (lhour(ET) >=17) || (lhour(ET) <2) && (marketcombobits & (1 << 1)) ) return 1; //inside Sydney if ( (lhour(ET) >=19) || (lhour(ET) <4) && (marketcombobits & (1 << 2)) ) return 1; //inside Tokyo if ( (lhour(ET) >=3) && (lhour(ET) <12) && (marketcombobits & (1 << 3)) ) return 1; //inside London return 0; //default - current market not in combination, don't allow trade opens } function checkTradesPerCycle() { //require minimum 30 trades per WFO cycle or stop training static int LastWFOCycle = 0, LastNumTrades = 0; if(Train && (WFOCycle != LastWFOCycle) ) { if(LastNumTrades > 0 and LastNumTrades < 30) { char tradecount[100]; sprintf(tradecount,"Not enough trades per cycle: %d",LastNumTrades); quit(tradecount); } LastWFOCycle = WFOCycle; } LastNumTrades = NumWinTotal+NumLossTotal; } function calculateMargin(int direction) { //calculate risk Margin based on OptimalF and trade direction Capital = 1000; //simulated account balance var riskCapital = 300; //basis to trade with if (direction && OptimalF>.001) //long trade, historically profitable { Lots = 1; //allow live trading return OptimalFLong * riskCapital; } else if (!direction && OptimalF>.001) //short trade, historically profitable { Lots = 1; //allow live trading return OptimalFShort * riskCapital; } else Lots = -1; //non-historically profitable = phantom live trades only return 0; //no Margin allocated for non-historically profitable } function getOpt(string param) { switch (Asset) { case "EURUSD": switch (param) { case "timecycle": return optimize(75,65,85,1,0); case "timefactor": return optimize(2.4,0.2,5,0.2,0); case "stop": return optimize(7.5,2,15,0.5,-1); case "trail": return optimize(8,3,11,0.5); case "maxtrades": return 5; //limit trades per signal case "marketopencombo": return optimize(13,1,15,1); //15)every market case "dayopencombo": return 63; //optimize(61,1,63,1); //63)every day case "dayclosecombo": return 0; //optimize(33,1,63,1); //0)none; 63)every day case "fridayclose": return 1; //no trades after NYSE 3pm Friday case "reversedir": return 0; //trade opposite signals: 0)normal trade direction case "hedge": return 0; //Hedge mode: 0)none, 4)virtual case "hourblockstart": return 0; //block trade opens beginning at NY hour case "hourblockend": return 0; //block trade opens ending at NY hour case "lossstreaks": return 21; //0)no limit case "emode": return 0; //equity-curve trading: 0)none; 1)standard; 2)switch hitter; 3)weighted; 4)mean reversion } case "NZDJPY": switch (param) { case "timecycle": return optimize(70,60,80,1,0); case "timefactor": return optimize(2.4,0.2,5,0.2,0); case "stop": return optimize(4,1,7,0.5,-2); case "trail": return optimize(7,3,11,0.5); case "maxtrades": return 7; //limit trades per signal case "marketopencombo": return 15; //optimize(9,1,15,1); //15)every market case "dayopencombo": return 63; //optimize(13,1,63,1); //63)every day case "dayclosecombo": return 0; //optimize(34,1,63,1); //0)none; 63)every day case "fridayclose": return 0; //no trades after NYSE 3pm Friday case "reversedir": return 0; //trade opposite signals: 0)normal trade direction case "hedge": return 4; //Hedge mode: 0)none, 4)virtual case "hourblockstart": return 0; //block trade opens beginning at NY hour case "hourblockend": return 0; //block trade opens ending at NY hour case "lossstreaks": return 19; //0)no limit case "emode": return 1; //equity-curve trading: 0)none; 1)standard; 2)switch hitter; 3)weighted; 4)mean reversion } default: //use with Step 3a: prospecting switch (param) { case "timecycle": return optimize(70,60,80,1,0); case "timefactor": return optimize(2.4,0.2,5,0.2,0); case "stop": return optimize(4,1,7,0.5,-2); case "trail": return optimize(7,3,11,0.5); case "maxtrades": return 7; //limit trades per signal case "marketopencombo": return 15; //optimize(9,1,15,1); //15)every market case "dayopencombo": return 63; //optimize(13,1,63,1); //63)every day case "dayclosecombo": return 0; //optimize(34,1,63,1); //0)none; 63)every day case "fridayclose": return 0; //no trades after NYSE 3pm Friday case "reversedir": return 0; //trade opposite signals: 0)normal trade direction case "hedge": return 0; //Hedge mode: 0)none, 4)virtual case "hourblockstart": return 0; //block trade opens beginning at NY hour case "hourblockend": return 0; //block trade opens ending at NY hour case "lossstreaks": return 0; //0)no limit case "emode": return 0; //equity-curve trading: 0)none; 1)standard; 2)switch hitter; 3)weighted; 4)mean reversion } } } function checkEquity() { int emode = getOpt("emode"); if (!emode) return; //no equity-curve trading //emode 1 = standard: sets phantom/normal mode only (via Lots) //emode 2 = switch hitter: always in market (Lots=1), fades direction (via dir) //emode 3 = reward success with weighting: increase trades based on degree of improvement //emode 4 = mean reversion: trade when equity curve falls (Lots=1), sit out when it rises (Lots=-1) vars EquityCurve = series(EquityLong+EquityShort); //includes all phantom equity var dir; //indicates normal trade direction (dir=1) or reverse (dir=-1) //narrower curves //var slow = 50; //var fast = 10; //wider curves //var slow = 100; //var fast = 10; //mega-wide curves var slow = 200; var fast = 10; //uber-wide curves //var slow = 300; //var fast = 10; //optimized curves //var slow = optimize(50,50,300,12); //var fast = 10; vars EquityLP = series(LowPass(EquityCurve,fast)); var EquityLPfalling = LowPass(EquityLP,slow); var EquityLPrisingBigger = LowPass(EquityLP,slow*3.2); var EquityLPrisingBig = LowPass(EquityLP,slow*1.5); //plot("EquityLPslow",LowPass(EquityLP,slow),1,BLUE); //plot("EquityLPfast",LowPass(EquityLP,fast),0,GREEN); if(EquityLP[0] < EquityLPfalling && falling(EquityLP)) { //drawdown if (emode==1) Lots = -1; //set phantom trade mode if (emode==2) return 1; //fade: take signals when losing if (emode==3) { //reward success with weighting Lots = -1; //set phantom trade mode return 1; //allow max 1 phantom trade in drawdown } if (emode==4) Lots = 1; //mean-reversion: start trading when equity curve falls } else { //positive equity curve if (emode==1) Lots = 1; //set normal trade mode if (emode==2) return -1; //fade: take reverse signals when winning if (emode==3) { //reward success with weighting Lots = 1; //set normal trade mode if (EquityLP[0] > EquityLPrisingBigger && rising(EquityLP)) return 3; //very big rising else if (EquityLP[0] > EquityLPrisingBig && rising(EquityLP)) return 2; //big rising else return 1; //rising but not yet significantly } if (emode==4) Lots = -1; //mean-reversion: stop trading when equity curve rises } } function checkStreak() { //disallow trading this asset if loss streak threshold breached if (getOpt("lossstreaks") && ((LossStreakLong>=getOpt("lossstreaks")) || (LossStreakShort>=getOpt("lossstreaks")))) { Margin = 0; printf("\nWarning: %s breached loss streak limit of %i; may need reTrain",Asset,getOpt("lossstreaks")); } } function checkModifiers() { int reversedir = getOpt("reversedir"); //default 0=normal trade direction int fridayclose = getOpt("fridayclose"); //enforce auto-close and no trades after NYSE 3pm Friday int hourblockstart = getOpt("hourblockstart"); //block trade opens beginning at NY hour int hourblockend = getOpt("hourblockend"); //block trade opens ending at NY hour int dayopencombo = getOpt("dayopencombo"); //combo of days to open; 63=every day int dayclosecombo = getOpt("dayclosecombo"); //combo of days to close after NYSE 4pm; 0=none; 63=every day int marketopencombo = getOpt("marketopencombo"); //combo of markets to allow trade opens; 15=every market if ( (!fridayClose(fridayclose) //close NYSE 3pm on Friday || !todayCloseCombo(dayclosecombo) ) //close NYSE 4pm on selected days && todayOpenCombo(dayopencombo) //open on selected days only && marketOpenCombo(marketopencombo) //open during selected markets only && hourOpen(hourblockstart,hourblockend) ) //open during selected hours only return 1; //ok to place new trades else return 0; //no trade, restricted by a modifier } function run() { AssetList = "Assets-IBFXnano.dta"; //AssetList = "Assets-IBFXnano-spread100.dta"; //AssetList = "Assets-IBFXnano-spread300.dta"; //AssetList = "Assets-FXCMmicro.dta"; //AssetList = "Assets-FXCMmicro-spread100.dta"; set(PARAMETERS+ALLCYCLES+FACTORS); StartDate = 20080101; EndDate = 20131125; GapDays = 3; //alert if more than 3 days gap in data BarPeriod = 15; LookBack = 600; DataSplit = 70; //70% training, 30% OOS test NumWFOCycles = 5; if(is(TESTMODE)) NumSampleCycles = 15; //oversampling on Test only, not Train if (Train) { RollLong = 0; RollShort = 0; } //help prevent asymmetry in parameters & profit factors checkTradesPerCycle(); //stop Train early if not enough trades int reinvestprofits = 1; //invoke margin setting during trade logic while(asset(loop("USDJPY","EURAUD","EURCAD","AUDUSD","AUDCHF","AUDJPY","NZDJPY"))) //asset("NZDJPY"); { int maxtrades = getOpt("maxtrades"); checkEquity(); //equity-curve trading Hedge = getOpt("hedge"); //edge trading logic var TimeCycle = getOpt("timecycle"); var TimeFactor = getOpt("timefactor"); //Stop = BarPeriod*PIP; //simple stop level Stop = ATR(100) * getOpt("stop"); Trail = ATR(100) * getOpt("trail"); vars Price = series(price(0)); vars MA1 = series(SMA(Price,TimeCycle)); vars MA2 = series(SMA(Price,TimeCycle*TimeFactor)); if (checkModifiers()) //only evaluate signals if no restrictions in force { if (crossOver(MA1,MA2) && rising(MA1)) //long signal { if (reinvestprofits) Margin = calculateMargin(1); //long checkStreak(); //disallow trading if loss streak limit breached //enterLong(); //standard entry if (!getOpt("reversedir")) reverseLong(maxtrades); else reverseShort(maxtrades); } else if(crossUnder(MA1,MA2) && falling(MA2)) //short signal { if (reinvestprofits) Margin = calculateMargin(0); //short checkStreak(); //disallow trading if loss streak limit breached //enterShort(); //standard entry if (!getOpt("reversedir")) reverseShort(maxtrades); else reverseLong(maxtrades); } } } PlotWidth = 1100; PlotHeight1 = 600; }