mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-07 03:07:56 +08:00
5cff32e4f2
* feat: add Strategy Studio with multi-timeframe support - Add Strategy Studio page with three-column layout for strategy management - Support multi-timeframe K-line data selection (5m, 15m, 1h, 4h, etc.) - Add GetWithTimeframes() function in market package for fetching multiple timeframes - Add TimeframeSeriesData struct for storing per-timeframe technical indicators - Update formatMarketData() to display all selected timeframes in AI prompt - Add strategy API endpoints for CRUD operations and test run - Integrate real AI test runs with configured AI models - Support custom AI500 and OI Top API URLs from strategy config * docs: add Strategy Studio screenshot to README files * fix: correct strategy-studio.png filename case in README * refactor: remove legacy signal source config and simplify trader creation - Remove signal source configuration from traders page (now handled by strategy) - Remove advanced options (legacy config) from TraderConfigModal - Rename default strategy to "默认山寨策略" with AI500 coin pool URL - Delete SignalSourceModal and SignalSourceWarning components - Clean up related stores, hooks, and page components
328 lines
12 KiB
TypeScript
328 lines
12 KiB
TypeScript
import { Shield, AlertTriangle } from 'lucide-react'
|
|
import type { RiskControlConfig } from '../../types'
|
|
|
|
interface RiskControlEditorProps {
|
|
config: RiskControlConfig
|
|
onChange: (config: RiskControlConfig) => void
|
|
disabled?: boolean
|
|
language: string
|
|
}
|
|
|
|
export function RiskControlEditor({
|
|
config,
|
|
onChange,
|
|
disabled,
|
|
language,
|
|
}: RiskControlEditorProps) {
|
|
const t = (key: string) => {
|
|
const translations: Record<string, Record<string, string>> = {
|
|
positionLimits: { zh: '仓位限制', en: 'Position Limits' },
|
|
maxPositions: { zh: '最大持仓数量', en: 'Max Positions' },
|
|
maxPositionsDesc: { zh: '同时持有的最大币种数量', en: 'Maximum coins held simultaneously' },
|
|
btcEthLeverage: { zh: 'BTC/ETH 最大杠杆', en: 'BTC/ETH Max Leverage' },
|
|
altcoinLeverage: { zh: '山寨币最大杠杆', en: 'Altcoin Max Leverage' },
|
|
riskParameters: { zh: '风险参数', en: 'Risk Parameters' },
|
|
minRiskReward: { zh: '最小风险回报比', en: 'Min Risk/Reward Ratio' },
|
|
minRiskRewardDesc: { zh: '开仓要求的最低盈亏比', en: 'Minimum profit ratio for opening' },
|
|
maxMarginUsage: { zh: '最大保证金使用率', en: 'Max Margin Usage' },
|
|
maxMarginUsageDesc: { zh: '保证金使用率上限', en: 'Maximum margin utilization' },
|
|
maxPositionRatio: { zh: '单币最大仓位比', en: 'Max Position Ratio' },
|
|
maxPositionRatioDesc: { zh: '相对账户净值的倍数', en: 'Multiple of account equity' },
|
|
entryRequirements: { zh: '开仓要求', en: 'Entry Requirements' },
|
|
minPositionSize: { zh: '最小开仓金额', en: 'Min Position Size' },
|
|
minPositionSizeDesc: { zh: 'USDT 最小名义价值', en: 'Minimum notional value in USDT' },
|
|
minConfidence: { zh: '最小信心度', en: 'Min Confidence' },
|
|
minConfidenceDesc: { zh: 'AI 开仓信心度阈值', en: 'AI confidence threshold for entry' },
|
|
}
|
|
return translations[key]?.[language] || key
|
|
}
|
|
|
|
const updateField = <K extends keyof RiskControlConfig>(
|
|
key: K,
|
|
value: RiskControlConfig[K]
|
|
) => {
|
|
if (!disabled) {
|
|
onChange({ ...config, [key]: value })
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Position Limits */}
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Shield className="w-5 h-5" style={{ color: '#F0B90B' }} />
|
|
<h3 className="font-medium" style={{ color: '#EAECEF' }}>
|
|
{t('positionLimits')}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div
|
|
className="p-4 rounded-lg"
|
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
|
>
|
|
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
|
|
{t('maxPositions')}
|
|
</label>
|
|
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
|
{t('maxPositionsDesc')}
|
|
</p>
|
|
<input
|
|
type="number"
|
|
value={config.max_positions}
|
|
onChange={(e) =>
|
|
updateField('max_positions', parseInt(e.target.value) || 3)
|
|
}
|
|
disabled={disabled}
|
|
min={1}
|
|
max={10}
|
|
className="w-full px-3 py-2 rounded"
|
|
style={{
|
|
background: '#1E2329',
|
|
border: '1px solid #2B3139',
|
|
color: '#EAECEF',
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
className="p-4 rounded-lg"
|
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
|
>
|
|
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
|
|
{t('btcEthLeverage')}
|
|
</label>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="range"
|
|
value={config.btc_eth_max_leverage}
|
|
onChange={(e) =>
|
|
updateField('btc_eth_max_leverage', parseInt(e.target.value))
|
|
}
|
|
disabled={disabled}
|
|
min={1}
|
|
max={20}
|
|
className="flex-1 accent-yellow-500"
|
|
/>
|
|
<span
|
|
className="w-12 text-center font-mono"
|
|
style={{ color: '#F0B90B' }}
|
|
>
|
|
{config.btc_eth_max_leverage}x
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="p-4 rounded-lg"
|
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
|
>
|
|
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
|
|
{t('altcoinLeverage')}
|
|
</label>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="range"
|
|
value={config.altcoin_max_leverage}
|
|
onChange={(e) =>
|
|
updateField('altcoin_max_leverage', parseInt(e.target.value))
|
|
}
|
|
disabled={disabled}
|
|
min={1}
|
|
max={20}
|
|
className="flex-1 accent-yellow-500"
|
|
/>
|
|
<span
|
|
className="w-12 text-center font-mono"
|
|
style={{ color: '#F0B90B' }}
|
|
>
|
|
{config.altcoin_max_leverage}x
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Risk Parameters */}
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<AlertTriangle className="w-5 h-5" style={{ color: '#F6465D' }} />
|
|
<h3 className="font-medium" style={{ color: '#EAECEF' }}>
|
|
{t('riskParameters')}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div
|
|
className="p-4 rounded-lg"
|
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
|
>
|
|
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
|
|
{t('minRiskReward')}
|
|
</label>
|
|
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
|
{t('minRiskRewardDesc')}
|
|
</p>
|
|
<div className="flex items-center">
|
|
<span style={{ color: '#848E9C' }}>1:</span>
|
|
<input
|
|
type="number"
|
|
value={config.min_risk_reward_ratio}
|
|
onChange={(e) =>
|
|
updateField('min_risk_reward_ratio', parseFloat(e.target.value) || 3)
|
|
}
|
|
disabled={disabled}
|
|
min={1}
|
|
max={10}
|
|
step={0.5}
|
|
className="w-20 px-3 py-2 rounded ml-2"
|
|
style={{
|
|
background: '#1E2329',
|
|
border: '1px solid #2B3139',
|
|
color: '#EAECEF',
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="p-4 rounded-lg"
|
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
|
>
|
|
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
|
|
{t('maxMarginUsage')}
|
|
</label>
|
|
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
|
{t('maxMarginUsageDesc')}
|
|
</p>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="range"
|
|
value={config.max_margin_usage * 100}
|
|
onChange={(e) =>
|
|
updateField('max_margin_usage', parseInt(e.target.value) / 100)
|
|
}
|
|
disabled={disabled}
|
|
min={10}
|
|
max={100}
|
|
className="flex-1 accent-red-500"
|
|
/>
|
|
<span className="w-12 text-center font-mono" style={{ color: '#F6465D' }}>
|
|
{Math.round(config.max_margin_usage * 100)}%
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="p-4 rounded-lg"
|
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
|
>
|
|
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
|
|
{t('maxPositionRatio')}
|
|
</label>
|
|
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
|
{t('maxPositionRatioDesc')}
|
|
</p>
|
|
<div className="flex items-center">
|
|
<input
|
|
type="number"
|
|
value={config.max_position_ratio}
|
|
onChange={(e) =>
|
|
updateField('max_position_ratio', parseFloat(e.target.value) || 1.5)
|
|
}
|
|
disabled={disabled}
|
|
min={0.5}
|
|
max={5}
|
|
step={0.1}
|
|
className="w-20 px-3 py-2 rounded"
|
|
style={{
|
|
background: '#1E2329',
|
|
border: '1px solid #2B3139',
|
|
color: '#EAECEF',
|
|
}}
|
|
/>
|
|
<span className="ml-2" style={{ color: '#848E9C' }}>
|
|
x
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Entry Requirements */}
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-4">
|
|
<Shield className="w-5 h-5" style={{ color: '#0ECB81' }} />
|
|
<h3 className="font-medium" style={{ color: '#EAECEF' }}>
|
|
{t('entryRequirements')}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div
|
|
className="p-4 rounded-lg"
|
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
|
>
|
|
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
|
|
{t('minPositionSize')}
|
|
</label>
|
|
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
|
{t('minPositionSizeDesc')}
|
|
</p>
|
|
<div className="flex items-center">
|
|
<input
|
|
type="number"
|
|
value={config.min_position_size}
|
|
onChange={(e) =>
|
|
updateField('min_position_size', parseFloat(e.target.value) || 12)
|
|
}
|
|
disabled={disabled}
|
|
min={10}
|
|
max={1000}
|
|
className="w-24 px-3 py-2 rounded"
|
|
style={{
|
|
background: '#1E2329',
|
|
border: '1px solid #2B3139',
|
|
color: '#EAECEF',
|
|
}}
|
|
/>
|
|
<span className="ml-2" style={{ color: '#848E9C' }}>
|
|
USDT
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="p-4 rounded-lg"
|
|
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
|
>
|
|
<label className="block text-sm mb-1" style={{ color: '#EAECEF' }}>
|
|
{t('minConfidence')}
|
|
</label>
|
|
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
|
{t('minConfidenceDesc')}
|
|
</p>
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="range"
|
|
value={config.min_confidence}
|
|
onChange={(e) =>
|
|
updateField('min_confidence', parseInt(e.target.value))
|
|
}
|
|
disabled={disabled}
|
|
min={50}
|
|
max={100}
|
|
className="flex-1 accent-green-500"
|
|
/>
|
|
<span className="w-12 text-center font-mono" style={{ color: '#0ECB81' }}>
|
|
{config.min_confidence}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|