| 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();
}
}
}