Jelajahi Sumber

Add risk control and net exposure enforcement

helium3@sina.com 19 jam lalu
induk
melakukan
5ff371ef8d

+ 8 - 0
config/delta-strategy-config.json

@@ -78,5 +78,13 @@
       "useMarketOrder": true,
       "netExposureThreshold": 0.001
     }
+  },
+  "riskControls": {
+    "netExposureTolerance": 0.0003,
+    "maxSlippagePercent": 0.001,
+    "maxDrawdownPercent": 0.02,
+    "volatilityWindowSeconds": 60,
+    "volatilityUpperPercent": 0.004,
+    "minTradeFraction": 0.25
   }
 }

+ 8 - 3
src/modules/ComponentInitializer.ts

@@ -4,7 +4,7 @@
  */
 
 import Logger from '../utils/Logger';
-import { DeltaStrategyConfig, ExecutionConfig, MonitoringSettings } from './ConfigurationManager';
+import { DeltaStrategyConfig, ExecutionConfig, MonitoringSettings, RiskControlSettings } from './ConfigurationManager';
 import WebSocket from 'ws';
 
 // Ensure WebSocket is available globally when running under Node.js
@@ -80,6 +80,7 @@ export class ComponentInitializer {
     config: DeltaStrategyConfig,
     executionConfig: ExecutionConfig,
     monitoringSettings: MonitoringSettings,
+    riskControls: RiskControlSettings,
     accountManager: AccountManager,
     tradingSymbol: string
   ): Promise<CoreComponents> {
@@ -129,7 +130,8 @@ export class ComponentInitializer {
       accountManager,
       utilizationThresholds: monitoringSettings.utilizationThresholds,
       minOrderValue: executionConfig.minOrderValue,
-      effectiveLeverage: monitoringSettings.effectiveLeverage
+      effectiveLeverage: monitoringSettings.effectiveLeverage,
+      riskControls
     });
 
     // Create HedgingModule
@@ -149,7 +151,8 @@ export class ComponentInitializer {
       maxOrderValue: executionConfig.maxOrderValue,
       maxOrderValueRatio: executionConfig.maxOrderValueRatio,
       allowBypassMaxOrderValue: executionConfig.allowBypassMaxOrderValue || false,
-      positionTPSL: config.positionTPSL
+      positionTPSL: config.positionTPSL,
+      maxSlippagePercent: riskControls.maxSlippagePercent
     });
 
     // Register accounts to signal executor
@@ -157,6 +160,8 @@ export class ComponentInitializer {
       signalExecutor.registerClient(accountId, accountInfo.client);
     }
 
+    signalExecutor.setDataAggregator(dataAggregator);
+
     // Initialize TradingPhaseManager if enabled
     let phaseManager: TradingPhaseManager | null = null;
     if (config.phaseManagement?.enabled) {

+ 69 - 0
src/modules/ConfigurationManager.ts

@@ -78,8 +78,17 @@ export interface DeltaStrategyConfig {
       minNotional?: number;
       cooldownMs?: number;
       useMarketOrder?: boolean;
+      netExposureThreshold?: number;
     };
   };
+  riskControls?: {
+    netExposureTolerance?: number;
+    maxSlippagePercent?: number;
+    maxDrawdownPercent?: number;
+    volatilityWindowSeconds?: number;
+    volatilityUpperPercent?: number;
+    minTradeFraction?: number;
+  };
 }
 
 export interface ExecutionConfig {
@@ -100,6 +109,15 @@ export interface MonitoringSettings {
   minOrderValue?: number;
 }
 
+export interface RiskControlSettings {
+  netExposureTolerance: number;
+  maxSlippagePercent: number;
+  maxDrawdownPercent: number;
+  volatilityWindowSeconds: number;
+  volatilityUpperPercent: number;
+  minTradeFraction: number;
+}
+
 export class ConfigurationManager {
   private static instance: ConfigurationManager;
   private logger: Logger;
@@ -109,6 +127,7 @@ export class ConfigurationManager {
   private deltaConfig: DeltaStrategyConfig | null = null;
   private executionConfig: ExecutionConfig | null = null;
   private monitoringSettings: MonitoringSettings | null = null;
+  private riskControlSettings: RiskControlSettings | null = null;
 
   private constructor() {
     this.logger = Logger.getInstance();
@@ -146,6 +165,8 @@ export class ConfigurationManager {
       );
       this.logger.info('Loaded delta strategy configuration');
 
+      this.riskControlSettings = this.extractRiskControls(this.deltaConfig?.riskControls);
+
       // Load execution configuration
       this.executionConfig = JSON.parse(
         readFileSync('./config/execution.json', 'utf-8')
@@ -238,6 +259,11 @@ export class ConfigurationManager {
       throw new Error('Monitoring settings: minOrderValue must be positive when provided');
     }
 
+    if (!this.riskControlSettings) {
+      this.logger.warn('Risk control settings not provided; using defaults');
+      this.riskControlSettings = this.extractRiskControls(undefined);
+    }
+
     this.logger.info('Configuration validation passed');
   }
 
@@ -285,6 +311,13 @@ export class ConfigurationManager {
     return this.monitoringSettings;
   }
 
+  public getRiskControls(): RiskControlSettings {
+    if (!this.riskControlSettings) {
+      throw new Error('Risk control settings not loaded');
+    }
+    return this.riskControlSettings;
+  }
+
   /**
    * Get trading interval
    */
@@ -292,6 +325,42 @@ export class ConfigurationManager {
     return this.deltaConfig?.strategy?.tradingInterval || 15000;
   }
 
+  private extractRiskControls(source?: DeltaStrategyConfig['riskControls']): RiskControlSettings {
+    const defaults: RiskControlSettings = {
+      netExposureTolerance: 0.0005,
+      maxSlippagePercent: 0.002,
+      maxDrawdownPercent: 0.05,
+      volatilityWindowSeconds: 60,
+      volatilityUpperPercent: 0.005,
+      minTradeFraction: 0.25
+    };
+
+    if (!source) {
+      return defaults;
+    }
+
+    return {
+      netExposureTolerance: source.netExposureTolerance && source.netExposureTolerance > 0
+        ? source.netExposureTolerance
+        : defaults.netExposureTolerance,
+      maxSlippagePercent: source.maxSlippagePercent && source.maxSlippagePercent > 0
+        ? source.maxSlippagePercent
+        : defaults.maxSlippagePercent,
+      maxDrawdownPercent: source.maxDrawdownPercent && source.maxDrawdownPercent > 0
+        ? source.maxDrawdownPercent
+        : defaults.maxDrawdownPercent,
+      volatilityWindowSeconds: source.volatilityWindowSeconds && source.volatilityWindowSeconds > 0
+        ? source.volatilityWindowSeconds
+        : defaults.volatilityWindowSeconds,
+      volatilityUpperPercent: source.volatilityUpperPercent && source.volatilityUpperPercent > 0
+        ? source.volatilityUpperPercent
+        : defaults.volatilityUpperPercent,
+      minTradeFraction: source.minTradeFraction && source.minTradeFraction > 0 && source.minTradeFraction <= 1
+        ? source.minTradeFraction
+        : defaults.minTradeFraction
+    };
+  }
+
   /**
    * Get trading symbol
    */

+ 96 - 8
src/modules/MonitoringManager.ts

@@ -11,6 +11,7 @@ import { TradingCycleManager } from './TradingCycleManager';
 import { SignalExecutor } from '../services/SignalExecutor';
 import { TradingSignal } from '../core/HedgingDecisionModule';
 import { PriceTick } from '../types/market';
+import { RiskControlSettings } from './ConfigurationManager';
 
 // Monitoring components
 import {
@@ -71,6 +72,7 @@ export interface MonitoringConfig {
     useMarketOrder?: boolean;
     netExposureThreshold?: number;
   };
+  riskControls?: RiskControlSettings;
 }
 
 export class MonitoringManager {
@@ -82,6 +84,8 @@ export class MonitoringManager {
   private emergencyMitigationConfig: MonitoringConfig['emergencyMitigation'] | null = null;
   private lastEmergencyMitigation = 0;
   private emergencyReductionActive = false;
+  private netExposureTolerance: number = 0;
+  private maxDrawdownPercent: number = 0;
 
   // Dependencies
   private dataAggregator: DataAggregator;
@@ -124,7 +128,8 @@ export class MonitoringManager {
         percent: Number(maxExposureConfig['percent'] ?? 0)
       },
       monitoringInterval: Number(raw['monitoringInterval'] ?? 5000),
-      leverageMultiplier
+      leverageMultiplier,
+      maxDrawdownPercent: this.maxDrawdownPercent
     };
   }
 
@@ -249,6 +254,13 @@ export class MonitoringManager {
         this.emergencyMitigationConfig.netExposureThreshold = config.emergencyMitigation.netExposureThreshold;
       }
     }
+
+    if (config.riskControls) {
+      const tolerance = config.riskControls.netExposureTolerance ?? 0;
+      this.netExposureTolerance = tolerance > 0 ? tolerance : 0;
+      const drawdown = config.riskControls.maxDrawdownPercent ?? 0;
+      this.maxDrawdownPercent = drawdown > 0 ? drawdown : 0;
+    }
   }
 
   /**
@@ -348,6 +360,14 @@ export class MonitoringManager {
       });
 
       await this.closePositionsForAccount(signal.accountId, 'margin_insufficient');
+      try {
+        await this.accountManager.refreshAccountData(signal.accountId);
+      } catch (error) {
+        this.logger.warn('Failed to refresh account data after forced close', {
+          accountId: signal.accountId,
+          error
+        });
+      }
 
       this.marginMonitor?.clearReduceCooldown(signal.accountId);
       if (this.tradingCycleManager) {
@@ -367,6 +387,15 @@ export class MonitoringManager {
       await this.reduceCounterpartPositions(signal);
     }
 
+    try {
+      await this.accountManager.refreshAccountData(signal.accountId);
+    } catch (error) {
+      this.logger.warn('Failed to refresh account data after margin reduction', {
+        accountId: signal.accountId,
+        error
+      });
+    }
+
     await this.syncCurrentBalancesToMarginMonitor();
 
     const marginStatus = this.marginMonitor?.getAccountsStatus().find(status => status.accountId === signal.accountId);
@@ -784,6 +813,10 @@ export class MonitoringManager {
       await this.handleMaxExposure(status);
     });
 
+    this.exposureMonitor.on('drawdown', async (status: ExposureStatus) => {
+      await this.handleExposureDrawdown(status);
+    });
+
     // Setup position update listener
     this.dataAggregator.on('account_positions_updated', ({ accountId, positions }: any) => {
       this.updateExposureData(accountId, positions);
@@ -855,6 +888,19 @@ export class MonitoringManager {
     await this.closeAllPositionsForExposure('take_profit', status.reason ?? undefined);
   }
 
+  private async handleExposureDrawdown(status: ExposureStatus): Promise<void> {
+    this.logger.error('📉 Exposure drawdown threshold hit!', {
+      drawdownPercent: ((status.drawdownPercent ?? 0) * 100).toFixed(2) + '%',
+      reason: status.reason
+    });
+
+    await this.closeAllPositionsForExposure('drawdown', status.reason ?? undefined);
+
+    if (this.tradingCycleManager) {
+      this.tradingCycleManager.stop();
+    }
+  }
+
   /**
    * Handle max exposure
    */
@@ -984,6 +1030,8 @@ export class MonitoringManager {
       reduceFraction?: number;
       minNotional?: number;
       allowMarketOrders?: boolean;
+      toleranceSize?: number;
+      targetSymbol?: string;
     }
   ): Promise<void> {
     if (this.emergencyReductionActive && !(options?.forceReduce)) {
@@ -1088,20 +1136,27 @@ export class MonitoringManager {
     }
 
     if (forceReduce) {
-      symbolTotals.forEach((totalExposure, symbol) => {
-        if (Math.abs(totalExposure) < MIN_ORDER_SIZE) {
+      const toleranceSize = options?.toleranceSize ?? 0;
+      const symbols = options?.targetSymbol
+        ? [options.targetSymbol]
+        : Array.from(symbolTotals.keys());
+
+      symbols.forEach(symbol => {
+        const totalExposure = symbolTotals.get(symbol) ?? 0;
+        const excessExposure = Math.max(0, Math.abs(totalExposure) - toleranceSize);
+        if (excessExposure <= MIN_ORDER_SIZE) {
           return;
         }
 
         const priceData: PriceTick | undefined = aggregatedData.market.prices.get(symbol);
         const currentPrice = this.extractPrice(priceData);
         if (!(currentPrice > 0)) {
-          this.logger.warn('Unable to perform emergency reduction without price data', { symbol });
+          this.logger.warn('Unable to perform reduction without price data', { symbol });
           return;
         }
 
         const isNetLong = totalExposure > 0;
-        let remaining = Math.abs(totalExposure);
+        let remaining = excessExposure;
         const minAmount = Math.max(emergencyMinNotional / currentPrice, MIN_ORDER_SIZE);
 
         const candidateAccounts = Array.from(exposuresByAccount.entries())
@@ -1139,7 +1194,7 @@ export class MonitoringManager {
             amount,
             price,
             useMarketOrder: allowMarketOrders,
-            reason: `emergency_force_reduce: ${reasonText}`,
+            reason: `exposure_force_reduce: ${reasonText}`,
             timestamp: Date.now()
           });
 
@@ -1147,9 +1202,10 @@ export class MonitoringManager {
         }
 
         if (remaining > MIN_ORDER_SIZE) {
-          this.logger.warn('Residual exposure remains after emergency reduction attempt', {
+          this.logger.warn('Residual exposure remains after forced reduction attempt', {
             symbol,
-            remaining
+            remaining,
+            toleranceSize
           });
         }
       });
@@ -1279,6 +1335,38 @@ export class MonitoringManager {
     this.emergencyReductionActive = false;
   }
 
+  public async enforceNetExposureTolerance(): Promise<boolean> {
+    if (this.netExposureTolerance <= 0) {
+      return false;
+    }
+
+    const { netSize } = this.computeSymbolNetExposure(this.tradingSymbol);
+    if (Math.abs(netSize) <= this.netExposureTolerance) {
+      return false;
+    }
+
+    if (this.emergencyReductionActive) {
+      this.logger.debug('Emergency mitigation active; skipping net exposure enforcement');
+      return false;
+    }
+
+    this.logger.debug('Net exposure tolerance exceeded; triggering rebalance', {
+      netSize,
+      tolerance: this.netExposureTolerance
+    });
+
+    await this.rebalanceExposurePositions(0, 'net_tolerance', {
+      forceReduce: true,
+      reduceFraction: 1,
+      minNotional: this.minOrderValue,
+      allowMarketOrders: false,
+      toleranceSize: this.netExposureTolerance,
+      targetSymbol: this.tradingSymbol
+    });
+
+    return true;
+  }
+
   private selectAccountForExposureOpen(
     symbol: string,
     totalExposure: number,

+ 9 - 0
src/modules/TradingCycleManager.ts

@@ -212,6 +212,15 @@ export class TradingCycleManager {
         return;
       }
 
+      if (this.monitoringManager) {
+        const rebalanced = await this.monitoringManager.enforceNetExposureTolerance();
+        if (rebalanced) {
+          this.logger.info('Net exposure rebalanced; waiting for balances to settle');
+          this.stats.skippedCycles++;
+          return;
+        }
+      }
+
       // 1. Get aggregated data
       const aggregatedData = this.dataAggregator.getAggregatedData();
       this.logger.debug('Aggregated data retrieved', {

+ 36 - 1
src/services/ExposureRiskMonitor.ts

@@ -24,6 +24,7 @@ export interface ExposureControlConfig {
   };
   monitoringInterval: number;  // 监控间隔(ms)
   leverageMultiplier: number;  // 杠杆倍数,用于将名义敞口换算成所需保证金
+  maxDrawdownPercent?: number; // 最大允许回撤百分比
 }
 
 export interface PositionExposure {
@@ -48,6 +49,8 @@ export interface ExposureStatus {
   shouldStopLoss: boolean;     // 是否触发止损
   shouldTakeProfit: boolean;   // 是否触发止盈
   shouldReduceExposure: boolean; // 是否需要降低敞口
+  shouldTriggerDrawdown: boolean;
+  drawdownPercent?: number;
   reason?: string;             // 触发原因
 }
 
@@ -63,6 +66,7 @@ export class ExposureRiskMonitor extends EventEmitter {
   // 敞口数据追踪
   private positionExposures: Map<string, PositionExposure> = new Map();
   private accountEquity: number = 0;
+  private peakEquity: number = 0;
   private leverageMultiplier: number;
 
   // 统计数据
@@ -136,6 +140,9 @@ export class ExposureRiskMonitor extends EventEmitter {
    */
   public updateAccountEquity(equity: number): void {
     this.accountEquity = equity;
+    if (equity > this.peakEquity) {
+      this.peakEquity = equity;
+    }
   }
 
   /**
@@ -229,6 +236,16 @@ export class ExposureRiskMonitor extends EventEmitter {
       this.emit('maxExposure', status);
     }
 
+    if (status.shouldTriggerDrawdown) {
+      this.logger.error('📉 DRAW DOWN THRESHOLD TRIGGERED', {
+        drawdownPercent: ((status.drawdownPercent ?? 0) * 100).toFixed(2) + '%',
+        peakEquity: this.peakEquity,
+        currentEquity: this.accountEquity,
+        reason: status.reason
+      });
+      this.emit('drawdown', status);
+    }
+
     // 发送状态更新
     this.emit('exposureUpdate', status);
   }
@@ -259,6 +276,7 @@ export class ExposureRiskMonitor extends EventEmitter {
     let shouldStopLoss = false;
     let shouldTakeProfit = false;
     let shouldReduceExposure = false;
+    let shouldTriggerDrawdown = false;
     let reason: string | undefined;
 
     // 检查止损
@@ -311,6 +329,19 @@ export class ExposureRiskMonitor extends EventEmitter {
       }
     }
 
+    // 检查回撤
+    let drawdownPercent: number | undefined;
+    if (this.config.maxDrawdownPercent && this.config.maxDrawdownPercent > 0 && this.peakEquity > 0) {
+      drawdownPercent = this.peakEquity > 0
+        ? Math.max(0, 1 - (accountEquity / this.peakEquity))
+        : 0;
+      if (drawdownPercent >= this.config.maxDrawdownPercent) {
+        shouldTriggerDrawdown = true;
+        riskLevel = 'critical';
+        reason = `权益回撤 ${(drawdownPercent * 100).toFixed(2)}% 超过阈值 ${(this.config.maxDrawdownPercent * 100).toFixed(2)}%`;
+      }
+    }
+
     const status: ExposureStatus = {
       totalExposure,
       effectiveExposure,
@@ -323,12 +354,16 @@ export class ExposureRiskMonitor extends EventEmitter {
       riskLevel,
       shouldStopLoss,
       shouldTakeProfit,
-      shouldReduceExposure
+      shouldReduceExposure,
+      shouldTriggerDrawdown
     };
 
     if (reason) {
       status.reason = reason;
     }
+    if (drawdownPercent !== undefined) {
+      status.drawdownPercent = drawdownPercent;
+    }
 
     return status;
   }

+ 60 - 0
src/services/SignalExecutor.ts

@@ -15,6 +15,8 @@ import { PacificaSigningClient } from './PacificaSigningClient';
 import Logger from '../utils/Logger';
 import { v4 as uuidv4 } from 'uuid';
 import { TradingPhaseManager } from '../modules/TradingPhaseManager';
+import { DataAggregator } from './DataAggregator';
+import { PriceTick } from '../types/market';
 
 /**
  * 订单执行配置
@@ -39,6 +41,7 @@ export interface ExecutorConfig {
       percent: number;             // 止损百分比(相对入场价)
     };
   };
+  maxSlippagePercent?: number;     // 最大允许滑点百分比
 }
 
 /**
@@ -88,6 +91,7 @@ export class SignalExecutor extends EventEmitter {
   private lastOrderTime: number = 0;
   private phaseManager: TradingPhaseManager | null = null;
   private rebalanceLocks: Set<string> = new Set();
+  private dataAggregator: DataAggregator | null = null;
 
   constructor(config?: Partial<ExecutorConfig>) {
     super();
@@ -129,6 +133,11 @@ export class SignalExecutor extends EventEmitter {
     this.logger.info('TradingPhaseManager set for SignalExecutor');
   }
 
+  public setDataAggregator(dataAggregator: DataAggregator): void {
+    this.dataAggregator = dataAggregator;
+    this.logger.info('DataAggregator set for SignalExecutor');
+  }
+
   /**
    * 执行单个信号
    *
@@ -170,6 +179,30 @@ export class SignalExecutor extends EventEmitter {
       }
     }
 
+    if (this.config.maxSlippagePercent && this.config.maxSlippagePercent > 0 && signal.type === 'open') {
+      const referencePrice = this.getReferencePrice(signal.symbol);
+      if (referencePrice > 0) {
+        const targetPrice = signal.price ?? referencePrice;
+        const slippage = Math.abs(targetPrice - referencePrice) / referencePrice;
+        if (slippage > this.config.maxSlippagePercent) {
+          const error = `Slippage ${(slippage * 100).toFixed(2)}% exceeds limit ${(this.config.maxSlippagePercent * 100).toFixed(2)}%`;
+          this.logger.warn('⛔ Slippage limit exceeded, skipping signal', {
+            symbol: signal.symbol,
+            targetPrice,
+            referencePrice,
+            slippagePercent: (slippage * 100).toFixed(2) + '%'
+          });
+          return {
+            success: false,
+            signal,
+            error,
+            timestamp: Date.now(),
+            retryCount: 0
+          };
+        }
+      }
+    }
+
     // 检查账户是否已注册客户端
     const client = this.clientMap.get(signal.accountId);
     if (!client) {
@@ -685,6 +718,33 @@ export class SignalExecutor extends EventEmitter {
     };
   }
 
+  private getReferencePrice(symbol: string): number {
+    if (!this.dataAggregator) {
+      return 0;
+    }
+
+    try {
+      const aggregated = this.dataAggregator.getAggregatedData();
+      const priceTick: PriceTick | undefined = aggregated.market.prices.get(symbol);
+      if (!priceTick) {
+        return 0;
+      }
+      if (typeof priceTick.midPrice === 'number' && priceTick.midPrice > 0) {
+        return priceTick.midPrice;
+      }
+      if (typeof priceTick.markPrice === 'number' && priceTick.markPrice > 0) {
+        return priceTick.markPrice;
+      }
+      if (typeof priceTick.oraclePrice === 'number' && priceTick.oraclePrice > 0) {
+        return priceTick.oraclePrice;
+      }
+      return 0;
+    } catch (error) {
+      this.logger.warn('Failed to resolve reference price', { symbol, error });
+      return 0;
+    }
+  }
+
   /**
    * 下单(使用WebSocket,带签名认证)
    *

+ 4 - 1
src/strategies/ModularDeltaNeutralStrategy.ts

@@ -63,11 +63,13 @@ export class ModularDeltaNeutralStrategy {
       console.log('\n📡 Initializing core components...');
       const executionConfig = this.configManager.getExecutionConfig();
       const monitoringSettings = this.configManager.getMonitoringSettings();
+      const riskControls = this.configManager.getRiskControls();
 
       const coreComponents = await this.componentInitializer.initializeCoreComponents(
         deltaConfig,
         executionConfig,
         monitoringSettings,
+        riskControls,
         this.accountManager,
         tradingSymbol
       );
@@ -192,7 +194,8 @@ export class ModularDeltaNeutralStrategy {
         effectiveLeverage: monitoringSettings.effectiveLeverage,
         ...(deltaConfig.phaseManagement?.emergencyMitigation
           ? { emergencyMitigation: deltaConfig.phaseManagement.emergencyMitigation }
-          : {})
+          : {}),
+        riskControls
       };
 
       this.monitoringManager = new MonitoringManager(

+ 86 - 0
src/strategies/SimpleStrategyEngine.ts

@@ -33,6 +33,12 @@ interface StrategyOptions {
   utilizationThresholds?: UtilizationThresholds;
   minOrderValue?: number;
   effectiveLeverage?: number;
+  riskControls?: {
+    maxSlippagePercent?: number;
+    volatilityWindowSeconds?: number;
+    volatilityUpperPercent?: number;
+    minTradeFraction?: number;
+  };
 }
 
 export interface Sprint1Components {
@@ -56,6 +62,11 @@ export class SimpleStrategyEngine {
   private minOrderValue: number = 30;
   private effectiveLeverage: number = 1;
   private currentPhase: TradingPhase = TradingPhase.GRADUAL_BUILD;
+  private maxSlippagePercent: number = 0;
+  private volatilityWindowMs: number = 60_000;
+  private volatilityUpperPercent: number = 0.005;
+  private minTradeFraction: number = 0.2;
+  private priceHistory: Array<{ price: number; timestamp: number }> = [];
 
   // Sprint 1 components
   private pricingEngine: PricingEngine | null = null;
@@ -85,6 +96,18 @@ export class SimpleStrategyEngine {
     if (options?.effectiveLeverage && options.effectiveLeverage > 0) {
       this.effectiveLeverage = options.effectiveLeverage;
     }
+    if (options?.riskControls?.maxSlippagePercent && options.riskControls.maxSlippagePercent > 0) {
+      this.maxSlippagePercent = options.riskControls.maxSlippagePercent;
+    }
+    if (options?.riskControls?.volatilityWindowSeconds && options.riskControls.volatilityWindowSeconds > 0) {
+      this.volatilityWindowMs = options.riskControls.volatilityWindowSeconds * 1000;
+    }
+    if (options?.riskControls?.volatilityUpperPercent && options.riskControls.volatilityUpperPercent > 0) {
+      this.volatilityUpperPercent = options.riskControls.volatilityUpperPercent;
+    }
+    if (options?.riskControls?.minTradeFraction && options.riskControls.minTradeFraction > 0 && options.riskControls.minTradeFraction <= 1) {
+      this.minTradeFraction = options.riskControls.minTradeFraction;
+    }
   }
 
   /**
@@ -187,6 +210,20 @@ export class SimpleStrategyEngine {
     let calculatedPrice = await this.calculateOptimalPrice(currentPrice);
     let pricingMethod = this.getPricingMethod();
 
+    if (this.maxSlippagePercent > 0) {
+      const diffPercent = Math.abs(calculatedPrice - currentPrice) / currentPrice;
+      if (diffPercent > this.maxSlippagePercent) {
+        this.logger.info('Slippage guard triggered, skipping signal generation', {
+          currentPrice,
+          calculatedPrice,
+          diffPercent: diffPercent.toFixed(4)
+        });
+        return signals;
+      }
+    }
+
+    const volatility = this.updateAndMeasureVolatility(currentPrice);
+
     // Optimize order size
     let optimizedAmount = await this.optimizeOrderSize(
       this.config.volumeTargetPerCycle,
@@ -194,6 +231,17 @@ export class SimpleStrategyEngine {
       calculatedPrice
     );
 
+    if (volatility > 0 && this.volatilityUpperPercent > 0) {
+      const scale = this.calculateVolatilityScale(volatility);
+      if (scale < 1) {
+        this.logger.debug('Volatility scale applied to order size', {
+          volatility: volatility.toFixed(5),
+          scale
+        });
+        optimizedAmount *= scale;
+      }
+    }
+
     const utilizationContext = this.getUtilizationContext();
 
     this.logger.debug('Utilization context', {
@@ -407,6 +455,44 @@ export class SimpleStrategyEngine {
     return clamped;
   }
 
+  private updateAndMeasureVolatility(currentPrice: number): number {
+    const now = Date.now();
+    this.priceHistory.push({ price: currentPrice, timestamp: now });
+
+    while (this.priceHistory.length > 0 && now - this.priceHistory[0]!.timestamp > this.volatilityWindowMs) {
+      this.priceHistory.shift();
+    }
+
+    if (this.priceHistory.length < 2) {
+      return 0;
+    }
+
+    const prices = this.priceHistory.map(p => p.price);
+    const mean = prices.reduce((sum, price) => sum + price, 0) / prices.length;
+    if (mean <= 0) {
+      return 0;
+    }
+
+    const variance = prices.reduce((acc, price) => acc + Math.pow(price - mean, 2), 0) / prices.length;
+    const stdDev = Math.sqrt(variance);
+
+    return stdDev / mean;
+  }
+
+  private calculateVolatilityScale(volatility: number): number {
+    if (volatility <= 0 || this.volatilityUpperPercent <= 0) {
+      return 1;
+    }
+
+    if (volatility >= this.volatilityUpperPercent) {
+      return this.minTradeFraction;
+    }
+
+    const fraction = 1 - (volatility / this.volatilityUpperPercent);
+    const scaled = this.minTradeFraction + (1 - this.minTradeFraction) * fraction;
+    return Math.max(this.minTradeFraction, Math.min(1, scaled));
+  }
+
   /**
    * Check if delta rebalancing is needed
    */