refactor(ui): migrate visual strategy controls
This commit is contained in:
parent
a622326be6
commit
bb4efc2b0d
@ -19,8 +19,8 @@ import {
|
|||||||
} from '@dnd-kit/sortable';
|
} from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { GripVertical, Plus, Trash2, Save, Play } from 'lucide-react';
|
import { GripVertical, Plus, Trash2, Save, Play } from 'lucide-react';
|
||||||
import { Button } from '../ui/button';
|
|
||||||
import { Card, CardContent } from '../ui/card';
|
import { Card, CardContent } from '../ui/card';
|
||||||
|
import { Button, IconButton, Input, Select } from '../ui/Primitives';
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
export type Indicator = 'RSI' | 'MACD' | 'EMA_50' | 'EMA_200' | 'Price' | 'Volume';
|
export type Indicator = 'RSI' | 'MACD' | 'EMA_50' | 'EMA_200' | 'Price' | 'Volume';
|
||||||
@ -130,29 +130,35 @@ function RuleCard({
|
|||||||
<span style={{ fontSize: 12, fontWeight: 600, color: '#374151' }}>IF</span>
|
<span style={{ fontSize: 12, fontWeight: 600, color: '#374151' }}>IF</span>
|
||||||
|
|
||||||
{/* Indicator */}
|
{/* Indicator */}
|
||||||
<select value={rule.indicator} style={sel}
|
<Select
|
||||||
|
value={rule.indicator}
|
||||||
|
style={sel}
|
||||||
|
controlSize="sm"
|
||||||
|
variant="surface"
|
||||||
|
options={(Object.keys(INDICATOR_LABELS) as Indicator[]).map(k => ({ value: k, label: INDICATOR_LABELS[k] }))}
|
||||||
onChange={e => onChange(rule.id, {
|
onChange={e => onChange(rule.id, {
|
||||||
indicator: e.target.value as Indicator,
|
indicator: e.target.value as Indicator,
|
||||||
value: INDICATOR_DEFAULTS[e.target.value as Indicator],
|
value: INDICATOR_DEFAULTS[e.target.value as Indicator],
|
||||||
})}>
|
})}
|
||||||
{(Object.keys(INDICATOR_LABELS) as Indicator[]).map(k => (
|
/>
|
||||||
<option key={k} value={k}>{INDICATOR_LABELS[k]}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
{/* Condition */}
|
{/* Condition */}
|
||||||
<select value={rule.condition} style={sel}
|
<Select
|
||||||
onChange={e => onChange(rule.id, { condition: e.target.value as Condition })}>
|
value={rule.condition}
|
||||||
{(Object.keys(CONDITION_LABELS) as Condition[]).map(k => (
|
style={sel}
|
||||||
<option key={k} value={k}>{CONDITION_LABELS[k]}</option>
|
controlSize="sm"
|
||||||
))}
|
variant="surface"
|
||||||
</select>
|
options={(Object.keys(CONDITION_LABELS) as Condition[]).map(k => ({ value: k, label: CONDITION_LABELS[k] }))}
|
||||||
|
onChange={e => onChange(rule.id, { condition: e.target.value as Condition })}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Value */}
|
{/* Value */}
|
||||||
<input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={rule.value}
|
value={rule.value}
|
||||||
style={numInp}
|
style={numInp}
|
||||||
|
controlSize="sm"
|
||||||
|
variant="surface"
|
||||||
onChange={e => onChange(rule.id, { value: parseFloat(e.target.value) || 0 })}
|
onChange={e => onChange(rule.id, { value: parseFloat(e.target.value) || 0 })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -160,36 +166,51 @@ function RuleCard({
|
|||||||
<span style={{ fontSize: 12, fontWeight: 600, color: '#374151' }}>→</span>
|
<span style={{ fontSize: 12, fontWeight: 600, color: '#374151' }}>→</span>
|
||||||
|
|
||||||
{/* Action */}
|
{/* Action */}
|
||||||
<select value={rule.action} style={{
|
<Select value={rule.action} style={{
|
||||||
...sel,
|
...sel,
|
||||||
color: rule.action === 'BUY' ? '#16A34A' : '#DC2626',
|
color: rule.action === 'BUY' ? '#16A34A' : '#DC2626',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
background: rule.action === 'BUY' ? '#F0FDF4' : '#FEF2F2',
|
background: rule.action === 'BUY' ? '#F0FDF4' : '#FEF2F2',
|
||||||
border: `1px solid ${rule.action === 'BUY' ? '#86EFAC' : '#FCA5A5'}`,
|
border: `1px solid ${rule.action === 'BUY' ? '#86EFAC' : '#FCA5A5'}`,
|
||||||
}}
|
}}
|
||||||
onChange={e => onChange(rule.id, { action: e.target.value as TradeAction })}>
|
controlSize="sm"
|
||||||
<option value="BUY">BUY</option>
|
variant="surface"
|
||||||
<option value="SELL">SELL</option>
|
options={[
|
||||||
</select>
|
{ value: 'BUY', label: 'BUY' },
|
||||||
|
{ value: 'SELL', label: 'SELL' },
|
||||||
|
]}
|
||||||
|
onChange={e => onChange(rule.id, { action: e.target.value as TradeAction })}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Quantity */}
|
{/* Quantity */}
|
||||||
<input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
value={rule.quantity}
|
value={rule.quantity}
|
||||||
style={numInp}
|
style={numInp}
|
||||||
min={1}
|
min={1}
|
||||||
|
controlSize="sm"
|
||||||
|
variant="surface"
|
||||||
onChange={e => onChange(rule.id, { quantity: parseFloat(e.target.value) || 1 })}
|
onChange={e => onChange(rule.id, { quantity: parseFloat(e.target.value) || 1 })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Qty type */}
|
{/* Qty type */}
|
||||||
<select value={rule.quantityType} style={sel}
|
<Select
|
||||||
onChange={e => onChange(rule.id, { quantityType: e.target.value as QtyType })}>
|
value={rule.quantityType}
|
||||||
<option value="shares">shares</option>
|
style={sel}
|
||||||
<option value="percent">% of capital</option>
|
controlSize="sm"
|
||||||
</select>
|
variant="surface"
|
||||||
|
options={[
|
||||||
|
{ value: 'shares', label: 'shares' },
|
||||||
|
{ value: 'percent', label: '% of capital' },
|
||||||
|
]}
|
||||||
|
onChange={e => onChange(rule.id, { quantityType: e.target.value as QtyType })}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Delete */}
|
{/* Delete */}
|
||||||
<button
|
<IconButton
|
||||||
|
type="button"
|
||||||
|
label="Remove rule"
|
||||||
|
icon={<Trash2 size={14} />}
|
||||||
onClick={() => onDelete(rule.id)}
|
onClick={() => onDelete(rule.id)}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 'auto', background: 'none', border: 'none',
|
marginLeft: 'auto', background: 'none', border: 'none',
|
||||||
@ -197,10 +218,7 @@ function RuleCard({
|
|||||||
display: 'flex', alignItems: 'center',
|
display: 'flex', alignItems: 'center',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
title="Remove rule"
|
/>
|
||||||
>
|
|
||||||
<Trash2 size={14} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -295,9 +313,11 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) {
|
|||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontSize: 11, color: 'var(--muted-foreground)', fontWeight: 500, marginBottom: 3 }}>Strategy name</div>
|
<div style={{ fontSize: 11, color: 'var(--muted-foreground)', fontWeight: 500, marginBottom: 3 }}>Strategy name</div>
|
||||||
<input
|
<Input
|
||||||
value={name}
|
value={name}
|
||||||
onChange={e => setName(e.target.value)}
|
onChange={e => setName(e.target.value)}
|
||||||
|
controlSize="sm"
|
||||||
|
variant="surface"
|
||||||
style={{
|
style={{
|
||||||
border: '1px solid var(--border)', borderRadius: 12,
|
border: '1px solid var(--border)', borderRadius: 12,
|
||||||
padding: '7px 12px', fontSize: 14, fontWeight: 600,
|
padding: '7px 12px', fontSize: 14, fontWeight: 600,
|
||||||
@ -362,8 +382,11 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) {
|
|||||||
</DndContext>
|
</DndContext>
|
||||||
|
|
||||||
{/* Add rule */}
|
{/* Add rule */}
|
||||||
<button
|
<Button
|
||||||
|
type="button"
|
||||||
onClick={() => setRules(prev => [...prev, makeRule()])}
|
onClick={() => setRules(prev => [...prev, makeRule()])}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
style={{
|
style={{
|
||||||
display: 'flex', alignItems: 'center', gap: 7,
|
display: 'flex', alignItems: 'center', gap: 7,
|
||||||
width: '100%', padding: '10px 0', border: '1px dashed var(--border-strong)',
|
width: '100%', padding: '10px 0', border: '1px dashed var(--border-strong)',
|
||||||
@ -373,7 +396,7 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus size={15} /> Add Rule
|
<Plus size={15} /> Add Rule
|
||||||
</button>
|
</Button>
|
||||||
|
|
||||||
{/* Rule summary */}
|
{/* Rule summary */}
|
||||||
{rules.length > 0 && (
|
{rules.length > 0 && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user