Skip to main content
Glama
Ademscodeisnotsobad

Quant Companion MCP

strategies.ts11.6 kB
/** * Sample Strategy Implementations * * Simple strategies for testing the backtest framework. * All strategies are pure functions: StrategyContext → StrategyDecision */ import { StrategyContext, StrategyDecision, StrategyFn, } from "./types"; import { calculateSMA } from "./backtest"; // ============================================ // HELPER FUNCTIONS // ============================================ /** * Calculate target shares for a given allocation. */ function targetSharesForAllocation( equity: number, price: number, allocation: number ): number { if (price <= 0) return 0; return Math.floor((equity * allocation) / price); } // ============================================ // STRATEGY: ALWAYS LONG (Baseline) // ============================================ /** * Always Long Strategy * * Simple baseline: always maintain maximum allowed position. * Useful for comparing against buy-and-hold. */ export function createAlwaysLongStrategy(allocation: number = 0.95): StrategyFn { return (context: StrategyContext): StrategyDecision => { const targetShares = targetSharesForAllocation( context.equity, context.price, allocation ); return { targetPositionShares: targetShares, confidence: 1.0, reason: "always long", }; }; } // ============================================ // STRATEGY: SMA CROSSOVER // ============================================ export interface SMACrossoverConfig { fastPeriod: number; // e.g., 20 slowPeriod: number; // e.g., 50 allocation: number; // e.g., 0.95 } /** * SMA Crossover Strategy * * - Long when fast SMA > slow SMA * - Flat when fast SMA < slow SMA */ export function createSMACrossoverStrategy(config: SMACrossoverConfig): StrategyFn { const { fastPeriod, slowPeriod, allocation } = config; return (context: StrategyContext): StrategyDecision => { const { analytics, priceHistory, equity, price } = context; // Check if we have analytics with pre-computed SMAs let fastSMA = analytics?.sma20; let slowSMA = analytics?.sma50; // If not using default periods or analytics missing, calculate from history if (fastPeriod !== 20 || !fastSMA) { fastSMA = priceHistory ? calculateSMA(priceHistory, fastPeriod) : undefined; } if (slowPeriod !== 50 || !slowSMA) { slowSMA = priceHistory ? calculateSMA(priceHistory, slowPeriod) : undefined; } // Not enough data if (fastSMA === undefined || slowSMA === undefined) { return { targetPositionShares: 0, confidence: 0, reason: `insufficient data for SMA(${fastPeriod}/${slowPeriod})`, }; } // Signal logic const isBullish = fastSMA > slowSMA; if (isBullish) { const targetShares = targetSharesForAllocation(equity, price, allocation); return { targetPositionShares: targetShares, confidence: 0.7, reason: `SMA crossover bullish: SMA${fastPeriod}=${fastSMA.toFixed(2)} > SMA${slowPeriod}=${slowSMA.toFixed(2)}`, }; } else { return { targetPositionShares: 0, confidence: 0.7, reason: `SMA crossover bearish: SMA${fastPeriod}=${fastSMA.toFixed(2)} < SMA${slowPeriod}=${slowSMA.toFixed(2)}`, }; } }; } // ============================================ // STRATEGY: VOL REGIME // ============================================ export interface VolRegimeConfig { hvShortPeriod: number; // e.g., 20 hvLongPeriod: number; // e.g., 60 allocation: number; // e.g., 0.95 /** If true, go long when vol is compressing (HV short < HV long) */ longOnCompression: boolean; } /** * Volatility Regime Strategy * * - If HV20 < HV60 (vol compressing) → risk-on (long) * - If HV20 > HV60 (vol expanding) → risk-off (flat) */ export function createVolRegimeStrategy(config: VolRegimeConfig): StrategyFn { const { hvShortPeriod, hvLongPeriod, allocation, longOnCompression } = config; return (context: StrategyContext): StrategyDecision => { const { analytics, equity, price } = context; // Get volatility readings const hvShort = analytics?.hv20; const hvLong = analytics?.hv60; // Not enough data if (hvShort === undefined || hvLong === undefined) { return { targetPositionShares: 0, confidence: 0, reason: `insufficient data for HV(${hvShortPeriod}/${hvLongPeriod})`, }; } const isCompressing = hvShort < hvLong; const shouldBeLong = longOnCompression ? isCompressing : !isCompressing; if (shouldBeLong) { const targetShares = targetSharesForAllocation(equity, price, allocation); return { targetPositionShares: targetShares, confidence: 0.6, reason: `vol regime: HV${hvShortPeriod}=${(hvShort * 100).toFixed(1)}% ${isCompressing ? "<" : ">"} HV${hvLongPeriod}=${(hvLong * 100).toFixed(1)}%`, }; } else { return { targetPositionShares: 0, confidence: 0.6, reason: `vol regime risk-off: HV${hvShortPeriod}=${(hvShort * 100).toFixed(1)}% ${isCompressing ? "<" : ">"} HV${hvLongPeriod}=${(hvLong * 100).toFixed(1)}%`, }; } }; } // ============================================ // STRATEGY: RSI MEAN REVERSION // ============================================ export interface RSIMeanReversionConfig { oversoldThreshold: number; // e.g., 30 overboughtThreshold: number; // e.g., 70 allocation: number; // e.g., 0.95 } /** * RSI Mean Reversion Strategy * * - Long when RSI < oversold threshold * - Flat when RSI > overbought threshold * - Hold current position otherwise */ export function createRSIMeanReversionStrategy(config: RSIMeanReversionConfig): StrategyFn { const { oversoldThreshold, overboughtThreshold, allocation } = config; return (context: StrategyContext): StrategyDecision => { const { analytics, equity, price, position } = context; const rsi = analytics?.rsi14; // Not enough data if (rsi === undefined) { return { targetPositionShares: 0, confidence: 0, reason: "insufficient data for RSI", }; } if (rsi < oversoldThreshold) { const targetShares = targetSharesForAllocation(equity, price, allocation); return { targetPositionShares: targetShares, confidence: 0.7, reason: `RSI oversold: ${rsi.toFixed(1)} < ${oversoldThreshold}`, }; } else if (rsi > overboughtThreshold) { return { targetPositionShares: 0, confidence: 0.7, reason: `RSI overbought: ${rsi.toFixed(1)} > ${overboughtThreshold}`, }; } else { // Hold current return { targetPositionShares: position, confidence: 0.4, reason: `RSI neutral: ${rsi.toFixed(1)}`, }; } }; } // ============================================ // STRATEGY: TREND + VOL FILTER // ============================================ export interface TrendVolFilterConfig { smaPeriod: number; // e.g., 50 maxVolThreshold: number; // e.g., 0.30 (30% annualized) allocation: number; // e.g., 0.95 } /** * Trend + Vol Filter Strategy * * - Long when price > SMA AND volatility is not extreme * - Flat otherwise */ export function createTrendVolFilterStrategy(config: TrendVolFilterConfig): StrategyFn { const { smaPeriod, maxVolThreshold, allocation } = config; return (context: StrategyContext): StrategyDecision => { const { analytics, price, equity, priceHistory } = context; const hv = analytics?.hv20; let sma = analytics?.sma50; if (smaPeriod !== 50) { sma = priceHistory ? calculateSMA(priceHistory, smaPeriod) : undefined; } // Not enough data if (sma === undefined || hv === undefined) { return { targetPositionShares: 0, confidence: 0, reason: "insufficient data for trend+vol filter", }; } const isUptrend = price > sma; const isVolOK = hv < maxVolThreshold; if (isUptrend && isVolOK) { const targetShares = targetSharesForAllocation(equity, price, allocation); return { targetPositionShares: targetShares, confidence: 0.75, reason: `trend up (${price.toFixed(2)} > SMA${smaPeriod}=${sma.toFixed(2)}) + vol OK (${(hv * 100).toFixed(1)}%)`, }; } else if (!isUptrend) { return { targetPositionShares: 0, confidence: 0.7, reason: `trend down: ${price.toFixed(2)} < SMA${smaPeriod}=${sma.toFixed(2)}`, }; } else { return { targetPositionShares: 0, confidence: 0.65, reason: `vol too high: ${(hv * 100).toFixed(1)}% > ${(maxVolThreshold * 100).toFixed(1)}%`, }; } }; } // ============================================ // STRATEGY: DUAL MOMENTUM // ============================================ export interface DualMomentumConfig { lookbackPeriod: number; // e.g., 60 days allocation: number; // e.g., 0.95 } /** * Dual Momentum Strategy * * - Long if price is higher than lookbackPeriod ago AND positive momentum * - Flat otherwise */ export function createDualMomentumStrategy(config: DualMomentumConfig): StrategyFn { const { lookbackPeriod, allocation } = config; return (context: StrategyContext): StrategyDecision => { const { priceHistory, price, equity } = context; if (!priceHistory || priceHistory.length < lookbackPeriod) { return { targetPositionShares: 0, confidence: 0, reason: `insufficient data for ${lookbackPeriod}-day momentum`, }; } const priceNDaysAgo = priceHistory[lookbackPeriod - 1]; const momentum = (price - priceNDaysAgo) / priceNDaysAgo; if (momentum > 0) { const targetShares = targetSharesForAllocation(equity, price, allocation); return { targetPositionShares: targetShares, confidence: Math.min(0.9, 0.5 + momentum), reason: `positive ${lookbackPeriod}-day momentum: ${(momentum * 100).toFixed(1)}%`, }; } else { return { targetPositionShares: 0, confidence: 0.6, reason: `negative ${lookbackPeriod}-day momentum: ${(momentum * 100).toFixed(1)}%`, }; } }; } // ============================================ // STRATEGY FACTORY // ============================================ export type StrategyType = | "always_long" | "sma_crossover" | "vol_regime" | "rsi_mean_reversion" | "trend_vol_filter" | "dual_momentum"; /** * Create a strategy by name with default configuration. */ export function createStrategy(type: StrategyType): StrategyFn { switch (type) { case "always_long": return createAlwaysLongStrategy(0.95); case "sma_crossover": return createSMACrossoverStrategy({ fastPeriod: 20, slowPeriod: 50, allocation: 0.95 }); case "vol_regime": return createVolRegimeStrategy({ hvShortPeriod: 20, hvLongPeriod: 60, allocation: 0.95, longOnCompression: true }); case "rsi_mean_reversion": return createRSIMeanReversionStrategy({ oversoldThreshold: 30, overboughtThreshold: 70, allocation: 0.95 }); case "trend_vol_filter": return createTrendVolFilterStrategy({ smaPeriod: 50, maxVolThreshold: 0.30, allocation: 0.95 }); case "dual_momentum": return createDualMomentumStrategy({ lookbackPeriod: 60, allocation: 0.95 }); default: throw new Error(`Unknown strategy type: ${type}`); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ademscodeisnotsobad/Quant-Companion-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server