Step 3a: Goal: identify best assets
Yes WFO; Yes oversample; Yes maxtrades; Yes all param optimizing; Yes best modifier(s); Yes emode; No reinvest

In this step, I will take the dynamic logic that exists thus far and set it up to for multi-asset trading. Since this will require further Train'ing, the reinvestprofits switch needs to be turned off for now. In my opinion, a good rule of thumb is that: whenever Train'ing, you should only do it with flat lots (no Margin). When you Train, you are letting Zorro optimize logic parameters, and you don't want Margin to obscure those settings or results.

Converting to a multi-asset tradebot is relatively simple from the code aspect. All you need to do is wrap the edge trading logic with a loop like this:
Code:
while(asset(loop("NZDJPY","EURUSD","AUDUSD")))


However, we need to determine a few things before we can get to that step. Namely, we need Train'ing data for all the assets (to see whether or not they could even be profitable), and then we would need to choose a select handful. Of those that are chosen, we also need to calculate OptimalF factors across the entire basket of assets (because some assets will deserve more weight than others).

Depending how many assets you want to consider for building your multi-asset tradebot, this step could take a long time. However, the effort is worth it -- I promise!! Keep in mind that due to memory limitations of 32bit applications like Zorro, (I've found that) you will only be able to effectively train about 7 assets at once, and possibly less depending on the complexity of your edge trading logic. You can try more, but if you hit the memory limit Zorro just stops. (Sidenote: technically you could probably divide the assets into groups and then train them individually and attempt to sew those results back together... but I haven't figured out how to take OptimalF into consideration if you do that, because you really need an OptimalF global basket view.)

There are 27 tradeable forex assets in my IBFX account, and those assets are also available in FXCM. So those are the ones I've selected to Train and Test. Essentially the process here is as follows:

1. Ensure reinvestprofits is set to 0;
2. Do NOT setup a multi-asset loop yet. Instead, Train the first asset, then press Test and record AR% (and possibly other metrics that are important to you);
3. Repeat #2 with the next individual asset until you have collected Test data for every asset;

I organize the test data into 2 groups like this:

NOT LOOKING GOOD: GBPUSD; USDCAD; AUDCAD; AUDNZD ...

USE FOR BASKET: NZDJPY 56%; EURUSD 54%; AUDUSD 47%; AUDCHF 43%; NZDCHF 34%; AUDJPY 21%; USDCHF 14%; USDJPY 14%; GBPJPY 10%; EURJPY 6%; NZDUSD 5%

Note I have organized them from highest AR% to lowest. This is the order of preference I will use when I select my cream-of-crop assets.

Now we need to combine those top assets into the trading loop and do a re-Train once more, still without Margin. The purpose of this Train is to calculate OptimalF factors:

The top assets I've chosen are: NZDJPY, EURUSD, AUDUSD, AUDCHF, AUDJPY, USDCHF, and USDJPY. Another asset NZDCHF performed well, but I've excluded it due to less data available. In a multi-asset loop like we are building here, my understanding is that Zorro uses the first asset to setup the series for all that will follow. So introducing a non-similar data history like NZDCHF could cause an issue (for example, if Zorro was unable to achieve the required LookBack at the same StartDate as other assets).

Now here is where I am scratching my head a little... you can see in the above list "USE FOR BASKET" that the flat-lot AR% ranges peaked around 56%. So how can simply introducing more assets cause that figure to be higher? I have some theories, but I'm not sure. When I test the basket of top assets with flat-lot trading, I'm now getting the following Test result:
Quote:
Walk-Forward Test: dt-demo4 portfolio 2008..2013
Read dt-demo4.fac dt-demo4_1.par dt-demo4_2.par dt-demo4_3.par dt-demo4_4.par
Profit 83$ MI 2$ DD 21$ Capital 33$
Trades 1855 Win 30% Avg +4.4p Bars 146
AR 94% PF 1.17 SR 0.90 UI 8.6% Error 21%


Step 3b
Yes WFO; Yes oversample; Yes maxtrades; Yes all param optimizing; Yes best modifier(s); Yes emode; Yes reinvest; Yes multi-asset loop

But to really see what this tradebot is capable of, go ahead and switch on the reinvestprofits flag now and press Test again.

Now here is where a bit of tweaking is required. Look at the "Capital" figure in the Zorro window after the test (Capital required). We need that figure to be no higher than the "Capital" simulation figure we've used in the reinvestprofits section. You can vary this figure by adjusting the riskCapital variable, the amount of funds that we're allowing Zorro to base its trade sizing on. If your Capital required exceeds the simulated Capital figure, then it means your account could blow up, depending on the time you start the tradebot. (It could still blow up even if Capital required is less that the simulated Capital, but it would be less statistically likely.)

After settling on a riskCapital value of $600, Capital required is now $1005, which is just about right:
Quote:
Walk-Forward Test: dt-demo4 portfolio 2008..2013
Read dt-demo4.fac dt-demo4_1.par dt-demo4_2.par dt-demo4_3.par dt-demo4_4.par
Profit 4101$ MI 101$ DD 831$ Capital 1005$
Trades 1879 Win 30% Avg +217.8p Bars 147
CAGR 62% PF 1.29 SR 1.19 UI 6.5% Error 20%

Note how the quality metrics have all improved now that we've implemented the OptimalF calculations, vs. the flat-lot section above: PF, SR, UI and Error%.



Look how adding multiple assets has smoothed the overall equity curve. Even the flat period noted on EURUSD from about mid-2011 to 2013 is now much improved. Perhaps the US Dollar fell out of favor during that time, while exotics took the stage. That's some nice diversification!

Here is the complete code we've assembled so far:
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 checkEquity(var emode)
{
	//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 checkModifiers()
{
	int reversedir = 0; //default normal trade direction (0) unless specified otherwise
	int fridayclose = 0; //enforce auto-close and no trades after NYSE 3pm Friday
	int hourblockstart = 0; //block trade opens beginning at NY hour
	int hourblockend = 0; //block trade opens ending at NY hour
	int dayopencombo = 63; //optimize(54,1,63,1); //combo of days to open; 63=every day
	int dayclosecombo = 0; //optimize(33,1,63,1); //combo of days to close after NYSE 4pm; 0=none; 63=every day
	int marketopencombo = optimize(12,1,15,1); //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()
{
	set(PARAMETERS+FACTORS);
	StartDate = 20080101;
	EndDate = 20130531;
	BarPeriod = 15;
	LookBack = 600;
	if(is(TESTMODE)) NumSampleCycles = 15; //oversampling on Test only, not Train
	if (Train) { RollLong = 0; RollShort = 0; } //help prevent asymmetry in parameters & profit factors
	DataSplit = 70; //70% training, 30% OOS test
	NumWFOCycles = 5;
	int maxtrades = 1;

	//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;
   
	//equity-curve trading
	checkEquity(1); //emode 1: normal/phantom trading
	//reversedir = checkEquity(2); //emode 2: switch hitter
	//maxtrades = checkEquity(3); //emode 3: reward success
	//checkEquity(4); //emode 4: mean-reversion mode
   
   while(asset(loop("NZDJPY","EURUSD","AUDUSD","AUDCHF","AUDJPY","USDCHF","USDJPY")))
	{
		//reinvest a portion of profits
		int reinvestprofits = 1; //invoke margin setting during trade logic
		Margin = 0; //default
		var MarginLong = 0; //default
		var MarginShort = 0; //default
		if (reinvestprofits)
		{
			Capital = 1000; //simulated account balance
			var riskCapital = 600; //basis to trade with
			if (OptimalF>.001) //profitable as compared to other assets
			{
				MarginLong = OptimalFLong * riskCapital;
				MarginShort = OptimalFShort * riskCapital;
			}
		}
   
		//edge trading logic
		var TimeCycle = optimize(64,55,75,1,0);
		var TimeFactor = optimize(2.6,0.2,5,0.2,0);
		//Stop = BarPeriod*PIP; //simple stop level
		Stop = ATR(200) * optimize(1.99,1,15,0.5,-3); // allow 3% tolerance for preferring low stop distances
		Trail = ATR(200) * optimize(8.5,3,13,0.5);

		vars Price = series(price(0));
		vars MA1 = series(SMA(Price,TimeCycle));
		vars MA2 = series(SMA(Price,TimeCycle*TimeFactor));

		if (checkModifiers())
		{
			//OK to trade, let's evaluate signals then
			if (crossOver(MA1,MA2) && rising(MA1))
			{
				if (reinvestprofits && MarginLong>0) Margin = MarginLong; else if(is(TRADEMODE)) Lots = -1;
				//enterLong(); //standard entry
				reverseLong(maxtrades);
			}
			else if(crossUnder(MA1,MA2) && falling(MA2))
			{
				if (reinvestprofits && MarginShort>0) Margin = MarginShort; else if(is(TRADEMODE)) Lots = -1;
				//enterShort(); //standard entry
				reverseShort(maxtrades);
			}
		}
	}

	PlotWidth = 1100;
	PlotHeight1 = 800;
}