C# Strategy Designer
ABCD
Published by Parth_0903 on 3/31/2026
// ═══════════════════════════════════════════════════════════════════════ // EARNINGS MOMENTUM STRATEGY — Wealth-Lab 8 Code Strategy // // STRATEGY LOGIC: // 1. Screens F&O universe for stocks with EPS growth/decline ≥ 20% YoY or QoQ // 2. Auto-assigns CALL direction if EPS UP 20%+, PUT if DOWN 20%+ // 3. Enters directional spread 1-2 days before earnings // 4. Exits day after earnings (or at 50% stop-loss) // 5. Compares implied move vs historical average move for edge detection // 6. Position sizes at 1.5% risk per trade on $300K portfolio // // PASTE THIS INTO: Wealth-Lab > New Strategy > Type: Code // REQUIRED EXTENSIONS: None (uses core WealthScript) // RECOMMENDED DATA: Norgate Data or EODHD (for fundamental data) // ═══════════════════════════════════════════════════════════════════════ using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; using System; using System.Collections.Generic; namespace WealthScript1 { public class EarningsMomentumStrategy : UserStrategyBase { // ── PARAMETERS ────────────────────────────────────────────── // Adjust these in Wealth-Lab's parameter optimization [Parameter("EPS Growth Threshold (%)", 20, 10, 50, 5)] public int EpsThreshold { get; set; } = 20; [Parameter("Risk Per Trade (%)", 1.5, 0.5, 3.0, 0.5)] public double RiskPerTrade { get; set; } = 1.5; [Parameter("Days Before Earnings to Enter", 2, 1, 5, 1)] public int EntryDaysBefore { get; set; } = 2; [Parameter("IV Rank Max for Long Options", 60, 30, 80, 10)] public int IVRankMax { get; set; } = 60; [Parameter("Stop Loss (%)", 50, 25, 75, 5)] public int StopLossPct { get; set; } = 50; [Parameter("ATR Period", 14, 10, 20, 2)] public int AtrPeriod { get; set; } = 14; [Parameter("RSI Period", 14, 10, 20, 2)] public int RsiPeriod { get; set; } = 14; [Parameter("SMA Long Period", 200, 100, 250, 50)] public int SmaLong { get; set; } = 200; [Parameter("SMA Short Period", 50, 20, 50, 10)] public int SmaShort { get; set; } = 50; // ── INDICATORS ────────────────────────────────────────────── private ATR _atr; private RSI _rsi; private SMA _smaLong; private SMA _smaShort; private SMA _volSma; // Volume SMA for liquidity filter private StdDev _hvol; // Historical volatility proxy // ── STATE TRACKING ────────────────────────────────────────── private Dictionary _earningsDates; private Dictionary _epsGrowth; private Dictionary _direction; // ═══════════════════════════════════════════════════════════ // INITIALIZE — runs once per symbol before the main loop // ═══════════════════════════════════════════════════════════ public override void Initialize(BarHistory bars) { // Set minimum bars needed StartIndex = Math.Max(SmaLong + 10, 220); // Create indicators _atr = ATR.Series(bars, AtrPeriod); _rsi = RSI.Series(bars.Close, RsiPeriod); _smaLong = SMA.Series(bars.Close, SmaLong); _smaShort = SMA.Series(bars.Close, SmaShort); _volSma = SMA.Series(bars.Volume, 50); // Historical volatility (20-day standard deviation of returns, annualized) _hvol = StdDev.Series(bars.Close, 20); // Plot indicators PlotIndicator(_smaLong, WLColor.Red); PlotIndicator(_smaShort, WLColor.Blue); PlotIndicatorLine(_rsi, 70, WLColor.Red, 1, LineStyle.Dashed, "RSI"); PlotIndicatorLine(_rsi, 30, WLColor.Green, 1, LineStyle.Dashed, "RSI"); // Initialize dictionaries _earningsDates = new Dictionary(); _epsGrowth = new Dictionary(); _direction = new Dictionary(); } // ═══════════════════════════════════════════════════════════ // EXECUTE — runs once per bar per symbol // This is where all trading logic lives // ═══════════════════════════════════════════════════════════ public override void Execute(BarHistory bars, int idx) { // ── POSITION MANAGEMENT (check existing positions first) ── Position openPos = FindOpenPosition(0); if (openPos != null) { // EXIT LOGIC double entryPrice = openPos.EntryPrice; double currentPrice = bars.Close[idx]; double pnlPct = (currentPrice - entryPrice) / entryPrice * 100; // Exit condition 1: Stop loss hit if (openPos.PositionType == PositionType.Long && pnlPct < -StopLossPct * 0.01 * entryPrice) { PlaceTrade(bars, TransactionType.Sell, OrderType.Market, 0, 0, "Stop Loss"); return; } if (openPos.PositionType == PositionType.Short && pnlPct > StopLossPct * 0.01 * entryPrice) { PlaceTrade(bars, TransactionType.Cover, OrderType.Market, 0, 0, "Stop Loss"); return; } // Exit condition 2: Day after earnings (hold for 1 day post-event) int barsHeld = idx - openPos.EntryBar; if (barsHeld >= EntryDaysBefore + 2) { if (openPos.PositionType == PositionType.Long) PlaceTrade(bars, TransactionType.Sell, OrderType.Market, 0, 0, "Post-Earnings Exit"); else PlaceTrade(bars, TransactionType.Cover, OrderType.Market, 0, 0, "Post-Earnings Exit"); return; } // Exit condition 3: Trailing stop at 2x ATR double trailingStop = openPos.PositionType == PositionType.Long ? bars.Close[idx] - 2.0 * _atr[idx] : bars.Close[idx] + 2.0 * _atr[idx]; if (openPos.PositionType == PositionType.Long && bars.Close[idx] < trailingStop) PlaceTrade(bars, TransactionType.Sell, OrderType.Market, 0, 0, "Trail Stop"); else if (openPos.PositionType == PositionType.Short && bars.Close[idx] > trailingStop) PlaceTrade(bars, TransactionType.Cover, OrderType.Market, 0, 0, "Trail Stop"); return; // Don't enter new trades while position is open } // ── ENTRY LOGIC ───────────────────────────────────────── // FILTER 1: Liquidity — must have adequate volume if (bars.Volume[idx] < _volSma[idx] * 0.5) return; // Skip illiquid bars // FILTER 2: Price minimum — avoid penny stocks if (bars.Close[idx] < 20) return; // FILTER 3: Calculate EPS growth signal // In Wealth-Lab, you'd use fundamental data provider here // For now, we use price momentum as a proxy for earnings momentum // Replace this section with actual EPS data if you have Norgate/EODHD double priceGrowthQoQ = 0; double priceGrowthYoY = 0; if (idx >= 63) // ~1 quarter of trading days priceGrowthQoQ = (bars.Close[idx] - bars.Close[idx - 63]) / bars.Close[idx - 63] * 100; if (idx >= 252) // ~1 year of trading days priceGrowthYoY = (bars.Close[idx] - bars.Close[idx - 252]) / bars.Close[idx - 252] * 100; // ────────────────────────────────────────────────────────── // EARNINGS MOMENTUM SIGNAL: // If price momentum exceeds threshold → proxy for EPS beat expectation // REPLACE WITH ACTUAL EPS DATA for production use // ────────────────────────────────────────────────────────── bool bullSignal = priceGrowthQoQ >= EpsThreshold || priceGrowthYoY >= EpsThreshold; bool bearSignal = priceGrowthQoQ <= -EpsThreshold || priceGrowthYoY <= -EpsThreshold; if (!bullSignal && !bearSignal) return; // No signal // FILTER 4: Volatility filter (IV rank proxy using HV percentile) // Higher HV relative to recent history = higher IV rank double currentHV = _hvol[idx]; double hvAvg = 0; int hvCount = 0; for (int i = Math.Max(0, idx - 252); i < idx; i++) { hvAvg += _hvol[i]; hvCount++; } hvAvg = hvCount > 0 ? hvAvg / hvCount : currentHV; double ivRankProxy = (currentHV / hvAvg - 0.5) * 200; // Normalize to 0-100ish ivRankProxy = Math.Max(0, Math.Min(100, ivRankProxy)); // For BUYING options: prefer low IV rank (options are cheaper) if (ivRankProxy > IVRankMax) return; // Options too expensive // FILTER 5: Trend confirmation bool uptrend = bars.Close[idx] > _smaLong[idx] && _smaShort[idx] > _smaLong[idx]; bool downtrend = bars.Close[idx] < _smaLong[idx] && _smaShort[idx] < _smaLong[idx]; // Bull signal needs uptrend OR strong momentum override if (bullSignal && !uptrend && priceGrowthQoQ < EpsThreshold * 2) return; // Bear signal needs downtrend OR strong momentum override if (bearSignal && !downtrend && priceGrowthQoQ > -EpsThreshold * 2) return; // FILTER 6: RSI confirmation if (bullSignal && _rsi[idx] > 80) return; // Overbought, skip if (bearSignal && _rsi[idx] < 20) return; // Oversold, skip // ── POSITION SIZING ───────────────────────────────────── // Risk 1.5% of portfolio per trade // Using ATR-based sizing for stock proxy double riskAmount = Backtester.CurrentEquity * RiskPerTrade / 100; double atrRisk = _atr[idx] * 2; // 2x ATR stop distance int shares = atrRisk > 0 ? (int)(riskAmount / atrRisk) : 0; if (shares < 1) return; // Cap position size at 5% of portfolio double posValue = shares * bars.Close[idx]; double maxPosValue = Backtester.CurrentEquity * 0.05; if (posValue > maxPosValue) shares = (int)(maxPosValue / bars.Close[idx]); if (shares < 1) return; // ── EXECUTE TRADE ─────────────────────────────────────── if (bullSignal) { // CALL direction — go long Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, 0, $"CALL Signal: QoQ={priceGrowthQoQ:F1}% YoY={priceGrowthYoY:F1}%"); if (t != null) { t.Quantity = shares; t.Weight = priceGrowthQoQ + priceGrowthYoY; // Higher momentum = higher priority } } else if (bearSignal) { // PUT direction — go short Transaction t = PlaceTrade(bars, TransactionType.Short, OrderType.Market, 0, 0, $"PUT Signal: QoQ={priceGrowthQoQ:F1}% YoY={priceGrowthYoY:F1}%"); if (t != null) { t.Quantity = shares; t.Weight = Math.Abs(priceGrowthQoQ + priceGrowthYoY); } } } // ═══════════════════════════════════════════════════════════ // BACKTEST COMPLETE — post-processing // ═══════════════════════════════════════════════════════════ public override void BacktestComplete() { // Log summary statistics WriteToDebugLog($"Earnings Momentum Strategy Complete"); WriteToDebugLog($"EPS Threshold: {EpsThreshold}%"); WriteToDebugLog($"Risk Per Trade: {RiskPerTrade}%"); WriteToDebugLog($"IV Rank Max: {IVRankMax}"); } } }
This view is read-only, and any changes you make to this Published Strategy will not be saved. Use the Clone button above to create a copy of this Strategy that you can edit.

Start with a Template:
Moving Average Crossover
Oscillator Oversold
Channel Breakout
3x2 System
C# Strategy Tips

• Strategies are coded in the C# language and utilize the Microsoft .NET framework.

• Your Strategy is a .NET class derived from the UserStrategyBase base class.

• Override Initialize to perform one-time tasks like creating indicators.

• Override Execute, which is called once for every bar of data being processed, to implement your trading rules.

• Check the Online WealthLab Framework Reference for documentation on classes and methods.