{Function _SystemQuality
by Alex Matulich, 1 Feb 2004
Copyright (c) 2004 by Unicorn Research Corporation.
All rights reserved.
This function allows you to optimize on an objective measure of
quality of strategy performance. The results can be used to
compare objectively the quality of different strategies, or the
same strategy with different input parameters.
TradeStation does not offer a way to optimize a strategy against
some arbitrary user-defined result. It lets you optimize only
against a selection of canned results such as Net Profit or
win/loss ratio. These, unfortunately, are not objective measures
of system quality. This function builds an Excel file while an
optimization is running. The last column contains the quality
score of the strategy.
In the book _Trade Your Way To Financial Freedom_, Van K. Tharp
describes the use of an objective quality score, defined as
quality = expectancy * opportunities
where
expectancy = (AW*PW + AL*PL) / |AL|
= expected profit per dollar risked
opportunities = NST * (365 / studydays)
= opportunities to trade in a year
AW = average winning trade (excluding maximum win)
PW = probability of winning (total wins / opportunities)
AL = average losing trade (negative, excluding scratch losses)
PW = probability of losing (nonscratch losses/opportunities)
NST = number of non-scratch trades (a scratch trade loses
commission+slippage or less)
studydays = calendar days of history being tested
Examples of equivalent-quality systems would be:
* a market maker earning 1 cent per $1 risked, 50 times/day.
* a daytrader earning 10 cents per $1 risked, 5 times/day.
* a position trader earning $1 per $1 risked, 2-3 times/week.
(In reality the market maker earns something like 5 cents per $1
risked, 100 times a day. A market maker's quality score is
sky-high, higher than anything else, which is why they rarely
ever have a bad day.)
To use this function:
0. If your strategy contains position-sizing rules, disable them.
We're not trying for maximum equity growth in this optimization,
but rather risk-normalized performance on constant-quantity
orders. Position sizing rules destroy the measurement. After
finding the optimum quality this way, re-enabling the position
sizing rules will result in better performance than before,
especially if the sizing is a function of trade risk and equity.
1. Just insert a call to this function at the end of your signal,
passing the strategy input parameters of interest, as well as the
file name to output. The file name should have a .CSV extension
so that Excel will read it readily. The file name should not
contain anything TS would interpret as "jump commands" or you'll
get an error. See the warning in the input declaration below.
2. If you are using intraday data, you must be certain that the
last bar of the session is not a fractional bar, but a full bar.
For example, a session starting at 08:00 and ending at 15:15
will not work with 30 minute bars because the last bar isn't
a full 30 minutes long. You would need to adjust your session
times to ensure that the last bar is a full bar.
Here is an example of a simple trading system using this function.
Note value passed for the 2nd parameter (called 'dontsave') is
-999999 because this strategy has a negative quality score, so no
data would be saved if you use the recommeded dontsave value of 0.
--------------------------------------------------------------
Input: LenL(3), LenS(3), filename("");
Vars: dummy(0);
Value1 = Average(H,LenL);
Value2 = Average(L,LenS);
If Close < Value2[1] And Close[1] >= Value2[2] then
buy next bar Value1+1 point stop;
If Close > Value1[1] And Close[1] <= Value1[2] then
sell next bar Value2-1 point stop;
dummy = _SystemQuality(filename, -999999,
LenL, LenS, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
--------------------------------------------------------------
2. If you use the same strategy in different markets or different
workspaces, be sure to add a filename input parameter to your
strategy's signal, so you don't have multiple things writing to
the same file!
3. Make sure the output file is deleted. Every time your strategy
is recalculated the output file grows. You want to delete it to
start afresh before running an optimization. Also, setting the
filename parameter to "" (empty string) will disabled the output.
4. Run the optimization. Try to set up your parameters so that
less than 65,535 results are generated (you will have to import
it all into Excel).
5. When the optimization completes, load your file into Excel and
sort by the last column, the one you want optimized, in descending
order. The top result will be the optimum. The columns in the
file are:
p1, p2,...(input parameters),
#wins, GrossProfit, #losses, GrossLoss, #scratches, ScratchLoss,
LargestWinTrade, LargestLosTrade, MaxIDDrawdown,
Expectancy, QualityScore
Be aware that the optimum strategy found this way is objectively
and mathematically optimum. It may not be PSYCHOLOGICALLY
optimum, however. For example, the optimum combination of
parameters giving the highest score may have 25% winning trades.
You may be bothered by losing 3 out of 4 times. If such is the
case for you, you can compromise by multiplying the score by your
personal subjective measure of "psychological quality." In this
case we would multply the score by the percent of winning trades,
and re-sort the spreadsheet. If you're sensitive to drawdown, you
might try multiplying the score by AL/DD. You might find that the
resulting highest score represents a good compromise between your
psychology and the objective best strategy.
See _Trade Your Way To Financial Freedom_ by Van K. Tharp for
a detailed explanation of trading system expectancy. This function
calculates a more conservative version that Tharp's: It ignores
the maximum profit as an outlier, and it uses the average loss
rather than the minimum loss as the standard of risk.
}
inputs:
filename(string), {full pathname for .csv file}
{WARNING: avoid \hb, \he, \pb, \pe, \wb, or \we, in the file
name. TS interprets these as "jump commands." For example,
the filenames "c:\hello.csv" or "c:\data\performance.csv" will
result in an error message because they contain \he and \pe,
respectively.}
dontsave(NumericSimple), {don't save scores less than this}
{recommend using dontsave=0 because negative scores don't matter}
p1(NumericSimple), {strategy input parameters}
p2(NumericSimple),
p3(NumericSimple),
p4(NumericSimple), p5(NumericSimple), p6(NumericSimple),
p7(NumericSimple), p8(NumericSimple), p9(NumericSimple),
p10(NumericSimple), p11(NumericSimple), p12(NumericSimple),
p13(NumericSimple), p14(NumericSimple), p15(NumericSimple),
p16(NumericSimple), p17(NumericSimple), p18(NumericSimple);
vars: lb(false), nclosed(0), wins(0), losses(0), scratches(0), scratchloss(0),
scratchtrade(0), studystart(0), studydays(0),
p(0), j(0), k(0), AW(0), PW(0), AL(0), PL(0), expectancy(0),
expectancyscore(0);
{Initializations to occur on first bar}
if currentbar <= 1 then begin
nclosed = 0;
wins = 0;
losses = 0;
scratches = 0;
scratchloss = 0;
studystart = DateToJulian(Date);
{The maximum loss amount for a "scratch" trade will be a
round-turn commission and slippage.
Set Commission and Slippage in Format -> Strategy -> Costs.}
scratchtrade = -1.5 * (commission + slippage);
end;
{Do the following whenever a position is closed}
if totaltrades > nclosed then begin
k = totaltrades - nclosed;
for j = 1 to k begin {loop over multiple simultaneous closed trades}
p = PositionProfit(j);
if p <= 0 then begin
if p >= scratchtrade then begin
scratches = scratches + 1;
scratchloss = scratchloss + p;
end else
losses = losses + 1;
end else
wins = wins + 1;
end;
nclosed = totaltrades;
end;
{Finally, at the last bar, output the result to be optimized}
if datacompression < 2 then begin
{LastBarOnChart doesn't seem to work reliably on intraday data, so
we'll make up our own that detects the next-to-last bar on the chart.}
if LastCalcMMTime >= BarInterval then begin
value1 = LastCalcMMTime - BarInterval;
value2 = LastCalcJdate;
end else begin
value1 = 2400 - BarInterval;
value2 = LastCalcJdate - 1;
value3 = DayOfWeek(JulianToDate(value2));
while value3 = 0 or value3 = 6 begin
value2 = value2 - 1;
value3 = DayOfWeek(JulianToDate(value2));
end;
end;
lb = (date = JulianToDate(value2) and time = MinutesToTime(value1));
end else
lb = LastBarOnChart;
if lb and losses>0 and StrLen(filename)>0 then begin
studydays = DateToJulian(Date) - studystart;
if studydays < 1 then studydays = 1;
{Calculate results to be optimized, and output them}
if wins > 1 and losses > 0 then begin
j = wins - 1 + losses;{total trades excl scratches & max win}
AW = (GrossProfit-LargestWinTrade)/(wins-1); {avg win}
PW = (wins-1) / j; {% wins}
AL = (GrossLoss - scratchloss) / losses; {avg loss}
PL = losses / j; {% losses}
end else begin {handle division by zero cases}
j = wins + losses;
if wins <= 1 then AW = 0 else AW = GrossProfit / (wins-1);
if j = 0 then begin
PW = 0; PL = 0;
end else begin
PW = wins / j; PL = losses / j;
end;
if losses = 0 then AL = 0
else AL = (GrossLoss - scratchloss) / losses;
end;
if AL = 0 then expectancy = AW*PW
else expectancy = (AW*PW + AL*PL) / (-AL);
expectancyscore = expectancy * j * 365/studydays;
if expectancyscore >= dontsave then begin
{Append the strategy input parameters to a file}
FileAppend(filename,
numtostr(p1,3)+","+numtostr(p2,3)+","+numtostr(p3,3)+","+
numtostr(p4,3)+","+numtostr(p5,3)+","+numtostr(p6,3)+","+
numtostr(p7,3)+","+numtostr(p8,3)+","+numtostr(p9,3)+","+
numtostr(p10,3)+","+numtostr(p11,3)+","+numtostr(p12,3)+","+
numtostr(p13,3)+","+numtostr(p14,3)+","+numtostr(p15,3)+","+
numtostr(p16,3)+","+numtostr(p17,3)+","+numtostr(p18,3)+",");
{Append win and loss data as desired}
FileAppend(filename,
numtostr(wins,0)+","+numtostr(GrossProfit,2)+","+
numtostr(losses,0)+","+numtostr(GrossLoss,2)+","+
numtostr(scratches,0)+","+numtostr(scratchloss,2)+","+
numtostr(LargestWinTrade,2)+","+numtostr(LargestLosTrade,2)+","+
numtostr(MaxIDDrawDown,2)+",");
{Append expectancy and expectancy score}
FileAppend(filename,
numtostr(expectancy, 3) + "," +
numtostr(expectancyscore, 3));
FileAppend(filename, NewLine); {lastly append a carriage return}
end;
end;
_SystemQuality = 1; {dummy return value}