{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}