Overall Statistics |
Total Trades 1086 Average Win 3.04% Average Loss -2.02% Compounding Annual Return 37.041% Drawdown 44.000% Expectancy 0.839 Net Profit 4473.163% Sharpe Ratio 1.008 Probabilistic Sharpe Ratio 31.200% Loss Rate 27% Win Rate 73% Profit-Loss Ratio 1.50 Alpha 0 Beta 0 Annual Standard Deviation 0.297 Annual Variance 0.088 Information Ratio 1.008 Tracking Error 0.297 Treynor Ratio 0 Total Fees $3086.12 Estimated Strategy Capacity $380000.00 Lowest Capacity Asset SPY 323H68DJAL1JA|SPY R735QTJ8XC9X |
using System; using System.Collections.Generic; using System.Linq; using QuantConnect.Brokerages; using QuantConnect.Data; using QuantConnect.Securities; using QuantConnect.Securities.Equity; using QuantConnect.Securities.Option; using QuantConnect.Util; namespace QuantConnect.Algorithm.CSharp { internal class Protection { public Security Underlying { get; } public decimal Weight { get; } public decimal ProfitTarget { get; } public decimal PercentBelow { get; } private int MinimumDte { get; } private int MaximumDte { get; } public int ExerciseAtDte { get; } private Option? _contract; public Protection(Security underlying, decimal weight, decimal profitTarget = 1.3m, decimal percentBelow = 0.4m, int minimumDte = 270, int maximumDte = 420, int exerciseAtDte = 180) { Underlying = underlying; Weight = weight; ProfitTarget = profitTarget; PercentBelow = percentBelow; MinimumDte = minimumDte; MaximumDte = maximumDte; ExerciseAtDte = exerciseAtDte; } // Call this OnData at times to update options public void ManageContract(QCAlgorithm algo) { // if no contract, we try to find one if (_contract == null) { var symbol = FindContract(algo); if (symbol != null) { _contract = algo.AddOptionContract(symbol, Resolution.Daily); } } // okay: now we know the contract is good... else { if (algo.Portfolio[_contract.Symbol].Invested) { var tooShort = (_contract.Symbol.ID.Date - algo.Time).Days < ExerciseAtDte; var targetHit = Underlying.Price < _contract.Symbol.ID.StrikePrice * ProfitTarget; if (tooShort || targetHit) { algo.Liquidate(_contract.Symbol); algo.RemoveSecurity(_contract.Symbol); _contract = null; } } else { algo.SetHoldings(_contract.Symbol, Weight); } } } public Symbol? FindContract(QCAlgorithm algo) { var targetStrike = Underlying.Price * (1 - PercentBelow) - Underlying.Price * (1 - PercentBelow) % 5m; return algo.OptionChainProvider .GetOptionContractList(Underlying.Symbol, algo.Time) .Where(c => { var dte = (c.ID.Date - algo.Time).Days; var isPut = c.ID.OptionRight == OptionRight.Put; var strikeOk = c.ID.StrikePrice == targetStrike; var dateOk = MinimumDte < dte && dte <= MaximumDte; return isPut && strikeOk && dateOk; }) .OrderBy(p => (p.ID.Date, p.ID.StrikePrice)) .Take(1) .FirstOrDefault(); } } public class AllWeather : QCAlgorithm { private Protection _protection; private const decimal FreeCash = 0.02m; private const decimal EquityAllocation = 3m * 0.95m; private const decimal ProtectionAllocation = 0.01m; public bool ShouldRebalance { get; private set; } = true; public List<(Equity Security, decimal Target)> Targets { get; set; } public override void Initialize() { SetBrokerageModel(new InteractiveBrokersBrokerageModel()); SetStartDate(2010, 1, 1); SetCash(100000); LoadWeights(new List<(string Symbol, decimal Target)> { ("SPY", 0.35m), // Equities ("XLK", 0.16m), // Tech ("TLT", 0.15m), // Fixed ("XLE", 0.09m), // Energy ("XLF", 0.08m), // Finance ("XLV", 0.07m), // Health ("GXC", 0.06m) // China // ("GBTC", 0.35m), // Crypto // ("DBP", 0.10m), // Metals }); _protection = new Protection(Securities["SPY"], ProtectionAllocation); Schedule.On( DateRules.MonthStart(Targets[0].Security.Symbol), TimeRules.AfterMarketOpen(Targets[0].Security.Symbol, minutesAfterOpen: 10), delegate() { ShouldRebalance = true; } ); } public override void OnData(Slice data) { if (ShouldRebalance) { var allValid = Targets.All(x => data.Bars.Keys.Contains(x.Security.Symbol)); if (allValid) { var buyingPower = Portfolio.TotalPortfolioValue * (1 - FreeCash); var equityBuyingPower = buyingPower * EquityAllocation; Targets .Select(pair => { var (symbol, tgt) = pair; var close = data.Bars[symbol.Symbol].Close; var goal = (tgt * equityBuyingPower) / close; var current = Portfolio[symbol.Symbol].Quantity; var delta = (int)Math.Truncate(goal - current); return (symbol, close, current, goal, delta); }) .Where(x => x.delta != 0) // Do the sells first then the buys .OrderBy(x => x.delta) .DoForEach(x => { Debug($"Rebal {x.symbol} @ ${x.close:F} from {x.current} to {x.goal}"); MarketOrder(x.symbol, x.delta); }); // We're done ShouldRebalance = false; } } _protection.ManageContract(this); } private void LoadWeights(List<(string Symbol, decimal Target)> Raw) { var total = Raw.Select(pair => pair.Target).Sum(); Targets = Raw.Select(pair => { var (symbol, target) = pair; var security = AddEquity(symbol, Resolution.Daily); security.SetDataNormalizationMode(DataNormalizationMode.Raw); security.SetMarginModel(new PatternDayTradingMarginModel(4m, 4m)); return (Security: security, Target: target / total); }).ToList(); } } }