| |
|
|
| import React from 'react'; |
|
|
| export interface StrategyParams { |
| strategy_mode: 'consensus' | 'ema_cross' | 'breakout' | 'patterns' | 'opening_breakout' | 'dynamic'; |
| stop_loss_pct: number; |
| profit_target_pct: number; |
| trailing_stop_mode: 'atr' | 'flat' | 'none'; |
| trailing_stop_atr_mult: number; |
| rsi_threshold_buy: number; |
| risk_per_trade_pct: number; |
| max_position_size_pct: number; |
| position_sizing_mode: 'atr' | 'flat'; |
| commission_per_share: number; |
| slippage_rate: number; |
| market_open_focus: boolean; |
| } |
|
|
| interface StrategySettingsProps { |
| params: StrategyParams; |
| onChange: (newParams: StrategyParams) => void; |
| onReset: () => void; |
| aiAutoPilot: boolean; |
| onToggleAutoPilot: (val: boolean) => void; |
| } |
|
|
| export const StrategySettings: React.FC<StrategySettingsProps> = ({ params, onChange, onReset, aiAutoPilot, onToggleAutoPilot }) => { |
| const handleChange = (key: keyof StrategyParams, value: any) => { |
| onChange({ |
| ...params, |
| [key]: value |
| }); |
| }; |
|
|
| return ( |
| <div className="card" style={{ position: 'relative' }}> |
| |
| {/* AI Auto-Pilot Card */} |
| <div style={{ |
| background: aiAutoPilot ? 'linear-gradient(135deg, rgba(0, 200, 5, 0.12), rgba(0, 150, 5, 0.04))' : 'rgba(255, 255, 255, 0.02)', |
| border: aiAutoPilot ? '1px dashed rgba(0, 200, 5, 0.4)' : '1px solid var(--color-border)', |
| borderRadius: '8px', |
| padding: '12px 16px', |
| marginBottom: '1.25rem', |
| display: 'flex', |
| flexDirection: 'column', |
| gap: '6px', |
| transition: 'all 0.3s ease' |
| }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> |
| <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> |
| <span style={{ fontSize: '1.2rem' }}>🤖</span> |
| <span style={{ fontWeight: 700, fontSize: '0.9rem', color: '#ffffff' }}>AI 智能托管 / 自动调参</span> |
| {aiAutoPilot && ( |
| <span style={{ |
| background: 'rgba(0, 200, 5, 0.15)', |
| color: 'var(--color-green)', |
| padding: '2px 8px', |
| borderRadius: '12px', |
| fontSize: '0.75rem', |
| fontWeight: 700, |
| display: 'inline-flex', |
| alignItems: 'center', |
| gap: '4px' |
| }}> |
| <span style={{ |
| width: '6px', |
| height: '6px', |
| borderRadius: '50%', |
| backgroundColor: 'var(--color-green)', |
| animation: 'pulse 1.5s infinite', |
| display: 'inline-block' |
| }}></span> |
| 已激活 |
| </span> |
| )} |
| </div> |
| |
| {/* Switch Switch */} |
| <label style={{ display: 'inline-flex', alignItems: 'center', cursor: 'pointer' }}> |
| <input |
| type="checkbox" |
| checked={aiAutoPilot} |
| onChange={(e) => onToggleAutoPilot(e.target.checked)} |
| style={{ display: 'none' }} |
| /> |
| <div style={{ |
| width: '40px', |
| height: '20px', |
| backgroundColor: aiAutoPilot ? 'var(--color-green)' : '#2c2c2e', |
| borderRadius: '10px', |
| position: 'relative', |
| transition: 'background-color 0.2s', |
| border: '1px solid var(--color-border)' |
| }}> |
| <div style={{ |
| width: '16px', |
| height: '16px', |
| backgroundColor: aiAutoPilot ? '#000000' : '#ffffff', |
| borderRadius: '50%', |
| position: 'absolute', |
| top: '1px', |
| left: aiAutoPilot ? '21px' : '1px', |
| transition: 'left 0.2s' |
| }}></div> |
| </div> |
| </label> |
| </div> |
| <p style={{ color: 'var(--color-text-secondary)', fontSize: '0.75rem', margin: 0, lineHeight: 1.4 }}> |
| {aiAutoPilot |
| ? "机器学习调优中。算法自动分析开盘高频阻力与相对成交量,调配仓位与止损,保护本金并稳健止盈。" |
| : "开启后,系统将自动基于近期历史特征运行微型 Walk-Forward 调优,代替手动参数设置。" |
| } |
| </p> |
| </div> |
| |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1.25rem' }}> |
| <h3 className="card-title" style={{ margin: 0 }}>手动参数与风控 (Strategy Parameters)</h3> |
| |
| <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}> |
| {/* 开盘突击模式 Checkbox */} |
| <label style={{ display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '0.85rem', color: '#ffffff', cursor: aiAutoPilot ? 'not-allowed' : 'pointer', userSelect: 'none' }}> |
| <input |
| type="checkbox" |
| checked={params.market_open_focus} |
| disabled={aiAutoPilot} |
| onChange={(e) => handleChange('market_open_focus', e.target.checked)} |
| style={{ |
| accentColor: 'var(--color-green)', |
| width: '14px', |
| height: '14px', |
| cursor: aiAutoPilot ? 'not-allowed' : 'pointer' |
| }} |
| /> |
| 🌅 开盘突击 |
| </label> |
| |
| <button |
| onClick={onReset} |
| disabled={aiAutoPilot} |
| style={{ |
| background: '#2c2c2e', |
| border: 'none', |
| color: '#ffffff', |
| padding: '4px 10px', |
| borderRadius: '4px', |
| fontSize: '0.8rem', |
| cursor: aiAutoPilot ? 'not-allowed' : 'pointer', |
| fontWeight: 600, |
| opacity: aiAutoPilot ? 0.5 : 1 |
| }} |
| > |
| 重置 |
| </button> |
| </div> |
| </div> |
| |
| <div style={{ |
| display: 'flex', |
| flexDirection: 'column', |
| gap: '1rem', |
| opacity: aiAutoPilot ? 0.35 : 1, |
| pointerEvents: aiAutoPilot ? 'none' : 'auto', |
| transition: 'all 0.3s ease', |
| position: 'relative' |
| }}> |
| {aiAutoPilot && ( |
| <div style={{ |
| position: 'absolute', |
| top: 0, |
| left: 0, |
| right: 0, |
| bottom: 0, |
| zIndex: 10, |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| background: 'transparent' |
| }}> |
| <div style={{ |
| background: 'rgba(0, 0, 0, 0.8)', |
| border: '1px solid rgba(0, 200, 5, 0.3)', |
| borderRadius: '8px', |
| padding: '8px 16px', |
| fontSize: '0.8rem', |
| fontWeight: 700, |
| color: 'var(--color-green)', |
| boxShadow: '0 4px 12px rgba(0,0,0,0.5)' |
| }}> |
| 🔒 AI 智能托管进行中 |
| </div> |
| </div> |
| )} |
| |
| {/* 策略选择 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <label style={{ fontSize: '0.85rem', color: 'var(--color-text-secondary)', fontWeight: 600 }}>策略运行模式 (Strategy Mode)</label> |
| <select |
| value={params.strategy_mode} |
| onChange={(e) => handleChange('strategy_mode', e.target.value)} |
| style={{ |
| background: '#2c2c2e', |
| color: '#ffffff', |
| border: '1px solid var(--color-border)', |
| borderRadius: '6px', |
| padding: '8px', |
| fontWeight: 600 |
| }} |
| > |
| <option value="opening_breakout">🌅 Market Open Breakout (开盘突击区间突破 - 推荐)</option> |
| <option value="consensus">Combo Consensus (共振共识策略)</option> |
| <option value="dynamic">Regime Dynamic Router (自适应市场路由)</option> |
| <option value="ema_cross">EMA Golden/Dead Cross (日内均线交叉)</option> |
| <option value="breakout">Intraday Levels Breakout (阻力位突破)</option> |
| <option value="patterns">K-Line Patterns (M顶/W底形态反转)</option> |
| </select> |
| </div> |
| |
| <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.5rem' }}> |
| {/* 左列:止损止盈 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> |
| |
| {/* 止损设置 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem' }}> |
| <span style={{ color: 'var(--color-text-secondary)', fontWeight: 600 }}>最大硬止损比例</span> |
| <span style={{ color: 'var(--color-red)', fontWeight: 700 }}>{(params.stop_loss_pct * 100).toFixed(1)}%</span> |
| </div> |
| <input |
| type="range" |
| min="0.005" |
| max="0.05" |
| step="0.005" |
| value={params.stop_loss_pct} |
| onChange={(e) => handleChange('stop_loss_pct', parseFloat(e.target.value))} |
| style={{ accentColor: 'var(--color-green)' }} |
| /> |
| </div> |
| |
| {/* 止盈设置 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem' }}> |
| <span style={{ color: 'var(--color-text-secondary)', fontWeight: 600 }}>硬性止盈目标</span> |
| <span style={{ color: 'var(--color-green)', fontWeight: 700 }}>{(params.profit_target_pct * 100).toFixed(1)}%</span> |
| </div> |
| <input |
| type="range" |
| min="0.005" |
| max="0.10" |
| step="0.005" |
| value={params.profit_target_pct} |
| onChange={(e) => handleChange('profit_target_pct', parseFloat(e.target.value))} |
| style={{ accentColor: 'var(--color-green)' }} |
| /> |
| </div> |
| |
| {/* RSI 阈值 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem' }}> |
| <span style={{ color: 'var(--color-text-secondary)', fontWeight: 600 }}>买入超买 RSI 过滤</span> |
| <span style={{ fontWeight: 700 }}>RSI < {params.rsi_threshold_buy}</span> |
| </div> |
| <input |
| type="range" |
| min="50" |
| max="80" |
| step="1" |
| value={params.rsi_threshold_buy} |
| onChange={(e) => handleChange('rsi_threshold_buy', parseInt(e.target.value))} |
| style={{ accentColor: 'var(--color-green)' }} |
| /> |
| </div> |
| |
| </div> |
| |
| {/* 右列:追踪止损与仓位控制 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> |
| |
| {/* 追踪止损模式 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <label style={{ fontSize: '0.85rem', color: 'var(--color-text-secondary)', fontWeight: 600 }}>移动追踪止损模式 (Trailing Stop)</label> |
| <select |
| value={params.trailing_stop_mode} |
| onChange={(e) => handleChange('trailing_stop_mode', e.target.value)} |
| style={{ |
| background: '#2c2c2e', |
| color: '#ffffff', |
| border: '1px solid var(--color-border)', |
| borderRadius: '6px', |
| padding: '6px', |
| fontWeight: 600 |
| }} |
| > |
| <option value="atr">ATR 动态波幅追踪 (推荐)</option> |
| <option value="flat">最高点固定百分比回撤</option> |
| <option value="none">无追踪止损 (只用硬止损)</option> |
| </select> |
| </div> |
| |
| {/* ATR 乘数 */} |
| {params.trailing_stop_mode === 'atr' && ( |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem' }}> |
| <span style={{ color: 'var(--color-text-secondary)', fontWeight: 600 }}>ATR 追踪宽度乘数</span> |
| <span style={{ color: '#ff9800', fontWeight: 700 }}>{params.trailing_stop_atr_mult.toFixed(1)} * ATR</span> |
| </div> |
| <input |
| type="range" |
| min="1.0" |
| max="5.0" |
| step="0.1" |
| value={params.trailing_stop_atr_mult} |
| onChange={(e) => handleChange('trailing_stop_atr_mult', parseFloat(e.target.value))} |
| style={{ accentColor: 'var(--color-green)' }} |
| /> |
| </div> |
| )} |
| |
| {/* 仓位管理模式 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <label style={{ fontSize: '0.85rem', color: 'var(--color-text-secondary)', fontWeight: 600 }}>仓位大小控制 (Sizing Mode)</label> |
| <select |
| value={params.position_sizing_mode} |
| onChange={(e) => handleChange('position_sizing_mode', e.target.value)} |
| style={{ |
| background: '#2c2c2e', |
| color: '#ffffff', |
| border: '1px solid var(--color-border)', |
| borderRadius: '6px', |
| padding: '6px', |
| fontWeight: 600 |
| }} |
| > |
| <option value="atr">ATR 波动率风险对齐仓位 (专业级)</option> |
| <option value="flat">固定单只上限比例 (默认 50%)</option> |
| </select> |
| </div> |
| |
| {/* 风险占比 (只在 ATR 模式展示) */} |
| {params.position_sizing_mode === 'atr' && ( |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem' }}> |
| <span style={{ color: 'var(--color-text-secondary)', fontWeight: 600 }}>单笔交易允许最大亏损比例</span> |
| <span style={{ color: 'var(--color-red)', fontWeight: 700 }}>{(params.risk_per_trade_pct * 100).toFixed(1)}%</span> |
| </div> |
| <input |
| type="range" |
| min="0.005" |
| max="0.03" |
| step="0.005" |
| value={params.risk_per_trade_pct} |
| onChange={(e) => handleChange('risk_per_trade_pct', parseFloat(e.target.value))} |
| style={{ accentColor: 'var(--color-green)' }} |
| /> |
| </div> |
| )} |
| |
| {/* 最大持仓比例上限 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem' }}> |
| <span style={{ color: 'var(--color-text-secondary)', fontWeight: 600 }}>单只股票最大持仓资金比</span> |
| <span style={{ fontWeight: 700 }}>{(params.max_position_size_pct * 100).toFixed(0)}%</span> |
| </div> |
| <input |
| type="range" |
| min="0.10" |
| max="1.00" |
| step="0.05" |
| value={params.max_position_size_pct} |
| onChange={(e) => handleChange('max_position_size_pct', parseFloat(e.target.value))} |
| style={{ accentColor: 'var(--color-green)' }} |
| /> |
| </div> |
| |
| </div> |
| </div> |
| |
| {/* 交易摩擦与费用设置 */} |
| <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.5rem', borderTop: '1px solid var(--color-border)', paddingTop: '1rem', marginTop: '0.5rem' }}> |
| |
| {/* 左列:佣金费率 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem' }}> |
| <span style={{ color: 'var(--color-text-secondary)', fontWeight: 600 }}>每股交易佣金 (Commission per Share)</span> |
| <span style={{ color: params.commission_per_share === 0 ? 'var(--color-green)' : '#ffffff', fontWeight: 700 }}> |
| {params.commission_per_share === 0 ? '免佣金 ($0.00)' : `$${params.commission_per_share.toFixed(3)}`} |
| </span> |
| </div> |
| <input |
| type="range" |
| min="0.00" |
| max="0.015" |
| step="0.001" |
| value={params.commission_per_share} |
| onChange={(e) => handleChange('commission_per_share', parseFloat(e.target.value))} |
| style={{ accentColor: 'var(--color-green)' }} |
| /> |
| </div> |
| |
| {/* 右列:滑点损耗 */} |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.85rem' }}> |
| <span style={{ color: 'var(--color-text-secondary)', fontWeight: 600 }}>滑点损耗比例 (Slippage Rate)</span> |
| <span style={{ color: params.slippage_rate === 0 ? 'var(--color-green)' : '#ffffff', fontWeight: 700 }}> |
| {params.slippage_rate === 0 ? '无滑点损耗 (0%)' : `${(params.slippage_rate * 100).toFixed(2)}%`} |
| </span> |
| </div> |
| <input |
| type="range" |
| min="0.00" |
| max="0.0010" |
| step="0.0001" |
| value={params.slippage_rate} |
| onChange={(e) => handleChange('slippage_rate', parseFloat(e.target.value))} |
| style={{ accentColor: 'var(--color-green)' }} |
| /> |
| </div> |
| |
| </div> |
| |
| </div> |
| </div> |
| ); |
| }; |
|
|