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!
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); } } } } }
Rename
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 -
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); } } } } }
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.
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); } } } } }
Your Response
Post
Edit Post
Login is required