feat: trading bot MVP — ICT Order Block + Liquidity Sweep strategy
Full-stack trading bot with: - FastAPI backend with ICT strategy (Order Block + Liquidity Sweep detection) - Backtester engine with rolling window, spread simulation, and performance metrics - Hybrid market data service (yfinance + TwelveData with rate limiting + SQLite cache) - Simulated exchange for paper trading - React/TypeScript frontend with TradingView lightweight-charts v5 - Live dashboard with candlestick chart, OHLC legend, trade markers - Backtest page with configurable parameters, equity curve, and trade table - WebSocket support for real-time updates - Bot runner with asyncio loop for automated trading Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
116
frontend/src/components/Backtest/BacktestForm.tsx
Normal file
116
frontend/src/components/Backtest/BacktestForm.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { useState } from 'react'
|
||||
import { FlaskConical, Loader2 } from 'lucide-react'
|
||||
import type { BacktestResult } from '../../lib/api'
|
||||
import { runBacktest } from '../../lib/api'
|
||||
|
||||
interface Props {
|
||||
onResult: (result: BacktestResult) => void
|
||||
}
|
||||
|
||||
const INSTRUMENTS = [
|
||||
'EUR_USD', 'GBP_USD', 'USD_JPY', 'USD_CHF', 'AUD_USD', 'USD_CAD',
|
||||
'GBP_JPY', 'EUR_JPY', 'SPX500_USD', 'NAS100_USD', 'XAU_USD',
|
||||
]
|
||||
|
||||
const GRANULARITIES = ['M15', 'M30', 'H1', 'H4', 'D']
|
||||
|
||||
export default function BacktestForm({ onResult }: Props) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const [form, setForm] = useState({
|
||||
instrument: 'EUR_USD',
|
||||
granularity: 'H1',
|
||||
candle_count: 500,
|
||||
initial_balance: 10000,
|
||||
risk_percent: 1.0,
|
||||
rr_ratio: 2.0,
|
||||
swing_strength: 5,
|
||||
liquidity_tolerance_pips: 2.0,
|
||||
spread_pips: 1.5,
|
||||
})
|
||||
|
||||
const handleChange = (key: string, value: string | number) => {
|
||||
setForm((prev) => ({ ...prev, [key]: value }))
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const result = await runBacktest(form)
|
||||
onResult(result)
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : 'Erreur inconnue'
|
||||
setError(msg)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const Field = ({
|
||||
label, name, type = 'number', options
|
||||
}: {
|
||||
label: string; name: string; type?: string; options?: string[]
|
||||
}) => (
|
||||
<div>
|
||||
<label className="block text-xs text-[#64748b] mb-1">{label}</label>
|
||||
{options ? (
|
||||
<select
|
||||
value={form[name as keyof typeof form]}
|
||||
onChange={(e) => handleChange(name, e.target.value)}
|
||||
className="w-full bg-[#0f1117] border border-[#2a2d3e] rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:border-[#6366f1]"
|
||||
>
|
||||
{options.map((o) => <option key={o} value={o}>{o}</option>)}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
type={type}
|
||||
step="any"
|
||||
value={form[name as keyof typeof form]}
|
||||
onChange={(e) => handleChange(name, parseFloat(e.target.value) || 0)}
|
||||
className="w-full bg-[#0f1117] border border-[#2a2d3e] rounded-lg px-3 py-2 text-sm text-white focus:outline-none focus:border-[#6366f1]"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="bg-[#1a1d27] border border-[#2a2d3e] rounded-xl p-5 space-y-4">
|
||||
<h2 className="text-sm font-semibold text-[#64748b] uppercase tracking-wider mb-4">
|
||||
Paramètres du backtest
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Field label="Instrument" name="instrument" options={INSTRUMENTS} />
|
||||
<Field label="Timeframe" name="granularity" options={GRANULARITIES} />
|
||||
<Field label="Nombre de bougies" name="candle_count" />
|
||||
<Field label="Capital initial ($)" name="initial_balance" />
|
||||
<Field label="Risque par trade (%)" name="risk_percent" />
|
||||
<Field label="Ratio R:R" name="rr_ratio" />
|
||||
<Field label="Swing strength" name="swing_strength" />
|
||||
<Field label="Tolérance liquidité (pips)" name="liquidity_tolerance_pips" />
|
||||
<Field label="Spread (pips)" name="spread_pips" />
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="text-sm text-[#ef5350] bg-[#ef535020] border border-[#ef535033] rounded-lg px-3 py-2">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full flex items-center justify-center gap-2 py-2.5 bg-[#6366f1] hover:bg-[#4f46e5] text-white text-sm font-medium rounded-lg transition-colors disabled:opacity-60"
|
||||
>
|
||||
{loading ? (
|
||||
<><Loader2 size={16} className="animate-spin" /> Backtesting...</>
|
||||
) : (
|
||||
<><FlaskConical size={16} /> Lancer le backtest</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user