Gamestudio Links
Zorro Links
Newest Posts
MT5 bridge not working on MT5 v. 5 build 4160
by EternallyCurious. 04/25/24 20:49
Data from CSV not parsed correctly
by EternallyCurious. 04/25/24 10:20
Trading Journey
by howardR. 04/24/24 20:04
M1 Oversampling
by Petra. 04/24/24 10:34
Zorro FIX plugin - Experimental
by flink. 04/21/24 07:12
Scripts not found
by juergen_wue. 04/20/24 18:51
zorro 64bit command line support
by 7th_zorro. 04/20/24 10:06
StartWeek not working as it should
by jcl. 04/20/24 08:38
AUM Magazine
Latest Screens
The Bible Game
A psychological thriller game
SHADOW (2014)
DEAD TASTE
Who's Online Now
4 registered members (EternallyCurious, AndrewAMD, TipmyPip, Quad), 889 guests, and 8 spiders.
Key: Admin, Global Mod, Mod
Newest Members
Mega_Rod, EternallyCurious, howardR, 11honza11, ccorrea
19048 Registered Users
Previous Thread
Next Thread
Print Thread
Rating: 5
Page 1 of 2 1 2
DT design process: steps 1, 2 and 3 #431959
10/27/13 03:52
10/27/13 03:52
Joined: Jul 2013
Posts: 522
D
dusktrader Offline OP
User
dusktrader  Offline OP
User
D

Joined: Jul 2013
Posts: 522
I will try to describe the beginning of the design process I'm working on in a series of 5 steps. This is a work-in-progress, but I have already spent many many hours refining it to this point. My hope is that other n00bs will find this useful, perhaps a launching point to great new ideas that can be shared.

My objective is to build a legion of tradebots that I, the human trader, will manage. Each tradebot will have its own broker account and be assigned a part of the trading stake. Like any good manager, my job as human will be to watch over my bots and make sure they are performing within their "acceptable spec". If they fall out of spec, I plan to have other contender bots waiting in line to step-in. The number of bots running at any given time is not defined, but is limited by the total size of my trading stake.

With this basic understanding of my objectives, I am interested in rapid development of new bots. To achieve this, I have created a basic set of standards and tools that each bot follows. I believe it's possible that forcing a bot to fit into such an infrastructure could be limiting in some ways, and that leaves a lot of room for efficiency improvements. However my goal will always be to apply such improvements to the infrastructure so that it can be reused in future bots. With each iteration of new bots I've been able to continually improve the toolbox a little more, and I expect this would continue with no limits. As the number of available tradebots exceeds the room in my legion (ie, number of funded broker accounts), I would simply decommission the lesser-performing bots and replace with the best-of-breed contenders.

-----------------------------------------------------------------
STEP 1: Identify "the edge"
When you set out to create a bot, it can be overwhelming. Especially if you are new to scripting or unfamiliar with how Zorro works. You probably have some idea of how your system would trade, but perhaps unsure the best way to translate those mechanical steps into a tradebot. In this first step of design, you will assemble those simple trading rules and show that it is (at least marginally) profitable in a backtest.

I started with a simple trend-following logic that is found in a snippet on the manual page for "optimize" here. The goal of Step 1 is to identify a marginally profitable edge which is nicely achieved by the example on this page. However, the example code does not explicitly define other personalities of the tradebot, such as how frequently it implements the edge. I was looking to build a hyper-tradebot on a small timeframe of about 15 minutes. So here are the details of Step 1a:

Step 1a: Goal: identify marginally-profitable edge
No WFO; No oversample; Simple Stop; Std entry
I keep track of these design steps in a spreadsheet, and I use the blue notation above to "remind myself" of important restrictions in each step of the process. It's extremely important that the process be both objective and consistent. I don't ever want to find myself in the position of having created something successful, yet unsure how exactly I arrived at that point.

At this step: No WFO means that this will be a straight backtest only. Oversampling (NumSampleCycles) is not set. Simple stop means that, essentially, I have not yet defined an appropriate stop logic. In its place I use only a generic dynamic stop (see below). Std entry means that I also use a very generic entry method with no special bells or whistles yet.

Code:
function run()
{
	set(PARAMETERS);
	StartDate = 20080101;
	EndDate = 20130531;
	BarPeriod = 15;
   
	//edge trading logic
	var TimeCycle = optimize(75,50,100,1,0);
	var TimeFactor = optimize(3,1,5,0.2,0);
	Stop = BarPeriod*PIP; //simple stop level
	LookBack = 100*5;

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

	if(crossOver(MA1,MA2))
		enterLong(); //standard entry
	else if(crossUnder(MA1,MA2))
		enterShort(); //standard entry
}


With only minor tweaking of the example code, the Train+Test can already yield AR 44% which qualifies this edge as having potential at this timeframe.

Step 1b: optimize the BarPeriod
I want the BarPeriod small (this is to be a hyper-bot, remember?) but I want Zorro to tell me which BarPeriod is really best. So I tell Zorro to scan all nearby small timeframes. I want to manually review the optimization chart for this step, so I need to hardcode the TimeCycle and TimeFactor variables, which I'll do temporarily by getting the found-best values out of the Data/script.par file. Training then with only 1 optimized parameter (the BarPeriod) will cause Zorro to produce the opt chart that I want. I can see clearly from this chart that BarPeriod 15 is indeed the best-of-breed at this small frequency.
Code:
function run()
{
	set(PARAMETERS);
	StartDate = 20080101;
	EndDate = 20130531;
	BarPeriod = optimize(15,5,20,1);
   
	//edge trading logic
	var TimeCycle = 61; //optimize(75,50,100,1,0);
	var TimeFactor = 2.8; //optimize(3,1,5,0.2,0);
	Stop = BarPeriod*PIP; //simple stop level
	LookBack = 100*5;

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

	if(crossOver(MA1,MA2))
		enterLong(); //standard entry
	else if(crossUnder(MA1,MA2))
		enterShort(); //standard entry
}





(Coming next...)

Step 1c: Goal: identify optimizable parameters
Step 1d
Step 1e
Step 1f: Goal: identify modifiers that work well
Step 1g


Re: DT design process: step 1: identify the edge [Re: dusktrader] #431964
10/27/13 10:49
10/27/13 10:49
Joined: Nov 2012
Posts: 126
B
blaub4r Offline
Member
blaub4r  Offline
Member
B

Joined: Nov 2012
Posts: 126
Nice job laugh , I am curious to see your next steps

Re: DT design process: step 1: identify the edge [Re: blaub4r] #432014
10/28/13 15:40
10/28/13 15:40
Joined: Jul 2013
Posts: 522
D
dusktrader Offline OP
User
dusktrader  Offline OP
User
D

Joined: Jul 2013
Posts: 522
Step 1c: Goal: identify optimizable parameters
No WFO; No oversample; Simple Stop; Std entry
In this step, I want focus on all the parameters that might play a role in the edge logic specifically. I need to try to keep a global view of my ultimate intent, which is to produce a multi-asset tradebot. The parameters that work for my current asset (I have started with EURUSD) may not be appropriate for other assets, so I want to give them the flexibility to move around for other asset personalities, and for market changes over time.

Therefore, ideally, I want to try to identify parameters that do affect core logic and give them flexibility to adjust (via Zorro's optimizer), but at the same time I want to constrain them based on a reasonable range that will be identified from the parameter optimization charts. Zorro will only produce an opt chart if I am optimizing 1 parameter, so I need to first hardcode the other parameters and then individually check each one to review its opt chart. In this example, the parameters that affect core logic are called TimeCycle and TimeFactor. The parameters should already be hardcoded from our last Step 1b, because we needed to do that in order to check that BarPeriod opt chart. So now I will hardcode BarPeriod back to 15 minutes, and select the first parameter to be optimized. (TIP: so that I don't forget what the original optimal parameter was, I will set this value as the "start" value in the optimizer call.)

Code:
function run()
{
	set(PARAMETERS);
	StartDate = 20080101;
	EndDate = 20130531;
	BarPeriod = 15;
   
	//edge trading logic
	var TimeCycle = optimize(61,50,100,1,0);
	var TimeFactor = 2.8; //optimize(2.8,1,5,0.2,0);
	Stop = BarPeriod*PIP; //simple stop level
	LookBack = 100*5;

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

	if(crossOver(MA1,MA2))
		enterLong(); //standard entry
	else if(crossUnder(MA1,MA2))
		enterShort(); //standard entry
}




And then I set the "reasonable range" of that parameter (in this case, TimeCycle was set at 55-75, with the optimal value of 63 in the "start" position). Now just repeat this process for the other parameter:

Code:
function run()
{
	set(PARAMETERS);
	StartDate = 20080101;
	EndDate = 20130531;
	BarPeriod = 15;
   
	//edge trading logic
	var TimeCycle = 63; //optimize(63,55,75,1,0);
	var TimeFactor = optimize(2.8,1,5,0.2,0);
	Stop = BarPeriod*PIP; //simple stop level
	LookBack = 100*5;

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

	if(crossOver(MA1,MA2))
		enterLong(); //standard entry
	else if(crossUnder(MA1,MA2))
		enterShort(); //standard entry
}


For this parameter, since the range is already very small, I decided not to constrain it any further. I actually expanded it to start with a value below 1 as well, to give it a little more room.


Finally, after identifying all optimizable core-logic parameters and constraining their ranges, I now re-Train and re-Test. In multi-parameter Trains, Zorro only looks at one parameter at a time, and relies on the "start" value for all other parameters. For this reason, the results could now appear better than before, since we've set the reasonable ranges.
Code:
function run()
{
	set(PARAMETERS);
	StartDate = 20080101;
	EndDate = 20130531;
	BarPeriod = 15;
   
	//edge trading logic
	var TimeCycle = optimize(63,55,75,1,0);
	var TimeFactor = optimize(2.6,0.2,5,0.2,0);
	Stop = BarPeriod*PIP; //simple stop level
	LookBack = 100*5;

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

	if(crossOver(MA1,MA2))
		enterLong(); //standard entry
	else if(crossUnder(MA1,MA2))
		enterShort(); //standard entry
}



Step 1d
No WFO; No oversample; Rev entry
In this step, I add the enhancements of reverseLong() and reverseShort() to replace the simple entries. These helper functions have special features that are desirable and explained in Workshop 5.

Also in this step, I perform the same optimization as in Step 1c, only focusing specifically on the Stop and Trail. To find a good-working ATR value for this tradebot, I tested the following varieties before settling on ATR(200): 300, 200, 150, 100, 50, 25, 10.

The LookBack is now also adjusted to 600, which is required for the ATR(200) that I used. Here is the resultant code after all these adjustments:
Code:
function run()
{
	set(PARAMETERS);
	StartDate = 20080101;
	EndDate = 20130531;
	BarPeriod = 15;
   
	//edge trading logic
	var TimeCycle = optimize(63,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);
	LookBack = 600;

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

	if(crossOver(MA1,MA2))
		//enterLong(); //standard entry
		reverseLong(1);
	else if(crossUnder(MA1,MA2))
		//enterShort(); //standard entry
		reverseShort(1);
}



Step 1e
Yes WFO; Yes oversample, Rev entry
In this step, I've added a variable to control maxtrades (a feature of the reverseLong() and reverseShort() helper functions). I can get an idea of how many trades the system might take from looking at the Performance Report on Step 1c. I will manually adjust the value up and down to determine how many trades are most appropriate.

Also in this step, I've added oversampling. This is described in the manual here. Though the manual indicates values higher than 6 should not produce better results, I have found that I prefer a value of 15. My understanding is that higher values are not any risk, they only increase the Test time.

It's important to note that (at least in my findings), oversampling does not work well during a Train. To accommodate this, I've added some code to make sure NumSampleCycles is never set during a Train, but always set during a Test.

Additionally, this step adds Rolling Walk-Forward-Optimization. I have learned that it is important to get "enough" trades per cycle, so I've added some code that warns me (and stops the Train) if it falls below a threshold of 30 trades per cycle.

Code:
function run()
{
	set(PARAMETERS);
	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;
   
	//edge trading logic
	var TimeCycle = optimize(63,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(crossOver(MA1,MA2))
		//enterLong(); //standard entry
		reverseLong(maxtrades);
	else if(crossUnder(MA1,MA2))
		//enterShort(); //standard entry
		reverseShort(maxtrades);
}



In this step, I am looking for only a "reasonable positive return", because future adjustments will have a major impact on profitability. As you can see, I'm incrementally changing the composition of the tradebot. With each change, I check the Test result to make sure nothing has gone haywire (and backtrack if it does).
Quote:
Walk-Forward Test: dt-demo1 EURUSD 2008..2013
Read dt-demo1_EURUSD_1.par dt-demo1_EURUSD_2.par dt-demo1_EURUSD_3.par dt-demo1_EURUSD_4.par
Profit 17$ MI 0$ DD 15$ Capital 16$
Trades 569 Win 28% Avg +2.9p Bars 51
AR 33% PF 1.16 SR 0.60 UI 19.6% Error 28%

Re: DT design process: step 1: identify the edge [Re: dusktrader] #432035
10/29/13 10:57
10/29/13 10:57
Joined: Jul 2013
Posts: 522
D
dusktrader Offline OP
User
dusktrader  Offline OP
User
D

Joined: Jul 2013
Posts: 522
Step 1f: Goal: identify modifiers that work well
Yes WFO; Yes oversample; Yes maxtrades; Yes all param optimizing; No emode
The introduction of what I call "modifiers" has been one of the most exciting parts of strategy development for me. These seemingly slight adjustments to trading rules have the capability to make or break a system. If I think about my discretionary trading, it was always these types of rules that were hardest to follow; they add a level of complexity that is hard for a human trader, but easy for Zorro. The other reason I like this section of strategy development is that the sky is the limit -- there are so many ideas to explore. I am still writing modifiers as I think them up and I think this is one area where human creative expression can be very useful in tradebot design.

I have tried to write modifiers in a way that they can be modular, so that I can easily repurpose them for each new tradebot. Potentially each tradebot prefers its own unique combination of modifiers, so they are meant to be easily switched on/off, and leveraging Zorro's optimizer feature (perhaps in some ways not intended).

The process I use in Step 1f is to first identify and record the results of each modifier individually, and then I try combinations of the most successful. The goal is to try to find the best combination of modifiers that works well together. As with all other parts of this tradebot design, it's important to utilize Zorro's optimizer where applicable (ie, where a parameter value other than "on" or "off" exists) because the intent here is to build a dynamic and global-minded tradebot, one that can swiftly follow rules for multiple assets. Any time I catch myself setting hardcoded parameter values, I have to ask myself "is this something I can let optimizer control in a more dynamic way?"

Also, it's very important to provide an "estimated best start value" for the optimizer in cases where you will be Train/Test-ing multiple optimized parameters. I will explain below the process I use to learn this value when starting from scratch: using an incorrect start value can make or break your strategy!!

I will walk through each modifier I currently have in the toolbox individually, and give comments. As a goal, I try to keep these extracted as much as possible from the core logic code. Also, I try to avoid making adjustments within the modifier code for an individual tradebot. Instead, it's preferable to write more dynamic functions that can be controlled from the run() function, or separate helper functions. In this way, my hope is that one day the toolbox of modifiers could be completely extracted into a separate header file that would just be included at the top of new strategies (my coding abilities are not quite at that level yet).

fridayClose() is a switch that provides a simple way to exit all positions prior to the weekend market close. In practice, I have not yet used this tool, because it has not yet shown me that it is statistically worthy in any of the bots I've designed so far. With that said, I am keeping it in the toolbox because it seems like it "should be" a good idea.
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
}



hourOpen() is another modifier tool I have never used, but that I keep in the toolbox nonetheless. It's getting rusty and probably does need some logic adjustment. Perhaps some day I will have a need for something like this and decide to polish it up.
Code:
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
}



todayOpenCombo() is a modifier that allows Zorro's optimizer to find the best combination of days for opening new trades. The days that (forex) trades can be opened are: Mon, Tues, Wed, Thurs, Fri, and Sun. I have found, usually to my surprise, that constraining a trade logic to open trades only on specific days can be dramatic. The combo modifiers in my toolbox all support multiple combinations of parameters, and are all compatible with Zorro's optimizer. This binary table is also helpful when trying to envision what a combo equates to (unless your brain naturally thinks in binary LOL).
Code:
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
}



todayCloseCombo() is todayOpenCombo's cousin, and allows Zorro to find the best day combination for closing trades (currently by NYSE 4pm).
Code:
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
}



marketOpenCombo() is one of my favorite modifiers, because I find myself saying "WOW!" a lot when I utilize this. This tool allows Zorro's optimizer to find the best markets that trades should be opened in: New York, Sydney, Tokyo and/or London.
Code:
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
}



I use a helper function which is like a control interface to the toolbox. In this way, I have again extracted most of this from the core trading logic, which supports my goal of rapid development. On some bots, I have found that I need multiple helper functions: for example, on a bot that trades with 2 core logics, such as the example in Workshop 6 with both TRND and CNTR logic.
Code:
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; //combo of days to open; 63=every day
	int dayclosecombo = 0; //combo of days to close after NYSE 4pm; 0=none; 63=every day
	int marketopencombo = 15; //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	
}



Before I can fully implement each modifier though, I need to provide Zorro with an "estimated best start value" so that it can be Train'd appropriately with my other optimizable parameters (ie, core logic and Stop/Trail). To do this, the process I use is as follows:

1. DISABLE WFO temporarily (comment out the NumWFOCycles and DataSplit lines)

2. Hardcode all currently optimizable parameters. I do it this way, for example:
Code:
var TimeCycle = 64; //optimize(64,55,75,1,0);


3. Set an optimize on the first modifier to check, such as this:
Code:
int marketopencombo = optimize(1,1,15,1);


I don't have any idea what the "estimated best start value" is yet, so I've used just 1. IMPORTANT: no other parameters should be set to optimize except the modifier at this stage.

4. Now tell Zorro to Train. It will recurse through each value of the modifier, as compared with the known-reasonable values that we hardcoded in all the other parameters. This produces a figure in the Zorro window after Train that can then become your "estimated best start value". I will now store this value in my script like this:
Code:
int marketopencombo = 15; //optimize(12,1,15,1);


Shown here: 12 was the best value in that Train, so I'm recording that as part of my optimize statement. It is commented-out because my next step is to check another modifier. The value 15 in this case is the default "all markets" value for this particular modifier.

5. Go back and repeat for each optimizable modifier, starting from step #3. This will allow you to realize the "estimated best start value" for each optimizable modifier.

6. Don't forget to re-ENABLE the WFO that we disabled in step #1. Also re-ENABLE all core parameter and Stop/Trail optimization statements before proceeding.

So putting all this together... the tradebot now looks like the below, after identifying all the "estimated best start values" for each modifier. If you recall, in Step 1f, I will go through each modifier individually and record how it affects the AR%. Then when I have a grasp of those modifiers that seem to work good, I try combining them to find the best mix. For example, to tell Zorro to recurse through and find the best mix of days to open trades, use an optimizer command like: int dayopencombo = optimize(54,1,63,1); and then refer to the chart on this page if you're interested to see which days were chosen.

Note that from this point forward, I keep the optimizer running in all the core logic and Stop areas.
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 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 = 15; //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);
	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;
   
	//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))
		{
			//enterLong(); //standard entry
			reverseLong(maxtrades);
		}
		else if(crossUnder(MA1,MA2) && falling(MA2))
		{
			//enterShort(); //standard entry
			reverseShort(maxtrades);
		}
	}
}



After checking each modifier individually, combined with all the core optimizable parameters, I'm able to determine which modifiers work best, and can also test combinations of high performers. I record all this info in my spreadsheet.

For the particular bot, I have recorded these AR% figures:
best markets combo: AR 45%
best day open combo: AR 3%
best day close combo: (loss)
friday close option: AR 31%

If you recall from Step 1e, the best AR% I had before applying any modifiers was 33%. Therefore, I'm happy with the improvements that the "best markets combo" modifier has added.

Coming next... Step 1g: equity-curve trading (emode)

Re: DT design process: step 1: identify the edge [Re: dusktrader] #432106
10/30/13 19:17
10/30/13 19:17
Joined: Jul 2013
Posts: 522
D
dusktrader Offline OP
User
dusktrader  Offline OP
User
D

Joined: Jul 2013
Posts: 522
Step 1g: Goal: determine if equity-curve trading would be helpful
Yes WFO; Yes oversample; Yes maxtrades; Yes all param optimizing; Yes best modifier(s); Yes emode

Similar to other steps in this process, I've extracted the equity-curve trading feature into a modular on-off switch. During this step, I test each of the "emodes" as a layer on top of what has already been built from Step 1f.

When I began researching equity-curve trading, I realized there are many ways to slice-and-dice. Perhaps there is no "right way" and obviously your mileage may vary... so feel free to experiment (and let me know if you come up with something better!)

I tried to optimize the thresholds of these curves, but that never worked very well in my experience. Instead, I came up with a set of wider and narrower curves. It didn't seem to make good logical sense to me that the curves themselves should be optimized, because this is just a filter layer on top of the already-optimizing trade logic.

Instead, as you can see below, I have various curve widths that I will generally test as part of this step and then settle on one. I think the curve width probably relates to the BarPeriod of the strategy, so it may not be the same between strategies. I'm certainly open to suggestions on how this can be improved and/or made more dynamic.

In the meantime, I have the following emode switches that can be easily turned on. This code goes just above the "edge trading logic" section:
Code:
//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


By simply un-commenting one of the 4 available emodes, you can quickly Train and Test how the bot would perform utilizing phantom trades based on the equity curve.

The actual emode function is below:
Code:
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
	}
}



So the process in this Step 1g is to Train and Test once with each emode, and see if there is a noticeable improvement using any of the modes. In the case of this bot, I have chosen emode 1 which improves the AR% to 54% now.

Coming next... Stage 2a: Simulate actual margin and/or reinvestment

Re: DT design process: step 1: identify the edge [Re: dusktrader] #432132
10/31/13 10:53
10/31/13 10:53
Joined: Jul 2013
Posts: 522
D
dusktrader Offline OP
User
dusktrader  Offline OP
User
D

Joined: Jul 2013
Posts: 522
Step 2a: Goal: simulate actual margin and/or reinvestment
Yes rolling WFO; Yes oversample; Yes maxtrades; Yes all param optimizing; Yes modifier(s); Yes emode; Yes reinvest

In this step, I will use Zorro's OptimalF feature to tell me mathematically how great this tradebot has become (or perhaps how much it sucks!). Either way, OptimalF will slap a numeric rating on the performance, and I can use that as a guide for determining how much of my trading stake I want to risk.

So before anything else is done, I need to let Zorro calculate the OptF for this strategy by adding the FACTORS flag like below. I'll be training initially withOUT any reinvest or margin settings:
Code:
set(PARAMETERS+FACTORS);


The purpose of this step is to see how Zorro would perform if it had access to a specific sum of risk capital in my account. Up until this point, we have designed the tradebot using only flat lots (Lots = 1). This gave us an objective view of how well the trading logic itself could perform. Also, it did not answer the question of "how much" should be risked on each trade or asset, which is specifically what OptimalF gives you (the optimal fraction to place at risk).

I should note that in this section, I use the terminology "REinvest" but in fact I am actually not currently applying any reinvestment compounding. That is a somewhat complicated proposition which has been discussed elsewhere in this forum. The code section below that uses the flag "reinvestprofits" has a switch to enable it, but the formula does not provide any compounding effect (but this is the section where such a formula could live). My decision not to apply a compounding effect (at least for now) is based on some discussions in the forum where we've basically determined that the purpose of compounding is for long-running tradebots. The rate of compounding would be somewhat negligible on a small account. The growth of the account should come as a result of the tradebot performance, not as a result of compounding.

The following code section lives just above the "edge trading logic" section:
Code:
//reinvest a portion of profits
	int reinvestprofits = 0; //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 = 900; //basis to trade with
		if (OptimalF>.001) //profitable as compared to other assets
		{
			MarginLong = OptimalFLong * riskCapital;
			MarginShort = OptimalFShort * riskCapital;
		}
	}



And then just before our trade entry commands, I place this code:
Code:
if (reinvestprofits && MarginLong>0) Margin = MarginLong; else if(is(TRADEMODE)) Lots = -1;


Code:
if (reinvestprofits && MarginShort>0) Margin = MarginShort; else if(is(TRADEMODE)) Lots = -1;



A couple comments before proceeding further:
Capital above is used in the Zorro simulation only. It allows me to see the "what if" of how my real account would perform, had I turned Zorro loose on it. In a live trading account, your live balance would be the equivalent. See picture for further explanation on this.

riskCapital is the amount of funds that Zorro has to work with. This figure needs to be adjusted to be mindful of Zorro's "Capital required" figure, which is given in the Performance Report. Therefore, you can provide a "cushion" for drawdown by setting the riskCapital less than the Capital. However, for now just play around with different figures so you can see how changes affect the end result. A final adjustment for live trading is made in the next Step (Step 3).

You'll see in my code that I always refer to OptimalF using a comparison such as >.001. The reason I do this is because of precision truncation behind the scenes. For example, even though Zorro's Performance Report may show a component with a value of .000 it could actually be .0000046 and I want to ensure that is treated as a zero.

In the code before trade entry commands... the reason for the "else if(is(TRADEMODE))" section is that-- I found that in live trading, if OptimalF was .000, then Zorro would use the default Margin setting of 0, which results in a 1 Lot actual trade. So for live trading purposes, I added this to force those trades to be phantom only.

So now that we've trained with the FACTORS flag, we can now set reinvestprofits = 1; and then press Test. This will produce a simulation of how the tradebot performs in live trading, taking into account the balance, and utilizing OptimalF to weight trades (short or long) that historically perform best. Can you see where this is going? Imagine using this power on an entire basket of trading instruments? Each instrument, each trade direction, each trade algorithm -- has an optimal fraction calculation. It's way too much math for a human discretionary trader to keep up with, but really a snap for Zorro...

Look at the before & after differences... in the "before" picture, we are seeing the raw trade logic, without any enhancements that OptimalF brings to that table (namely, the weightings... the better an asset+direction+algo performs, the more equity it should be allowed to trade with). By treating all trades the same, you get an overall much sloppier performance.

BEFORE:


In the "after" picture, we can see significant improvements in the way this bot trades. The equity curve is smoother, even when flat. Spikes and other sloppy trades are reduced. Overall drawdown severity is reduced. And known good performance is rewarded by OptimalF.

AFTER:


Coming next... Step 3: Goal: identify best assets

Re: DT design process: step 1: identify the edge [Re: dusktrader] #432136
10/31/13 16:49
10/31/13 16:49
Joined: Jul 2013
Posts: 522
D
dusktrader Offline OP
User
dusktrader  Offline OP
User
D

Joined: Jul 2013
Posts: 522
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;
}


Re: DT design process: step 1: identify the edge [Re: dusktrader] #432151
11/01/13 10:58
11/01/13 10:58
Joined: Jul 2013
Posts: 522
D
dusktrader Offline OP
User
dusktrader  Offline OP
User
D

Joined: Jul 2013
Posts: 522
And that's the end of my design process. I mentioned above that it was 5 steps but in fact it has been whittled down to only those 3 shown above in this thread. I am constantly refining this process (and even refined it again as I wrote this thread). I had forgotten to renumber some of the steps which is why I thought there were 5.

Anyhow... I hope by now I've convinced you that by using a rigid, objective design process -- that you really can take "slop" and turn it into something useful. The reason why, I think, is because the success comes more from "perfect execution" and less from "edge logic". No human could execute perfectly over time, I think. But with a fair amount of discipline, I do think a computer could execute perfectly.

I have many challenges ahead, even with this design process. I'm not done with it, and may never be. Now that one tradebot is up and running... I have new goals. One of the next things I want to add to the process is an objective review/evaluation of performance. I want to create a mechanical process for observing the live OOS performance, and compare it to the statistical performance I know from the Tests. I want to know, as quickly as possible, when the bot has failed to stay on track -- when it has fallen out of spec. One figure I'll certainly be watching is the MAE%

Also, there are limitations in the design process that I think I can improve upon. You are welcome to help as well. I'd love to hear your feedback or criticism.

One area I noticed while typing this thread that needs improvement is: if you noticed, the bot in this example was designed originally from EURUSD, and then expanded to other assets that seemed to work. I think I can make the optimization process a little more dynamic, where other assets are involved earlier in the process. I think this could be important because as it stands now, I'm basically retro-fitting a logic that was personalized for one asset onto a different one, which may have a different personality altogether. It's only by chance that some other assets are similar enough. But if I involve those assets and collect Train'ing data on them earlier, it seems possible that I could build a way for Zorro to switch between different parameter ranges, for example.

Anyway... lots more to build and tinker around with.

I'm attaching here my spreadsheet for keeping track of the results. I've found that with each new bot I build, its best to start another tab in the spreadsheet program (I use Google Docs btw). The reason is because with each new bot, I make refinements/improvements to the process.

Attached Files
Re: DT design process: step 1: identify the edge [Re: dusktrader] #432156
11/01/13 12:38
11/01/13 12:38
Joined: Jun 2013
Posts: 41
Ohio, USA
P
Pork Offline
Newbie
Pork  Offline
Newbie
P

Joined: Jun 2013
Posts: 41
Ohio, USA
DuskTrader,
Thank You for your efforts.
This is just what I need to move my trading education forward.
To be honest, I had just started a similar step process of my own.
That will be abandoned and replaced with this.
This needs to be required reading for all users. AFTER doing the workshops. For me this really brings the entire process together.
If you're ever in NW Ohio. Please let me know.
If you're eating/drinking, I'm buying.
P

Re: DT design process: step 1: identify the edge [Re: Pork] #432211
11/02/13 12:44
11/02/13 12:44
Joined: Jul 2013
Posts: 75
R
royal Offline
Junior Member
royal  Offline
Junior Member
R

Joined: Jul 2013
Posts: 75
Thank you very much DuskTrader! This really helps to work more efficient on developing new ideas. Hope i can find some time to implement some ideas this way.
Great job, thanks! laugh

Page 1 of 2 1 2

Moderated by  Petra 

Gamestudio download | chip programmers | Zorro platform | shop | Data Protection Policy

oP group Germany GmbH | Birkenstr. 25-27 | 63549 Ronneburg / Germany | info (at) opgroup.de

Powered by UBB.threads™ PHP Forum Software 7.7.1