- ago
Hello, I am trying to code the wheel strategy in C#. For starters, I am attempting to code logic for an OTM Cash-secured Put.

The code seems to initially sell. However, when the put option expires, it does not sell another put option. Any thoughts on why this would be?

Also the code does not post the options price chart in the chart pane for some reason.

Thanks!



CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; using System.Windows.Media.Imaging; namespace WealthScript7 {    public class MyStrategy : UserStrategyBase    {       private IndicatorBase sma;       private TimeSeries close;              string _osymput;       BarHistory _obarsput; // the option's BarHistory       string _osymcall;       BarHistory _obarscall; // the option's BarHistory       DateTime _expiry;              int _hash = -1;       UniqueList<string> _syms = new UniqueList<string>();       TimeSeries _hv;              public MyStrategy() : base()       {       }              //create indicators and other objects here, this is executed prior to the main trading loop       public override void Initialize(BarHistory bars)       {          close = bars.Close;          sma = new SMA(bars.Close, Parameters[0].AsInt);          PlotIndicator(sma, WLColor.Salmon);                              int hvbars = 144;          _hv = HV.Series(bars.Close, hvbars, 280) / 100.0;          PlotTimeSeries(_hv, "HV", "HV");          StartIndex = hvbars;       }       //execute the strategy rules here, this is executed once for each bar in the backtest history       public override void Execute(BarHistory bars, int idx)       {          Position opt = FindOpenPositionAllSymbols(_hash);          // Use a synthetic contract. Cone states in webinar that synthetic can work for daily bars.          // PUT OPTION          _osymput = OptionSynthetic.GetOptionsSymbol(bars, OptionType.Put, bars.Close[idx] * 9, bars.DateTimes[idx], 30, useWeeklies: false, allowExpired: true);          _hash = Math.Abs(_osymput.GetHashCode());          _expiry = OptionsHelper.SymbolExpiry(_osymput);          //GetHistory generates Barhistory objects based on Stock's BarHistory and Implied IV          _obarsput = OptionSynthetic.GetHistory(bars, _osymput, _hv);          _syms.Add(_obarsput.Symbol);          // Use a synthetic contract. Cone states in webinar that synthetic can work for daily bars.          // CALL OPTION          /*          _osymcall = OptionSynthetic.GetOptionsSymbol(bars, OptionType.Call, bars.Close[idx], bars.DateTimes[idx], 30, useWeeklies: false, allowExpired: true);          _hash = Math.Abs(_osymcall.GetHashCode());          _expiry = OptionsHelper.SymbolExpiry(_osymcall);          //GetHistory generates Barhistory objects based on Stock's BarHistory and Implied IV          _obarsput = OptionSynthetic.GetHistory(bars, _osymcall, _hv);          _syms.Add(_obarscall.Symbol);          */          if (opt == null)          {             // Sell a put.             if (CurrentCash > ((bars.Close[idx] * 100 + 500) ))             {                Transaction t1 = PlaceTrade(_obarsput, TransactionType.Short, OrderType.Market, 0, _hash, "Sell Put");                t1.Quantity = 1;                WriteToDebugLog("-------");                WriteToDebugLog("selling " + t1.Quantity + " put on " + bars.DateTimes[idx]);                WriteToDebugLog("Should Expire on " + _expiry);                WriteToDebugLog(CurrentCash + " cash is greater than " + (bars.Close[idx] * 100 + 500));                WriteToDebugLog(bars.Close[idx] * 100);                                }          }                    else          {             // close the position at expiry             if (bars.DateTimes[idx].AddDays(1) >= _expiry && opt != null)             {                ClosePosition(opt, OrderType.Market, 0, "Expired");                WriteToDebugLog("Expiration. Selling on " + bars.DateTimes[idx]);             }          }       }       public override void Cleanup(BarHistory bars)       {          // don't overload the chart with panes - plot only the last 5 contracts that were traded in Single Symbol Mode          if (!Backtester.Strategy.SingleSymbolMode)             return;          // Indicate the trades          foreach (Position p in GetPositionsAllSymbols())          {             if (p.Bars == bars || _syms.IndexOf(p.Symbol) < _syms.Count - 5)                continue;             PlotBarHistory(p.Bars, p.Symbol);             if (!p.NSF)             {                if (p.EntryBar > 0)                   DrawTextVAlign("▲", p.EntryBar, p.Bars.Low[p.EntryBar] * 0.95, VerticalAlignment.Top, WLColor.NeonBlue, 16, 0, 0, p.Symbol, true);                if (p.ExitBar > 0)                   DrawTextVAlign("▼", p.ExitBar, p.Bars.High[p.ExitBar] * 1.05, VerticalAlignment.Bottom, WLColor.NeonFuschia, 16, 0, 0, p.Symbol, true);             }          }       }    } }
0
321
2 Replies

Reply

Bookmark

Sort
Cone8
 ( 5.78% )
- ago
#1
That code can't run without having Parameters[0] defined, but it's not required, so just remove it. Anyway, you specified bars.Close[idx] * 9 for the option price. It's not likely that a strike 9x the current price is even available, but if it were, it would be a very expensive put. I know you're shorting it, but the point is that it's not realistic to expect it to be/have been trading.

I can help you out with this, but explain to me the "strategy" for strike selection.
- Look at option chain for the next expiration to see the highest strike that has traded. Currently for XOM, the 180 strike is the highest you could trade - not even 2x the last close at $107.57.

Meanwhile, this will get you closer -

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; namespace WealthScript7 {    public class MyStrategy : UserStrategyBase    {       private IndicatorBase sma;       private TimeSeries close;       string _osymput;       BarHistory _obarsput; // the option's BarHistory       string _osymcall;       BarHistory _obarscall; // the option's BarHistory       DateTime _expiry;       int _hash = -1;       UniqueList<string> _syms = new UniqueList<string>();       TimeSeries _hv;       public MyStrategy() : base()       {       }       //create indicators and other objects here, this is executed prior to the main trading loop       public override void Initialize(BarHistory bars)       {          int hvbars = 144;          _hv = HV.Series(bars.Close, hvbars, 280) / 100.0;          //PlotTimeSeries(_hv, "HV", "HV");             StartIndex = hvbars;       }       //execute the strategy rules here, this is executed once for each bar in the backtest history       public override void Execute(BarHistory bars, int idx)       {          Position opt = FindOpenPositionAllSymbols(_hash);          if (opt == null)          {             // Use a synthetic contract             // PUT OPTION             _osymput = OptionSynthetic.GetOptionsSymbol(bars, OptionType.Put, bars.Close[idx] * 1.75, bars.DateTimes[idx], 30, useWeeklies: false, allowExpired: true);             _hash = Math.Abs(_osymput.GetHashCode());             _expiry = OptionsHelper.SymbolExpiry(_osymput);             //GetHistory generates Barhistory objects based on Stock's BarHistory and Implied IV             _obarsput = OptionSynthetic.GetHistory(bars, _osymput, _hv);             _syms.Add(_obarsput.Symbol);                          // Sell a put             if (CurrentCash > ((bars.Close[idx] * 100 + 500)))             {                Transaction t1 = PlaceTrade(_obarsput, TransactionType.Short, OrderType.Market, 0, _hash, "Sell Put");                t1.Quantity = 1;                WriteToDebugLog("-------");                WriteToDebugLog($"selling {t1.Quantity} {_osymput} put on " + bars.DateTimes[idx]);                WriteToDebugLog("Should Expire on " + _expiry);                WriteToDebugLog(CurrentCash + " cash is greater than " + (bars.Close[idx] * 100 + 500));                WriteToDebugLog(bars.Close[idx] * 100);             }          }          else          {             // close the position at expiry             if (bars.DateTimes[idx].AddDays(1) >= _expiry && opt != null)             {                ClosePosition(opt, OrderType.Market, 0, "Expired");                WriteToDebugLog("Expiration. Selling on " + bars.DateTimes[idx]);             }          }       }       public override void Cleanup(BarHistory bars)       {          // don't overload the chart with panes - plot only the last 5 contracts that were traded in Single Symbol Mode          if (!Backtester.Strategy.SingleSymbolMode)             return;          // Indicate the trades          foreach (Position p in GetPositionsAllSymbols())          {             if (p.Bars == bars || _syms.IndexOf(p.Symbol) < _syms.Count - 5)                continue;             PlotBarHistory(p.Bars, p.Symbol);             if (!p.NSF)             {                if (p.EntryBar > 0)                   DrawTextVAlign("▲", p.EntryBar, p.Bars.Low[p.EntryBar] * 0.95, VerticalAlignment.Top, WLColor.NeonBlue, 16, 0, 0, p.Symbol, true);                if (p.ExitBar > 0)                   DrawTextVAlign("▼", p.ExitBar, p.Bars.High[p.ExitBar] * 1.05, VerticalAlignment.Bottom, WLColor.NeonFuschia, 16, 0, 0, p.Symbol, true);             }          }       }    } }
0
- ago
#2
Cone, apologies for my careless mistake. I meant to put .9, not 9. I have now fixed it and have the strike price for the put to be closest to bars.Close[idx] *.95 and the call be closest to bars.Close[idx] * 1.05.

Here's my updated code, I think I'm getting pretty close. My only issue is that whenever I enter a long stock position for covered calls, it messes with my (opt == null) logic at the beginning. I need to figure out how to have a long stock position but still be able to sell a call option. I have commented out the code that enters the stock position below.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Data; using WealthLab.Indicators; using System.Collections.Generic; namespace WealthScript4 {    public class MyStrategy : UserStrategyBase    {       string _osymPut;       string _osymCall;       BarHistory _obarsPut; // the option's BarHistory       BarHistory _obarsCall; // the option's BarHistory              int _hash = -1;       DateTime _expiry;       UniqueList<string> _syms = new UniqueList<string>();       TimeSeries _hv;       bool doCoveredCalls;       bool doCashSecuredPuts;       int i;       public MyStrategy() : base()       {       }       //create indicators and other objects here, this is executed prior to the main trading loop       public override void Initialize(BarHistory bars)       {          int hvbars = 144;          _hv = HV.Series(bars.Close, hvbars, 350) / 100.0;          //PlotTimeSeries(_hv, "HV", "HV");          StartIndex = hvbars;          i = 0;       }       //execute the strategy rules here, this is executed once for each bar in the backtest history       public override void Execute(BarHistory bars, int idx)       {          // Enabling Secured Puts as our first strategy when i == 0 (start of backtest)          if (i == 0)          {             doCashSecuredPuts = true;             doCoveredCalls = false;          }          i++;          Position opt = FindOpenPositionAllSymbols(_hash);          if ( opt == null)          {             //-----------------------------------             // Use a synthetic contract             // PUT OPTION                _osymPut = OptionSynthetic.GetOptionsSymbol(bars, OptionType.Put, bars.Close[idx] * .95, bars.DateTimes[idx], 30, useWeeklies: false, allowExpired: true);                _hash = Math.Abs(_osymPut.GetHashCode());                _expiry = OptionsHelper.SymbolExpiry(_osymPut);                //GetHistory generates Barhistory objects based on Stock's BarHistory and Implied IV                _obarsPut = OptionSynthetic.GetHistory(bars, _osymPut, _hv);                _syms.Add(_obarsPut.Symbol);                                                    //-----------------------------------             // Use a synthetic contract             // CALL OPTION                _osymCall = OptionSynthetic.GetOptionsSymbol(bars, OptionType.Call, bars.Close[idx] * 1.05, bars.DateTimes[idx], 30, useWeeklies: false, allowExpired: true);                _hash = Math.Abs(_osymCall.GetHashCode());                _expiry = OptionsHelper.SymbolExpiry(_osymCall);                //GetHistory generates Barhistory objects based on Stock's BarHistory and Implied IV                _obarsCall = OptionSynthetic.GetHistory(bars, _osymCall, _hv);                _syms.Add(_obarsCall.Symbol);                          //----------------------------------                          // Perform Cash Secured Put Strategy             if (doCashSecuredPuts && CurrentCash > ((bars.Close[idx] * 100)))             {                Transaction t1 = PlaceTrade(_obarsPut, TransactionType.Short, OrderType.Market, 1, _hash, "Sell Put Option");                t1.Quantity = 1;                WriteToDebugLog("-------");                WriteToDebugLog($"Selling {t1.Quantity} {_osymPut} Put on " + bars.DateTimes[idx]);                WriteToDebugLog($"Should Expire on {_expiry}");                WriteToDebugLog($"{CurrentCash.ToString("N2")} cash is greater than {(bars.Close[idx] * 100).ToString("N2")}");                WriteToDebugLog($"doCashSecuredPuts is {doCashSecuredPuts}. doCoveredCalls is {doCoveredCalls}");                WriteToDebugLog("~~~~~~~~~");             }             if (!doCashSecuredPuts && doCoveredCalls)             {                if (CurrentCash > ((bars.Close[idx] * 100)))                {                   Transaction t3 = PlaceTrade(_obarsCall, TransactionType.Short, OrderType.Market, 2, _hash, "Sell Call Option");                   t3.Quantity = 1;                   WriteToDebugLog("-------");                   WriteToDebugLog($"Selling {t3.Quantity} {_osymCall} Call on " + bars.DateTimes[idx]);                   WriteToDebugLog($"Should Expire on {_expiry}");                   WriteToDebugLog($"{CurrentCash.ToString("N2")} cash is greater than {(bars.Close[idx] * 100).ToString("N2")}");                   WriteToDebugLog($"doCashSecuredPuts is {doCashSecuredPuts}. doCoveredCalls is {doCoveredCalls}");                   WriteToDebugLog("~~~~~~~~~");                }             }          }          else          {             // close the position at expiry             if (bars.DateTimes[idx].AddDays(1) >= _expiry && opt != null)             {                // for expiring put contracts                if (doCashSecuredPuts && !doCoveredCalls)                {                   // Put expires worthless, so keep doing cash secuerd puts                   if (_obarsPut.Close[idx] < 0.01)                   {                      WriteToDebugLog("++++++++++++++++++");                      WriteToDebugLog($"Put {_osymPut} Expires Worthless at {_obarsPut.Close[idx].ToString("N4")} on {bars.DateTimes[idx]}");                      ClosePosition(opt, OrderType.Market, 0, "Expired");                      doCashSecuredPuts = true;                      doCoveredCalls = false;                      WriteToDebugLog($"doCashSecuredPuts is {doCashSecuredPuts}. doCoveredCalls is {doCoveredCalls}");                      WriteToDebugLog("Continue Cash Secured Put Strategy.");                      WriteToDebugLog("++++++++++++++++++");                   }                   // put expires ITM, so switch to call options                   if (_obarsPut.Close[idx] > 0)                   {                      WriteToDebugLog("++++++++++++++++++");                      WriteToDebugLog($"Put {_osymPut} Expires ITM at {_obarsPut.Close[idx].ToString("N4")} on {bars.DateTimes[idx]}");                      ClosePosition(opt, OrderType.Market, 0, "Expired");                      doCashSecuredPuts = false;                      doCoveredCalls = true;                      WriteToDebugLog("Switching to Covered Call Strategy.");                      WriteToDebugLog($"doCashSecuredPuts is {doCashSecuredPuts}. doCoveredCalls is {doCoveredCalls}");                                         // have to buy 100 shares of current stock                      Transaction t2 = PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 1, 0, "Buy 100 shares at market");                      t2.Quantity = 100;                                            //WriteToDebugLog("Bought 100 Shares of Stock");                      //WriteToDebugLog("++++++++++++++++++");                   }                }                // for expiring option contracts                if (!doCashSecuredPuts && doCoveredCalls)                {                   // Call expires worthless, so keep doing covered calls                   if (_obarsCall.Close[idx] == 0)                   {                      WriteToDebugLog("++++++++++++++++++");                      WriteToDebugLog($"Call {_osymCall} Expires Worthless at {_obarsCall.Close[idx].ToString("N4")} on {bars.DateTimes[idx]}");                      // Close out the worthless call option, NOT the shares, hence the position tag.                      ClosePosition(FindOpenPosition(2), OrderType.Market, 0, "Expired");                      doCashSecuredPuts = false;                      doCoveredCalls = true;                      WriteToDebugLog("Continue Covered Call Strategy.");                      WriteToDebugLog($"doCashSecuredPuts is {doCashSecuredPuts}. doCoveredCalls is {doCoveredCalls}");                      WriteToDebugLog("++++++++++++++++++");                   }                   // Call expires ITM, so switch to Cash Secured Puts.                   if (_obarsCall.Close[idx] > 0)                   {                      WriteToDebugLog("++++++++++++++++++");                      WriteToDebugLog($"Call {_osymCall} Expires ITM at {_obarsCall.Close[idx].ToString("N4")} on {bars.DateTimes[idx]}");                      // Close out the Call and stocks.                      ClosePosition(opt, OrderType.Market, 0, "Expired");                      if(HasOpenPosition(bars, PositionType.Long))                      {                         PlaceTrade(bars, TransactionType.Sell, OrderType.Market, 1, 0, "Sell 100 shares at market");                      }                      doCashSecuredPuts = true;                      doCoveredCalls = false;                      WriteToDebugLog("Switching to Cash Secured Put Strategy.");                      WriteToDebugLog($"doCashSecuredPuts is {doCashSecuredPuts}. doCoveredCalls is {doCoveredCalls}");                      WriteToDebugLog("++++++++++++++++++");                   }                }             }          }       }       public override void Cleanup(BarHistory bars)       {          // don't overload the chart with panes - plot only the last 5 contracts that were traded in Single Symbol Mode          if (!Backtester.Strategy.SingleSymbolMode)             return;          // Indicate the trades          foreach (Position p in GetPositionsAllSymbols())          {             if (p.Bars == bars || _syms.IndexOf(p.Symbol) < _syms.Count - 5)                continue;             PlotBarHistory(p.Bars, p.Symbol);             if (!p.NSF)             {                if (p.EntryBar > 0)                   DrawTextVAlign("▲", p.EntryBar, p.Bars.Low[p.EntryBar] * 0.95, VerticalAlignment.Top, WLColor.NeonBlue, 16, 0, 0, p.Symbol, true);                if (p.ExitBar > 0)                   DrawTextVAlign("▼", p.ExitBar, p.Bars.High[p.ExitBar] * 1.05, VerticalAlignment.Bottom, WLColor.NeonFuschia, 16, 0, 0, p.Symbol, true);             }          }       }    } }
0

Reply

Bookmark

Sort