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();
|
||||
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" }; }' },
|
||||
});
|
||||
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 () => {
|
||||
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));
|
||||
});
|
||||
@ -112,7 +112,7 @@ describe('CodeStrategyEditor save behavior', () => {
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
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));
|
||||
expect(String(fetchMock.mock.calls[0][0])).toBe('https://trading.test/api/backtest/run');
|
||||
|
||||
@ -2,8 +2,7 @@
|
||||
* Monaco-based code strategy editor.
|
||||
* Users write a JS strategy function; "Run Backtest" posts it to /api/backtest.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import { Suspense, lazy, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Play, Save, Copy, RotateCcw } from 'lucide-react';
|
||||
import { getPlatformAccessToken } from '../../lib/authSession';
|
||||
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 {
|
||||
trades?: number;
|
||||
winRate?: number;
|
||||
@ -231,29 +232,31 @@ export function CodeStrategyEditor({
|
||||
|
||||
{/* Monaco editor */}
|
||||
<div style={{ border: '1px solid #E5E7EB', borderRadius: 10, overflow: 'hidden' }}>
|
||||
<Editor
|
||||
height="380px"
|
||||
defaultLanguage="javascript"
|
||||
value={code}
|
||||
onChange={v => {
|
||||
setCode(v ?? '');
|
||||
setSaved(false);
|
||||
}}
|
||||
theme="light"
|
||||
options={{
|
||||
fontSize: 13,
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
wordWrap: 'on',
|
||||
lineNumbers: 'on',
|
||||
renderLineHighlight: 'gutter',
|
||||
padding: { top: 12, bottom: 12 },
|
||||
fontFamily: '"Fira Code", "Cascadia Code", "Consolas", monospace',
|
||||
fontLigatures: true,
|
||||
tabSize: 2,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
<Suspense fallback={<CodeEditorFallback />}>
|
||||
<MonacoEditor
|
||||
height="380px"
|
||||
defaultLanguage="javascript"
|
||||
value={code}
|
||||
onChange={v => {
|
||||
setCode(v ?? '');
|
||||
setSaved(false);
|
||||
}}
|
||||
theme="light"
|
||||
options={{
|
||||
fontSize: 13,
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
wordWrap: 'on',
|
||||
lineNumbers: 'on',
|
||||
renderLineHighlight: 'gutter',
|
||||
padding: { top: 12, bottom: 12 },
|
||||
fontFamily: '"Fira Code", "Cascadia Code", "Consolas", monospace',
|
||||
fontLigatures: true,
|
||||
tabSize: 2,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
{/* 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 {
|
||||
return {
|
||||
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 { 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';
|
||||
import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel';
|
||||
|
||||
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[]) {
|
||||
return {
|
||||
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.
|
||||
</div>
|
||||
</div>
|
||||
<CodeStrategyEditor
|
||||
symbol={activeSymbol || Object.keys(botState.symbols)[0] || 'SPY'}
|
||||
allocatedCapital={botState.settings?.totalCapital ?? 1000}
|
||||
riskPerTradePercent={botState.settings?.riskPerTrade ?? 1}
|
||||
/>
|
||||
<Suspense fallback={<CodeStrategyEditorFallback />}>
|
||||
<CodeStrategyEditor
|
||||
symbol={activeSymbol || Object.keys(botState.symbols)[0] || 'SPY'}
|
||||
allocatedCapital={botState.settings?.totalCapital ?? 1000}
|
||||
riskPerTradePercent={botState.settings?.riskPerTrade ?? 1}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -166,3 +171,26 @@ export function ResearchView() {
|
||||
</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