I've figured out how to back test a four legged options trading strategy,
specifically selling iron butterflies or iron condors.  The AmiBroker AFL
code is below.

I welcome any questions, comments, suggestions, fixes, or improvements.

Warm regards,
David


// This option trading strategy is not intended to make money.
// It is intended to demonstrate the use of AmiBroker (AB) for back testing
option trading strategies.
// This code builds option contract symbols from the underlyings,
expirations, option types (puts or calls), and strike prices.

// The example option trading strategy contained herein is:
// Every day based on end of day data,
// sell to open 1 contract of an NDX front month iron butterfly or iron
condor at the Mid.
// On expiration, cash settle all remaining option positions.

// This code assumes Interactive Brokers (IB) symbology and data, and uses
Close instead of Mid because IB doesn't supply end of day Mid or Bid/Ask
data.
// To produce results, you need to load IB historical end of day data for
NDX-NASDAQ-IND and its put and call options,
// such as NDX   100619P01975000-SMART-OPT where the underlying root is
padded with spaces to 6 characters.

// The commented out Plot and _TRACE statements can be uncommented for
debugging purposes.

// To do longer term back testing, you would need to obtain lengthier
historical options data.  Some sources are listed below.
// Please verify this information yourself.  It was gathered from their
sales reps in May 2010.
/*
LiveVol:  trade and 1 min bid/ask, starting 1/04
TickData:  tick bid/ask and 1 sec close, starting 7/04
iQFeed:  trade bid/ask/last, starting 1 month ago; 1 min bid/ask/last,
starting 5/07
eSignal:  tick bid/ask starting up to 60 days ago, bar bid/ask starting up
to 120 days ago, day bid/ask starting up to 1 year ago
OptionVue:  30 min bid/ask, starting 1/2/01
Ivy DB:  day bid/ask, starting 1/96
iVolatility:  day bid/ask, starting 11/1/00
HistoricalOptionData:  day bid/ask/last, starting 2/02
Stricknet:  day bid/ask/last, starting 1/2/03
*/

SetFormulaName("Example of four legged option trading strategy - selling
iron butterflies or iron condors");

/* Setting StrikesFromAtmToShort = 0 sells iron butterflies.  Setting
StrikesFromAtmToShort > 0 sells iron condors.  */
StrikesFromAtmToShort = Param("StrikesFromATMToShort", 0, 0, 10, 1, sincr =
0);
StrikesFromShortToLong = Param("StrikesFromShortToLong", 1, 1, 10, 1, sincr
= 0);
DebugView = ParamToggle("DebugView", "OFF|ON", defaultval = 0); /* Controls
all debug output */

RoundLotSize = 100;
CommissionPerSharePerLeg = 0.01;
Underlying = "NDX-NASDAQ-IND";

SetBarsRequired( sbrAll, sbrAll ); /* Uses all bars, NOT any specified
subset.  Turns off QuickAFL to ensure proper behavior of BarIndex(). */
SetBacktestMode( backtestRegularRawMulti );
SetOption( "UseCustomBacktestProc", False );
//SetOption( "PortfolioReportMode", 0 );
//SetOption( "Generate", 1 );
SetOption( "InitialEquity", 1000000 );
SetOption( "AccountMargin", 100 );
SetOption( "UsePrevBarEquityForPosSizing", True );
SetOption( "MinPosValue", 0 );
SetOption( "MaxOpenPositions", 1000 );
SetOption( "MinShares", 100 );
SetOption( "PriceBoundChecking", False );
SetOption( "CommissionMode", 3 );
SetOption( "CommissionAmount", 0 );
SetOption( "InterestRate", 0);
SetOption( "AllowPositionShrinking", False );
SetOption( "DisableRuinStop", True );
SetOption( "ActivateStopsImmediately", False );
SetOption( "AllowSameBarExit", False );

SetOption( "NoDefaultColumns", False );
SetOption( "FuturesMode", False );
SetOption( "WorstRankHeld", 10 );
SetOption( "ReverseSignalForcesExit", False );
SetOption( "EveryBarNullCheck", False);
SetOption( "HoldMinBars", 0 );
SetOption( "EarlyExitBars", 0 );
SetOption( "EarlyExitFee", 0 );
SetOption( "HoldMinDays", 0 );
SetOption( "EarlyExitDays", 0 );
SetOption( "SeparateLongShortRank", True );
SetOption( "MaxOpenLong", 0 );
SetOption( "MaxOpenShort", 0 );
SetOption( "RefreshWhenCompleted", True );
SetOption( "RequireDeclarations", False );
SetOption( "ExtraColumnsLocation", 0 );

function ConvertDateFromAmiBrokerToOPRA(DateNumber)
{
_ConvertDateFromAmiBrokerToOPRA = IIf(DateNumber > 1000000, DateNumber -
1000000, DateNumber);
return _ConvertDateFromAmiBrokerToOPRA;
}

function DaysInMonth(MonthNum, YearNum)
{
_DaysInMonth   = IIf(MonthNum == 1 OR MonthNum == 3 OR MonthNum == 5 OR
MonthNum == 7 OR MonthNum == 8 OR MonthNum == 10 OR MonthNum == 12, 31, 30);
DaysInMonthFeb = IIf((YearNum % 4 == 0 AND YearNum % 100 != 0) OR (YearNum %
4 == 0 AND YearNum % 400 == 0), 29, 28);
_DaysInMonth   = IIf(MonthNum == 2, DaysInMonthFeb, _DaysInMonth);
return _DaysInMonth;
}

function DaysToThirdFriday()
{
Dy = Day();
WeekDay = DayOfWeek();
DaysToFriday       = IIf(5 - WeekDay < 0, (12 - WeekDay) % 7, (5 - WeekDay)
% 7);
ThirdFriday        = ((Dy + DaysToFriday) % 7) + 14;
ThirdFriday        = IIf(ThirdFriday == 14, 21, ThirdFriday); /* Adjusts for
the first of the month being a Saturday. */
_DaysToThirdFriday = ThirdFriday - Dy;
_DaysToThirdFriday = IIf(_DaysToThirdFriday >= 0,
                         _DaysToThirdFriday,
                         ThirdFriday + IIf(ThirdFriday + 14 >
DaysInMonth(Month(), Year()), 28, 35) - Dy);
return _DaysToThirdFriday;
}
//Plot(DaysToThirdFriday() * 100, "DaysToThirdFriday()", colorBlack,
styleLine);
//Plot((ConvertDateFromAmiBrokerToOPRA(DateNum()) + DaysToThirdFriday()) /
100, "Next 3rd Friday", colorRed, styleLine);

function FirstMonthExpiration()
{
_FirstMonthExpiration = ConvertDateFromAmiBrokerToOPRA(DateNum()) +
DaysToThirdFriday() + 1; /* OPRA symbols use Saturday after third Friday as
monthly expiration. */
Yr       = floor( _FirstMonthExpiration / 10000 );
MonthDay = _FirstMonthExpiration - Yr * 10000;
Mon      = floor( MonthDay / 100 );
Dy       = MonthDay - Mon * 100;
MonthDay = IIf( MonthDay > Mon * 100 + DaysInMonth(Mon, Yr) AND MonthDay <
(Mon + 1) * 100, (Mon + 1) * 100 + Dy - DaysInMonth(Mon, Yr), MonthDay);
Yr       = IIf( MonthDay >= 1300, Yr + 1, Yr);
MonthDay = IIf( MonthDay >= 1300, MonthDay - 1200, MonthDay);
_FirstMonthExpiration = Yr * 10000 + MonthDay;
return _FirstMonthExpiration;
}
//Plot(FirstMonthExpiration() / 100, "FirstMonthExpiration()", colorBlack,
styleLine);

UnderlyingRoot = StrLeft(Underlying, StrFind(Underlying, "-") - 1);
while(StrLen(UnderlyingRoot) < 6) UnderlyingRoot = UnderlyingRoot + " ";

switch(UnderlyingRoot)
{
case "NDX   ": StrikeIncrement = 25; SlippagePerSharePerLeg = 0.025;
SettlementSymbol = "NDS-CBOE-IND"; break;
case "SPX   ": StrikeIncrement =  5; SlippagePerSharePerLeg = 0.050;
SettlementSymbol = "SET-CBOE-IND"; break;
default:       StrikeIncrement =  0; SlippagePerSharePerLeg = 0.000;
SettlementSymbol = "error";             break;
};

Expiration = FirstMonthExpiration();
//Plot(Expiration / 100, "Expiration ", colorOrange, styleLine);

SetForeign(Underlying, fixup = True, tradeprices = False);
UnderlyingPrice = Close;
// Could add code here to determine trend or other underlying-based
indicators.
RestorePriceArrays();
//Plot (UnderlyingPrice, Underlying + " UnderlyingPrice", colorRed,
styleLine);
//if( DebugView ) _TRACE("# UnderlyingPrice = " + NumToStr(UnderlyingPrice,
1.2, separator = True) + " (" + typeof(UnderlyingPrice) + ")" );

AtmStrike = round( UnderlyingPrice / StrikeIncrement ) * StrikeIncrement;
//Plot (AtmStrike ,"AtmStrike", colorBlack, styleLine);

UnderlyingToEvaluate  = StrMid(Name(),0,6);
ExpirationToEvaluate  = IIf(UnderlyingToEvaluate == UnderlyingRoot,
StrToNum(StrMid(Name(),6,6)), 0);          /* Calculate only if an option is
being evaluated. */
OptionTypeToEvaluate  = IIf(UnderlyingToEvaluate == UnderlyingRoot,
IIf(StrMid(Name(),12,1) == "P", 1, 2), 0); /* Calculate only if an option is
being evaluated. */  /* 1 = put.  2 = call.  */
StrikeToEvaluate      = IIf(UnderlyingToEvaluate == UnderlyingRoot,
StrToNum(StrMid(Name(),13,8)) / 1000, 0);  /* Calculate only if an option is
being evaluated. */

OptionType = 1; /* 1 = put.  2 = call.  */
ShortPutStrike = AtmStrike - StrikeIncrement * StrikesFromAtmToShort;

ShortPutFound =
    IIf(UnderlyingToEvaluate  == UnderlyingRoot,     True, False)
AND IIf(ExpirationToEvaluate  == Expiration,         True, False)
AND IIf(OptionTypeToEvaluate  == OptionType,         True, False)
AND IIf(StrikeToEvaluate      == ShortPutStrike,     True, False);
//if( DebugView ) _TRACE("# UnderlyingToEvaluate  = " +
UnderlyingToEvaluate  + " from " + Name() );
//Plot(UnderlyingPrice, UnderlyingRoot, colorBlack, styleLine);
//Plot(StrikeToEvaluate, "StrikeToEvaluate", colorGreen, styleLine);
//Plot(ExpirationToEvaluate, "ExpirationToEvaluate", colorGreen, styleLine);
//Plot(Expiration, "Expiration", colorRed, styleLine);
//Plot(ShortPutFound, "ShortPutFound", colorBlack, styleDots);
//Plot(OptionTypeToEvaluate, "OptionTypeToEvaluate", colorBlack, styleLine);
//Plot(OptionType, "OptionType", colorBlack, styleLine);
//Plot(ShortPutStrike, "ShortPutStrike", colorBlack, styleLine);

OptionType = 2; /* 1 = put.  2 = call.  */
ShortCallStrike = AtmStrike + StrikeIncrement * StrikesFromAtmToShort;

ShortCallFound =
    IIf(UnderlyingToEvaluate  == UnderlyingRoot,      True, False)
AND IIf(ExpirationToEvaluate  == Expiration,          True, False)
AND IIf(OptionTypeToEvaluate  == OptionType,          True, False)
AND IIf(StrikeToEvaluate      == ShortCallStrike,     True, False);
//Plot(ShortCallFound, "ShortCallFound", colorBlack, styleDots);
//Plot(OptionTypeToEvaluate, "OptionTypeToEvaluate", colorBlack, styleLine);
//Plot(OptionType, "OptionType", colorRed, styleLine);
//Plot(ShortCallStrike, "ShortCallStrike", colorBlack, styleLine);
//if( DebugView ) _TRACE("# UnderlyingRoot        = " + UnderlyingRoot);

ForceExpiration = False; /* True means force settlment at expiration
behavior for testing purposes */

Short        = ShortPutFound OR ShortCallFound;
//PlotColor = IIf( Short == 0, colorRed, colorGreen ); Plot(Short + 100,
"Short", PlotColor, style = styleDots + styleNoLine);
ShortPrice   = Close - SlippagePerSharePerLeg - CommissionPerSharePerLeg; /*
Close should be Mid (average of Bid and Ask), but IB doesn't supply that
data end of day. */
//Plot(ShortPrice, "ShortPrice", colorBlack, styleLine);
PositionSize = ShortPrice * 100 + 0.01; /* Perhaps due to AB's rounding, the
additional penny is required to generate a trade every day. */

Cover =
      ConvertDateFromAmiBrokerToOPRA(DateNum()) > ExpirationToEvaluate /*
Cash settlement is calculated on the first trading day after Saturday
expiration. */
OR    ForceExpiration;
//PlotColor = IIf( Cover == 0, colorRed, colorGreen ); Plot(Cover, "Cover",
PlotColor, style = styleDots + styleNoLine);
CoverSettlementPrice = Foreign(SettlementSymbol, "Close", fixup = 1);
//Plot(CoverSettlementPrice, "CoverSettlementPrice", colorBlack, styleLine);
CoverSettlement =
   IIf(OptionTypeToEvaluate == 1,                    /* 1 = put.  2 = call.
*/
       Max(StrikeToEvaluate - CoverSettlementPrice, 0),   /* put settlement
*/
       Max(CoverSettlementPrice - StrikeToEvaluate, 0) ); /* call settlement
*/
//Plot(CoverSettlement, "CoverSettlement", colorBlack, styleLine);
CoverPrice =
       CoverSettlement + CommissionPerSharePerLeg * sign(CoverSettlement);
//Plot(CoverPrice, "CoverPrice", colorBlack, styleLine);

OptionType = 1; /* 1 = put.  2 = call.  */
LongPutStrike = AtmStrike - StrikeIncrement * StrikesFromAtmToShort -
StrikeIncrement * StrikesFromShortToLong;

LongPutFound =
    IIf(UnderlyingToEvaluate  == UnderlyingRoot,     True, False)
AND IIf(ExpirationToEvaluate  == Expiration,         True, False)
AND IIf(OptionTypeToEvaluate  == OptionType,         True, False)
AND IIf(StrikeToEvaluate      == LongPutStrike,      True, False);
//if( DebugView ) _TRACE("# UnderlyingToEvaluate  = " +
UnderlyingToEvaluate  + " from " + Name() );
//Plot(UnderlyingPrice, UnderlyingRoot, colorBlack, styleLine);
//Plot(StrikeToEvaluate, "StrikeToEvaluate", colorGreen, styleLine);
//Plot(ExpirationToEvaluate, "ExpirationToEvaluate", colorGreen, styleLine);
//Plot(Expiration, "Expiration", colorRed, styleLine);
//Plot(LongPutFound, "LongPutFound", colorBlack, styleDots);
//Plot(OptionTypeToEvaluate, "OptionTypeToEvaluate", colorBlack, styleLine);
//Plot(OptionType, "OptionType", colorBlack, styleLine);
//Plot(LongPutStrike, "LongPutStrike", colorBlack, styleLine);

OptionType = 2; /* 1 = put.  2 = call.  */
LongCallStrike = AtmStrike + StrikeIncrement * StrikesFromAtmToShort +
StrikeIncrement * StrikesFromShortToLong;

LongCallFound =
    IIf(UnderlyingToEvaluate  == UnderlyingRoot,      True, False)
AND IIf(ExpirationToEvaluate  == Expiration,          True, False)
AND IIf(OptionTypeToEvaluate  == OptionType,          True, False)
AND IIf(StrikeToEvaluate      == LongCallStrike,      True, False);
//Plot(LongCallFound, "LongCallFound", colorBlack, styleDots);
//Plot(OptionTypeToEvaluate, "OptionTypeToEvaluate", colorBlack, styleLine);
//Plot(OptionType, "OptionType", colorRed, styleLine);
//Plot(LongCallStrike, "LongCallStrike", colorBlack, styleLine);
//if( DebugView ) _TRACE("# UnderlyingRoot        = " + UnderlyingRoot);

Buy        = LongPutFound OR LongCallFound;
//PlotColor = IIf( Buy == 0, colorRed, colorGreen ); Plot(Buy + 100, "Buy",
PlotColor, style = styleDots + styleNoLine);
BuyPrice   = Close + SlippagePerSharePerLeg + CommissionPerSharePerLeg; /*
Close should be Mid (average of Bid and Ask), but IB doesn't supply that
data end of day. */
//Plot(BuyPrice, "BuyPrice", colorBlack, styleLine);
PositionSize = BuyPrice * 100 + 0.01; /* Perhaps due to AB's rounding, the
additional penny is required to generate a trade every day. */

Sell =
      ConvertDateFromAmiBrokerToOPRA(DateNum()) > ExpirationToEvaluate /*
Cash settlement calculated first trading day after Saturday expiration. */
OR    ForceExpiration;
//PlotColor = IIf( Sell == 0, colorRed, colorGreen ); Plot(Sell, "Sell",
PlotColor, style = styleDots + styleNoLine);
SellSettlementPrice = Foreign(SettlementSymbol, "Close", fixup = 1);
//Plot(SellSettlementPrice, "SellSettlementPrice", colorBlack, styleLine);
SellSettlement =
   IIf(OptionTypeToEvaluate == 1,                    /* 1 = put.  2 = call.
*/
       Max(StrikeToEvaluate - SellSettlementPrice, 0),   /* put settlement
*/
       Max(SellSettlementPrice - StrikeToEvaluate, 0) ); /* call settlement
*/
//Plot(SellSettlement, "SellSettlement", colorBlack, styleLine);
SellPrice =
       SellSettlement - CommissionPerSharePerLeg * sign(SellSettlement);
//Plot(SellPrice, "SellPrice", colorBlack, styleLine);

Reply via email to