// Investor Flow — Derisking Strategy Library (Slice 23) // // ADR-0007: "winners have flexibility; losers only cut, never average down." // Derisking suggestions are considerations, not instructions. No trade verbs. // // Pure/cache-deterministic: no I/O. Pure logic operating on position + market data. // ─── Derisking Strategy Types ─────────────────────────────────────────────── export type DeriskingStrategyId = | 'scale_out_at_targets' | 'stop_trail_ema21_50' | 'thesis_based_partial' | 'option_protected_collar' | 'regime_cut' | 'correlation_driven'; export interface DeriskingStrategy { id: DeriskingStrategyId; name: string; description: string; alfredFraming: string; requiresOptionsSleeve: boolean; } export const DERISKING_STRATEGIES: DeriskingStrategy[] = [ { id: 'scale_out_at_targets', name: 'Scale Out at Targets', description: 'Reduce partial position size as price reaches predefined profit targets. This adds flexibility — some position remains if the thesis continues to play out.', alfredFraming: 'Winners have flexibility. Scaling out at targets locks in some gains while leaving room for further upside.', requiresOptionsSleeve: false, }, { id: 'stop_trail_ema21_50', name: 'Stop-Trail EMA 21/50', description: 'Trail a stop above the 21-day or 50-day EMA. This adapts the stop to volatility, allowing the position to breathe while protecting gains.', alfredFraming: 'Trailing stops adapt to changing conditions. A static stop ignores new information.', requiresOptionsSleeve: false, }, { id: 'thesis_based_partial', name: 'Thesis-Based Partial Reduction', description: 'If the thesis is weakening but not fully invalidated, consider reducing position size rather than fully exiting. This acknowledges uncertainty without abandoning the thesis entirely.', alfredFraming: 'When the thesis weakens but is not broken, flexibility means adjusting size — not all or nothing.', requiresOptionsSleeve: false, }, { id: 'option_protected_collar', name: 'Option-Protected Collar', description: 'Buy a protective put and sell a covered call to collar the position. This defines downside risk while partially financing the protection with call premium.', alfredFraming: 'A collar is insurance financed by someone else. You give up some upside to protect the downside.', requiresOptionsSleeve: true, }, { id: 'regime_cut', name: 'Regime-Cut Reduction', description: 'When the market regime shifts to trending-down, consider reducing exposure across the portfolio. This is a macro-level derisking response to changed conditions.', alfredFraming: 'When the tide turns, reduce exposure. Fighting a regime shift is a common way winners become losers.', requiresOptionsSleeve: false, }, { id: 'correlation_driven', name: 'Correlation-Driven Reduction', description: 'When multiple positions become highly correlated, consider reducing the most correlated ones. Concentrated correlation creates hidden portfolio risk.', alfredFraming: 'When everything moves together, you do not have diversification — you have one bet wearing many masks.', requiresOptionsSleeve: false, }, ]; // ─── Derisking Suggestion Input ───────────────────────────────────────────── export interface DeriskingInput { symbol: string; currentPrice: number; avgCost: number; shares: number; ema21?: number; ema50?: number; thesisStatus?: 'intact' | 'weakening' | 'broken'; currentRegime?: 'trending-up' | 'trending-down' | 'range-bound'; portfolioCorrelation?: number; // 0-1, how correlated this position is with others optionsUnlockState?: number; // 0-4, from ConvexityGate profitTargets?: number[]; // price levels } export interface DeriskingSuggestion { strategy: DeriskingStrategy; applicable: boolean; reason: string; details: string; } // ─── Suggestion Engine ────────────────────────────────────────────────────── export function suggestDerisking(input: DeriskingInput): DeriskingSuggestion[] { const suggestions: DeriskingSuggestion[] = []; const inProfit = input.currentPrice > input.avgCost; for (const strat of DERISKING_STRATEGIES) { const suggestion = evaluateStrategy(strat, input, inProfit); if (suggestion.applicable) { suggestions.push(suggestion); } } return suggestions; } function evaluateStrategy( strat: DeriskingStrategy, input: DeriskingInput, inProfit: boolean, ): DeriskingSuggestion { switch (strat.id) { case 'scale_out_at_targets': { const targets = input.profitTargets ?? []; const hittingTarget = targets.some((t) => input.currentPrice >= t); return { strategy: strat, applicable: inProfit && hittingTarget, reason: hittingTarget ? `Price has reached a predefined target. Consider scaling out a portion.` : 'Price has not reached any predefined target.', details: strat.alfredFraming, }; } case 'stop_trail_ema21_50': { const hasEMA = input.ema21 !== undefined || input.ema50 !== undefined; const belowEMA = input.ema21 !== undefined && input.currentPrice < input.ema21; return { strategy: strat, applicable: hasEMA && inProfit, reason: belowEMA ? `Price has fallen below EMA21. A trailing stop may help protect gains.` : 'Consider trailing a stop above EMA21 or EMA50 to protect gains while allowing the position to breathe.', details: strat.alfredFraming, }; } case 'thesis_based_partial': { const weakening = input.thesisStatus === 'weakening'; return { strategy: strat, applicable: weakening, reason: weakening ? 'Thesis is weakening but not broken. Consider a partial reduction rather than a full exit.' : 'Thesis is not weakening.', details: strat.alfredFraming, }; } case 'option_protected_collar': { const hasOptionsSleeve = (input.optionsUnlockState ?? 0) >= 3; return { strategy: strat, applicable: hasOptionsSleeve && inProfit, reason: hasOptionsSleeve ? 'Options insurance sleeve is unlocked. A collar may define downside risk while partially financing protection.' : 'Options insurance sleeve is not unlocked. Collar strategy requires the insurance sleeve tier.', details: strat.alfredFraming, }; } case 'regime_cut': { const regimeDown = input.currentRegime === 'trending-down'; return { strategy: strat, applicable: regimeDown, reason: regimeDown ? 'Market regime has shifted to trending-down. Consider reducing exposure across the portfolio.' : 'Market regime is not trending-down.', details: strat.alfredFraming, }; } case 'correlation_driven': { const highCorr = (input.portfolioCorrelation ?? 0) > 0.7; return { strategy: strat, applicable: highCorr, reason: highCorr ? `Position correlation is ${(input.portfolioCorrelation! * 100).toFixed(0)}%. Consider reducing the most correlated positions.` : 'Position correlation is within acceptable range.', details: strat.alfredFraming, }; } } }