learning_ai_invt_trdg/web/src/views/ResearchView.tsx
Saravana Achu Mac ddbffb6cd1 fix(audit-A): repair the 5 critical broken integrations
A1+A2 — CodeStrategyEditor backtest call
  Was: POST /api/backtest with { symbol, strategyCode, mode: 'code' }
  Now: POST /api/backtest/run with { symbols: [s], strategyConfig: {
       type: 'code', language: 'javascript', code } }
  The backend route is /api/backtest/run (not /api/backtest), and
  /api/backtest/run validates `symbols[]` and `strategyConfig`, not the
  ad-hoc fields we were sending. Also unwraps the { success, results }
  envelope the engine returns and surfaces success:false errors.

A3 — VisualRuleBuilder save shape
  Was: hand-rolled fetch to /api/profiles with { name, symbol, strategyType,
       visualRules, description } — backend's saveTradeProfileForUser ignored
       all of that and either 400'd or persisted a half-empty row.
  Now: uses the canonical createTradeProfile() helper from lib/profileApi
       with the documented TradeProfilePayload shape. Visual rules go inside
       strategy_config.{type:'visual', version:1, rules:[...]} so the engine
       can fan out to a visual interpreter without conflicting with the
       existing rule-based engine. Allocated capital + risk pct pulled from
       botState.settings so the profile inherits the user's current sizing.
       is_active defaults false so the user activates explicitly.

A4+A5 — RightPanel.NewsFeed auth + runtime
  Was: raw fetch() to import.meta.env.VITE_TRADING_API_URL with no
       Authorization header → 401 on every render in any environment that
       requires auth, and prod-broken where the runtime resolver is the
       only source of truth for the API base URL.
  Now: uses fetchNews() from lib/marketApi which already carries the
       platform Bearer token and routes through tradingRuntime.tradingApiUrl.
       Adds an error state in the UI for visibility instead of silently
       leaving the panel blank.

Verified: web/ tsc --noEmit passes. No behavioural change to non-affected
code paths (RightPanel portfolio summary, ResearchView other tabs, etc.).

Refs: docs/AUDIT_REDESIGN.md items A1–A5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 06:23:52 -07:00

145 lines
4.9 KiB
TypeScript

import { useState } from 'react';
import { useAppContext } from '../context/AppContext';
import { SignalsTab } from '../tabs/SignalsTab';
import { BacktestTab } from '../tabs/BacktestTab';
import { MyStrategiesTab } from '../tabs/MyStrategiesTab';
import { VisualRuleBuilder, type VisualRule } from '../components/strategy/VisualRuleBuilder';
import { CodeStrategyEditor } from '../components/strategy/CodeStrategyEditor';
import { createTradeProfile } from '../lib/profileApi';
type ResearchTab = 'Strategies' | 'Visual Builder' | 'Code Editor' | 'Signals' | 'Backtesting';
// Sub-tab pill styles
function SubTab({
label, active, onClick,
}: { label: string; active: boolean; onClick: () => void }) {
return (
<button
onClick={onClick}
style={{
padding: '8px 16px',
border: 'none',
borderBottom: active ? '2px solid #2563EB' : '2px solid transparent',
background: 'transparent',
color: active ? '#2563EB' : '#6B7280',
fontSize: 13,
fontWeight: active ? 700 : 500,
cursor: 'pointer',
marginBottom: -1,
transition: 'all 0.15s',
fontFamily: 'inherit',
}}
>
{label}
</button>
);
}
export function ResearchView() {
const { botState, connected, showBacktestTab, isAdmin, activeSymbol } = useAppContext();
const [tab, setTab] = useState<ResearchTab>('Strategies');
const tabs: ResearchTab[] = [
'Strategies',
'Visual Builder',
'Code Editor',
...(isAdmin ? ['Signals' as ResearchTab] : []),
...(showBacktestTab ? ['Backtesting' as ResearchTab] : []),
];
// Save a visual-builder strategy via the canonical createTradeProfile helper.
// Backend `saveTradeProfileForUser` expects the TradeProfilePayload shape:
// { name, symbols, allocated_capital, risk_per_trade_percent, is_active,
// strategy_config: { ... } }
// Visual rules go inside strategy_config.rules so the strategy engine can
// route to the visual interpreter (alongside the existing rule-based engine).
const handleSaveVisualStrategy = async (name: string, rules: VisualRule[]) => {
const fallbackSymbol = Object.keys(botState.symbols)[0] ?? 'SPY';
const symbol = activeSymbol || fallbackSymbol;
const totalCapital = botState.settings?.totalCapital ?? 1000;
const riskPct = botState.settings?.riskPerTrade ?? 1;
await createTradeProfile({
name,
symbols: symbol,
allocated_capital: totalCapital,
risk_per_trade_percent: riskPct,
is_active: false, // user activates explicitly from MyStrategiesTab
strategy_config: {
type: 'visual',
version: 1,
rules: rules.map(r => ({
indicator: r.indicator,
condition: r.condition,
value: r.value,
action: r.action,
quantity: r.quantity,
quantityType: r.quantityType,
})),
},
});
};
return (
<div>
<h2 style={{ fontSize: 22, fontWeight: 800, color: '#111827', margin: '0 0 20px' }}>Research</h2>
<div style={{
display: 'flex', gap: 4, marginBottom: 20,
borderBottom: '1px solid #E5E7EB',
}}>
{tabs.map(t => (
<SubTab key={t} label={t} active={tab === t} onClick={() => setTab(t)} />
))}
</div>
{tab === 'Strategies' && (
<MyStrategiesTab
botState={botState}
alerts={botState.alerts}
previewAsCustomer={false}
/>
)}
{tab === 'Visual Builder' && (
<div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 15, fontWeight: 700, color: '#111827', marginBottom: 4 }}>
Visual Rule Builder
</div>
<div style={{ fontSize: 13, color: '#6B7280' }}>
Build a trading strategy by composing IF/THEN rules. Drag rows to reorder. Click "Save Strategy" to store it.
</div>
</div>
<VisualRuleBuilder
symbol={activeSymbol || Object.keys(botState.symbols)[0] || 'SPY'}
onSave={handleSaveVisualStrategy}
/>
</div>
)}
{tab === 'Code Editor' && (
<div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 15, fontWeight: 700, color: '#111827', marginBottom: 4 }}>
Code Strategy Editor
</div>
<div style={{ fontSize: 13, color: '#6B7280' }}>
Write a custom strategy function in JavaScript. Click "Run Backtest" to test it against historical data.
</div>
</div>
<CodeStrategyEditor symbol={activeSymbol || Object.keys(botState.symbols)[0] || 'SPY'} />
</div>
)}
{tab === 'Signals' && (
<SignalsTab botState={botState} connected={connected} />
)}
{tab === 'Backtesting' && (
<BacktestTab previewAsCustomer={false} />
)}
</div>
);
}