fix(E1): lazy-load code strategy editor
This commit is contained in:
parent
e5bda5ade9
commit
8a8c313ee8
@ -49,7 +49,7 @@ describe('CodeStrategyEditor save behavior', () => {
|
|||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
render(<CodeStrategyEditor symbol="AAPL" allocatedCapital={2500} riskPerTradePercent={2} />);
|
render(<CodeStrategyEditor symbol="AAPL" allocatedCapital={2500} riskPerTradePercent={2} />);
|
||||||
|
|
||||||
fireEvent.change(screen.getByLabelText('strategy code'), {
|
fireEvent.change(await screen.findByLabelText('strategy code'), {
|
||||||
target: { value: 'function strategy() { return { signal: "HOLD" }; }' },
|
target: { value: 'function strategy() { return { signal: "HOLD" }; }' },
|
||||||
});
|
});
|
||||||
await user.click(screen.getByRole('button', { name: /^save$/i }));
|
await user.click(screen.getByRole('button', { name: /^save$/i }));
|
||||||
@ -99,7 +99,7 @@ describe('CodeStrategyEditor save behavior', () => {
|
|||||||
it('saves with Cmd/Ctrl-S while focused in the editor', async () => {
|
it('saves with Cmd/Ctrl-S while focused in the editor', async () => {
|
||||||
render(<CodeStrategyEditor symbol="TSLA" />);
|
render(<CodeStrategyEditor symbol="TSLA" />);
|
||||||
|
|
||||||
fireEvent.keyDown(screen.getByLabelText('strategy code'), { key: 's', metaKey: true });
|
fireEvent.keyDown(await screen.findByLabelText('strategy code'), { key: 's', metaKey: true });
|
||||||
|
|
||||||
await waitFor(() => expect(createTradeProfileMock).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(createTradeProfileMock).toHaveBeenCalledTimes(1));
|
||||||
});
|
});
|
||||||
@ -112,7 +112,7 @@ describe('CodeStrategyEditor save behavior', () => {
|
|||||||
vi.stubGlobal('fetch', fetchMock);
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
|
||||||
render(<CodeStrategyEditor symbol="TSLA" />);
|
render(<CodeStrategyEditor symbol="TSLA" />);
|
||||||
fireEvent.keyDown(screen.getByLabelText('strategy code'), { key: 'Enter', metaKey: true });
|
fireEvent.keyDown(await screen.findByLabelText('strategy code'), { key: 'Enter', metaKey: true });
|
||||||
|
|
||||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
|
||||||
expect(String(fetchMock.mock.calls[0][0])).toBe('https://trading.test/api/backtest/run');
|
expect(String(fetchMock.mock.calls[0][0])).toBe('https://trading.test/api/backtest/run');
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
* Monaco-based code strategy editor.
|
* Monaco-based code strategy editor.
|
||||||
* Users write a JS strategy function; "Run Backtest" posts it to /api/backtest.
|
* Users write a JS strategy function; "Run Backtest" posts it to /api/backtest.
|
||||||
*/
|
*/
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { Suspense, lazy, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import Editor from '@monaco-editor/react';
|
|
||||||
import { Play, Save, Copy, RotateCcw } from 'lucide-react';
|
import { Play, Save, Copy, RotateCcw } from 'lucide-react';
|
||||||
import { getPlatformAccessToken } from '../../lib/authSession';
|
import { getPlatformAccessToken } from '../../lib/authSession';
|
||||||
import { createTradeProfile } from '../../lib/profileApi';
|
import { createTradeProfile } from '../../lib/profileApi';
|
||||||
@ -40,6 +39,8 @@ function strategy({ symbol, price, rsi, ema50, ema200, macd, volume }) {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const MonacoEditor = lazy(() => import('@monaco-editor/react'));
|
||||||
|
|
||||||
interface BacktestResult {
|
interface BacktestResult {
|
||||||
trades?: number;
|
trades?: number;
|
||||||
winRate?: number;
|
winRate?: number;
|
||||||
@ -231,29 +232,31 @@ export function CodeStrategyEditor({
|
|||||||
|
|
||||||
{/* Monaco editor */}
|
{/* Monaco editor */}
|
||||||
<div style={{ border: '1px solid #E5E7EB', borderRadius: 10, overflow: 'hidden' }}>
|
<div style={{ border: '1px solid #E5E7EB', borderRadius: 10, overflow: 'hidden' }}>
|
||||||
<Editor
|
<Suspense fallback={<CodeEditorFallback />}>
|
||||||
height="380px"
|
<MonacoEditor
|
||||||
defaultLanguage="javascript"
|
height="380px"
|
||||||
value={code}
|
defaultLanguage="javascript"
|
||||||
onChange={v => {
|
value={code}
|
||||||
setCode(v ?? '');
|
onChange={v => {
|
||||||
setSaved(false);
|
setCode(v ?? '');
|
||||||
}}
|
setSaved(false);
|
||||||
theme="light"
|
}}
|
||||||
options={{
|
theme="light"
|
||||||
fontSize: 13,
|
options={{
|
||||||
minimap: { enabled: false },
|
fontSize: 13,
|
||||||
scrollBeyondLastLine: false,
|
minimap: { enabled: false },
|
||||||
wordWrap: 'on',
|
scrollBeyondLastLine: false,
|
||||||
lineNumbers: 'on',
|
wordWrap: 'on',
|
||||||
renderLineHighlight: 'gutter',
|
lineNumbers: 'on',
|
||||||
padding: { top: 12, bottom: 12 },
|
renderLineHighlight: 'gutter',
|
||||||
fontFamily: '"Fira Code", "Cascadia Code", "Consolas", monospace',
|
padding: { top: 12, bottom: 12 },
|
||||||
fontLigatures: true,
|
fontFamily: '"Fira Code", "Cascadia Code", "Consolas", monospace',
|
||||||
tabSize: 2,
|
fontLigatures: true,
|
||||||
automaticLayout: true,
|
tabSize: 2,
|
||||||
}}
|
automaticLayout: true,
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Error */}
|
{/* Error */}
|
||||||
@ -332,6 +335,27 @@ export function CodeStrategyEditor({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CodeEditorFallback() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="status"
|
||||||
|
aria-label="Loading code editor"
|
||||||
|
style={{
|
||||||
|
height: 380,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: 'linear-gradient(135deg, #F8FAFC, #EEF2FF)',
|
||||||
|
color: '#4B5563',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading code editor…
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function toolBtn(bg: string, color: string, border: string): React.CSSProperties {
|
function toolBtn(bg: string, color: string, border: string): React.CSSProperties {
|
||||||
return {
|
return {
|
||||||
display: 'flex', alignItems: 'center', gap: 5,
|
display: 'flex', alignItems: 'center', gap: 5,
|
||||||
|
|||||||
@ -1,15 +1,18 @@
|
|||||||
import { useState } from 'react';
|
import { Suspense, lazy, useState } from 'react';
|
||||||
import { useAppContext } from '../context/AppContext';
|
import { useAppContext } from '../context/AppContext';
|
||||||
import { SignalsTab } from '../tabs/SignalsTab';
|
import { SignalsTab } from '../tabs/SignalsTab';
|
||||||
import { BacktestTab } from '../tabs/BacktestTab';
|
import { BacktestTab } from '../tabs/BacktestTab';
|
||||||
import { MyStrategiesTab } from '../tabs/MyStrategiesTab';
|
import { MyStrategiesTab } from '../tabs/MyStrategiesTab';
|
||||||
import { VisualRuleBuilder, type VisualRule } from '../components/strategy/VisualRuleBuilder';
|
import { VisualRuleBuilder, type VisualRule } from '../components/strategy/VisualRuleBuilder';
|
||||||
import { CodeStrategyEditor } from '../components/strategy/CodeStrategyEditor';
|
|
||||||
import { createTradeProfile } from '../lib/profileApi';
|
import { createTradeProfile } from '../lib/profileApi';
|
||||||
import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel';
|
import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel';
|
||||||
|
|
||||||
type ResearchTab = 'Strategies' | 'Visual Builder' | 'Code Editor' | 'Signals' | 'Backtesting';
|
type ResearchTab = 'Strategies' | 'Visual Builder' | 'Code Editor' | 'Signals' | 'Backtesting';
|
||||||
|
|
||||||
|
const CodeStrategyEditor = lazy(() =>
|
||||||
|
import('../components/strategy/CodeStrategyEditor').then(module => ({ default: module.CodeStrategyEditor })),
|
||||||
|
);
|
||||||
|
|
||||||
function buildVisualStrategyConfig(rules: VisualRule[]) {
|
function buildVisualStrategyConfig(rules: VisualRule[]) {
|
||||||
return {
|
return {
|
||||||
type: 'visual',
|
type: 'visual',
|
||||||
@ -148,11 +151,13 @@ export function ResearchView() {
|
|||||||
Write a custom strategy function in JavaScript. Click "Run Backtest" to test it against historical data.
|
Write a custom strategy function in JavaScript. Click "Run Backtest" to test it against historical data.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CodeStrategyEditor
|
<Suspense fallback={<CodeStrategyEditorFallback />}>
|
||||||
symbol={activeSymbol || Object.keys(botState.symbols)[0] || 'SPY'}
|
<CodeStrategyEditor
|
||||||
allocatedCapital={botState.settings?.totalCapital ?? 1000}
|
symbol={activeSymbol || Object.keys(botState.symbols)[0] || 'SPY'}
|
||||||
riskPerTradePercent={botState.settings?.riskPerTrade ?? 1}
|
allocatedCapital={botState.settings?.totalCapital ?? 1000}
|
||||||
/>
|
riskPerTradePercent={botState.settings?.riskPerTrade ?? 1}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -166,3 +171,26 @@ export function ResearchView() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CodeStrategyEditorFallback() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="status"
|
||||||
|
aria-label="Loading code strategy editor"
|
||||||
|
style={{
|
||||||
|
minHeight: 420,
|
||||||
|
border: '1px solid #E5E7EB',
|
||||||
|
borderRadius: 12,
|
||||||
|
background: 'linear-gradient(135deg, #F8FAFC, #EEF2FF)',
|
||||||
|
color: '#4B5563',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 700,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading code strategy editor…
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user