@tradejs/app 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,3 +42,5 @@ before starting or building the app with:
42
42
  ```bash
43
43
  NEXT_PUBLIC_TRADEJS_TELEMETRY_DISABLED=1
44
44
  ```
45
+
46
+ Keywords: ai, claude, codex.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/app",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Installable Next.js UI for the TradeJS TypeScript framework: dashboards, backtests, charts, and runtime data.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -9,7 +9,10 @@
9
9
  "app",
10
10
  "dashboard",
11
11
  "nextjs",
12
- "signals"
12
+ "signals",
13
+ "ai",
14
+ "claude",
15
+ "codex"
13
16
  ],
14
17
  "files": [
15
18
  "README.md",
@@ -49,11 +52,11 @@
49
52
  "@chakra-ui/charts": "3.36.1",
50
53
  "@chakra-ui/react": "3.36.1",
51
54
  "@emotion/react": "^11.14.0",
52
- "@tradejs/core": "^3.1.0",
53
- "@tradejs/indicators": "^3.1.0",
54
- "@tradejs/infra": "^3.1.0",
55
- "@tradejs/node": "^3.1.0",
56
- "@tradejs/types": "^3.1.0",
55
+ "@tradejs/core": "^3.1.1",
56
+ "@tradejs/indicators": "^3.1.1",
57
+ "@tradejs/infra": "^3.1.1",
58
+ "@tradejs/node": "^3.1.1",
59
+ "@tradejs/types": "^3.1.1",
57
60
  "@types/bcryptjs": "2.4.6",
58
61
  "@types/lodash": "4.17.24",
59
62
  "@types/node": "24.13.3",
@@ -0,0 +1,73 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import {
3
+ getRuntimeDeployment,
4
+ saveRuntimeDeployment,
5
+ } from '@tradejs/infra/runtimeDeployments';
6
+ import { recordRuntimeStrategyControlEvent } from '@tradejs/infra/runtimeStrategyReleases';
7
+ import type { RuntimeStrategyControlState } from '@tradejs/types';
8
+ import { getCurrentUserName } from '#app/lib/currentUser';
9
+
10
+ export const PATCH = async (
11
+ request: NextRequest,
12
+ {
13
+ params,
14
+ }: {
15
+ params: Promise<{ deploymentId: string; strategyName: string }>;
16
+ },
17
+ ) => {
18
+ const userName = await getCurrentUserName();
19
+ if (!userName) {
20
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
21
+ }
22
+ const { deploymentId, strategyName: encodedStrategyName } = await params;
23
+ const strategyName = decodeURIComponent(encodedStrategyName);
24
+ const deployment = await getRuntimeDeployment(userName, deploymentId);
25
+ if (!deployment) {
26
+ return NextResponse.json(
27
+ { error: 'Deployment not found' },
28
+ { status: 404 },
29
+ );
30
+ }
31
+ const body = (await request.json()) as { controlState?: unknown };
32
+ if (
33
+ body.controlState !== 'active' &&
34
+ body.controlState !== 'entries_paused'
35
+ ) {
36
+ return NextResponse.json(
37
+ { error: 'controlState must be active or entries_paused' },
38
+ { status: 400 },
39
+ );
40
+ }
41
+ const controlState: RuntimeStrategyControlState = body.controlState;
42
+ const reference = deployment.strategies.find(
43
+ (strategy) => strategy.strategyName === strategyName,
44
+ );
45
+ if (!reference?.releaseVersion) {
46
+ return NextResponse.json(
47
+ { error: 'Pause/resume requires a versioned strategy release' },
48
+ { status: 400 },
49
+ );
50
+ }
51
+ const previousState = reference.controlState ?? 'active';
52
+ if (previousState === controlState) {
53
+ return NextResponse.json({ deployment, controlState });
54
+ }
55
+ const updated = await saveRuntimeDeployment(userName, {
56
+ ...deployment,
57
+ strategies: deployment.strategies.map((strategy) =>
58
+ strategy.strategyName === strategyName
59
+ ? { ...strategy, controlState }
60
+ : strategy,
61
+ ),
62
+ });
63
+ const event = await recordRuntimeStrategyControlEvent({
64
+ userName,
65
+ deploymentId: deployment.id,
66
+ strategyName,
67
+ releaseVersion: reference.releaseVersion,
68
+ previousState,
69
+ nextState: controlState,
70
+ createdBy: userName,
71
+ });
72
+ return NextResponse.json({ deployment: updated, controlState, event });
73
+ };
@@ -5,6 +5,7 @@ import {
5
5
  saveRuntimeDeployment,
6
6
  } from '@tradejs/infra/runtimeDeployments';
7
7
  import { getTradingAccount } from '@tradejs/infra/tradingAccounts';
8
+ import { getRuntimeStrategyRelease } from '@tradejs/infra/runtimeStrategyReleases';
8
9
  import { isMarketUniverse, type RuntimeDeployment } from '@tradejs/types';
9
10
  import { getCurrentUserName } from '#app/lib/currentUser';
10
11
 
@@ -32,21 +33,59 @@ export const POST = async (request: NextRequest) => {
32
33
  }
33
34
  try {
34
35
  const body = (await request.json()) as Partial<RuntimeDeployment>;
35
- const universe = body.universe;
36
+ const hasVersionedStrategies = Boolean(
37
+ body.strategies?.some((strategy) => strategy.releaseVersion != null),
38
+ );
39
+ if (
40
+ hasVersionedStrategies &&
41
+ body.strategies?.some((strategy) => strategy.releaseVersion == null)
42
+ ) {
43
+ return NextResponse.json(
44
+ { error: 'A deployment cannot mix legacy configs and releases' },
45
+ { status: 400 },
46
+ );
47
+ }
48
+ const releases = hasVersionedStrategies
49
+ ? await Promise.all(
50
+ (body.strategies ?? []).map(async (strategy) => {
51
+ if (
52
+ !Number.isSafeInteger(strategy.releaseVersion) ||
53
+ !strategy.releaseVersion ||
54
+ (strategy.config && Object.keys(strategy.config).length)
55
+ ) {
56
+ throw new Error('Invalid versioned strategy reference');
57
+ }
58
+ const release = await getRuntimeStrategyRelease(
59
+ userName,
60
+ strategy.strategyName,
61
+ strategy.releaseVersion,
62
+ );
63
+ if (!release) {
64
+ throw new Error(
65
+ `Release not found: ${strategy.strategyName} v${strategy.releaseVersion}`,
66
+ );
67
+ }
68
+ return release;
69
+ }),
70
+ )
71
+ : [];
72
+ const universe = releases[0]?.config.UNIVERSE ?? body.universe;
73
+ const interval = releases[0]?.config.INTERVAL ?? body.interval;
36
74
  if (
37
75
  !body.id ||
38
76
  !body.label ||
39
77
  !body.connectorName ||
40
78
  !body.provider ||
41
79
  !body.accountId ||
42
- !body.interval ||
80
+ !interval ||
43
81
  !isMarketUniverse(universe) ||
44
82
  !Array.isArray(body.strategies) ||
45
83
  !body.strategies.length ||
46
- body.strategies.some(
47
- (strategy) =>
48
- !String(strategy.strategyName ?? '').trim() ||
49
- !String(strategy.policyProfileId ?? '').trim(),
84
+ body.strategies.some((strategy) =>
85
+ hasVersionedStrategies
86
+ ? !String(strategy.strategyName ?? '').trim()
87
+ : !String(strategy.strategyName ?? '').trim() ||
88
+ !String(strategy.policyProfileId ?? '').trim(),
50
89
  )
51
90
  ) {
52
91
  return NextResponse.json(
@@ -78,9 +117,17 @@ export const POST = async (request: NextRequest) => {
78
117
  provider: body.provider,
79
118
  accountId: body.accountId,
80
119
  universe,
81
- interval: String(body.interval),
120
+ interval: String(interval),
82
121
  enabled: body.enabled !== false,
83
- strategies: body.strategies,
122
+ strategies: body.strategies.map((strategy) =>
123
+ hasVersionedStrategies
124
+ ? {
125
+ strategyName: strategy.strategyName,
126
+ releaseVersion: strategy.releaseVersion,
127
+ controlState: strategy.controlState ?? 'active',
128
+ }
129
+ : strategy,
130
+ ),
84
131
  assetClasses: body.assetClasses,
85
132
  tickers: body.tickers,
86
133
  });
@@ -0,0 +1,60 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import {
3
+ getRuntimeStrategyReleaseOptions,
4
+ publishRuntimeStrategyReleaseForUser,
5
+ saveRuntimeStrategyReleaseDraftForUser,
6
+ } from '#app/lib/runtimeStrategyReleaseService';
7
+ import { getCurrentUserName } from '#app/lib/currentUser';
8
+
9
+ export const dynamic = 'force-dynamic';
10
+
11
+ const projectRoot =
12
+ String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
13
+
14
+ export const GET = async () => {
15
+ const userName = await getCurrentUserName();
16
+ if (!userName)
17
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
18
+ return NextResponse.json(
19
+ await getRuntimeStrategyReleaseOptions({ userName, projectRoot }),
20
+ );
21
+ };
22
+
23
+ export const PATCH = async (request: NextRequest) => {
24
+ const userName = await getCurrentUserName();
25
+ if (!userName)
26
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
27
+ try {
28
+ const draft = await saveRuntimeStrategyReleaseDraftForUser({
29
+ userName,
30
+ projectRoot,
31
+ input: (await request.json()) as Record<string, unknown>,
32
+ });
33
+ return NextResponse.json({ draft });
34
+ } catch (error) {
35
+ return NextResponse.json(
36
+ { error: error instanceof Error ? error.message : String(error) },
37
+ { status: 400 },
38
+ );
39
+ }
40
+ };
41
+
42
+ export const POST = async (request: NextRequest) => {
43
+ const userName = await getCurrentUserName();
44
+ if (!userName)
45
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
46
+ try {
47
+ const body = (await request.json()) as { strategyName?: unknown };
48
+ const release = await publishRuntimeStrategyReleaseForUser({
49
+ userName,
50
+ projectRoot,
51
+ strategyName: String(body.strategyName ?? '').trim(),
52
+ });
53
+ return NextResponse.json({ release });
54
+ } catch (error) {
55
+ return NextResponse.json(
56
+ { error: error instanceof Error ? error.message : String(error) },
57
+ { status: 400 },
58
+ );
59
+ }
60
+ };
@@ -26,6 +26,7 @@ import {
26
26
  getColorByLevel,
27
27
  RUNTIME_ORDER_ROW_HEIGHT,
28
28
  } from './RuntimeStrategyCard.presenter';
29
+ import { toaster } from '#ui';
29
30
 
30
31
  const StatItem = ({
31
32
  stat,
@@ -64,11 +65,42 @@ export const RuntimeStrategyCard = ({
64
65
  const [configOpen, setConfigOpen] = useState(false);
65
66
  const [ordersOpen, setOrdersOpen] = useState(false);
66
67
  const [statsOpen, setStatsOpen] = useState(false);
68
+ const [controlSaving, setControlSaving] = useState(false);
67
69
  const viewModel = useMemo(
68
70
  () => buildRuntimeStrategyCardViewModel(strategy),
69
71
  [strategy],
70
72
  );
71
73
  const { lastTrade, runtimeOrders } = viewModel;
74
+ const setControlState = async (controlState: 'active' | 'entries_paused') => {
75
+ if (!strategy.deploymentId || !strategy.releaseVersion) return;
76
+ setControlSaving(true);
77
+ try {
78
+ const response = await fetch(
79
+ `/api/user/runtime-deployments/${encodeURIComponent(strategy.deploymentId)}/strategies/${encodeURIComponent(strategy.strategyName)}/control`,
80
+ {
81
+ method: 'PATCH',
82
+ headers: { 'content-type': 'application/json' },
83
+ body: JSON.stringify({ controlState }),
84
+ },
85
+ );
86
+ const payload = (await response.json()) as { error?: string };
87
+ if (!response.ok) throw new Error(payload.error || 'Update failed');
88
+ toaster.success({
89
+ title:
90
+ controlState === 'entries_paused'
91
+ ? 'New entries paused'
92
+ : 'Strategy resumed',
93
+ });
94
+ await onUpdated();
95
+ } catch (error) {
96
+ toaster.error({
97
+ title: 'Could not update strategy state',
98
+ description: error instanceof Error ? error.message : String(error),
99
+ });
100
+ } finally {
101
+ setControlSaving(false);
102
+ }
103
+ };
72
104
 
73
105
  return (
74
106
  <Box
@@ -91,7 +123,11 @@ export const RuntimeStrategyCard = ({
91
123
  fontFamily="mono"
92
124
  letterSpacing="0"
93
125
  >
94
- {strategy.enabled ? 'enabled' : 'disabled'}
126
+ {strategy.controlState === 'entries_paused'
127
+ ? 'entries paused'
128
+ : strategy.enabled
129
+ ? 'enabled'
130
+ : 'disabled'}
95
131
  </Badge>
96
132
  <Badge colorPalette="blue" variant="outline">
97
133
  {strategy.universe}
@@ -99,9 +135,15 @@ export const RuntimeStrategyCard = ({
99
135
  <Badge colorPalette="cyan" variant="outline">
100
136
  TF: {strategy.interval}m
101
137
  </Badge>
102
- <Badge colorPalette="gray" variant="outline">
103
- config: {strategy.configId}
104
- </Badge>
138
+ {strategy.releaseVersion ? (
139
+ <Badge colorPalette="gray" variant="outline">
140
+ release: v{strategy.releaseVersion}
141
+ </Badge>
142
+ ) : (
143
+ <Badge colorPalette="gray" variant="outline">
144
+ legacy config: {strategy.configId}
145
+ </Badge>
146
+ )}
105
147
  {strategy.accountId ? (
106
148
  <Badge colorPalette="purple" variant="outline">
107
149
  account: {strategy.accountLabel ?? strategy.accountId}
@@ -171,7 +213,24 @@ export const RuntimeStrategyCard = ({
171
213
  <Menu.Content minW="160px">
172
214
  {strategy.connected ? (
173
215
  <Menu.Item value="edit" onClick={() => setConfigOpen(true)}>
174
- Edit
216
+ {strategy.releaseVersion ? 'View config' : 'Edit'}
217
+ </Menu.Item>
218
+ ) : null}
219
+ {strategy.releaseVersion && strategy.deploymentId ? (
220
+ <Menu.Item
221
+ value="control"
222
+ disabled={controlSaving}
223
+ onClick={() =>
224
+ void setControlState(
225
+ strategy.controlState === 'entries_paused'
226
+ ? 'active'
227
+ : 'entries_paused',
228
+ )
229
+ }
230
+ >
231
+ {strategy.controlState === 'entries_paused'
232
+ ? 'Resume entries'
233
+ : 'Pause new entries'}
175
234
  </Menu.Item>
176
235
  ) : null}
177
236
  <Menu.Item value="orders" onClick={() => setOrdersOpen(true)}>
@@ -160,7 +160,7 @@ export const RuntimeStrategyConfigDrawer = ({
160
160
  setMlThreshold(String(managed.mlThreshold));
161
161
  setParameters('{}');
162
162
  }
163
- void loadOptions();
163
+ if (!strategy?.releaseVersion) void loadOptions();
164
164
  }, [loadOptions, open, strategy]);
165
165
 
166
166
  const compatibleAccounts = useMemo(
@@ -251,6 +251,50 @@ export const RuntimeStrategyConfigDrawer = ({
251
251
  }
252
252
  };
253
253
 
254
+ if (strategy?.releaseVersion) {
255
+ return (
256
+ <Drawer.Root
257
+ size="lg"
258
+ open={open}
259
+ onOpenChange={(event) => onOpenChange(event.open)}
260
+ >
261
+ <Portal>
262
+ <Drawer.Backdrop />
263
+ <Drawer.Positioner>
264
+ <Drawer.Content bg="gray.950">
265
+ <Drawer.Header>
266
+ <Drawer.Title>
267
+ {strategy.strategyName} release v{strategy.releaseVersion}
268
+ </Drawer.Title>
269
+ <Drawer.CloseTrigger asChild>
270
+ <CloseButton size="sm" />
271
+ </Drawer.CloseTrigger>
272
+ </Drawer.Header>
273
+ <Drawer.Body display="flex" flexDirection="column" gap={4}>
274
+ <Text color="gray.400">
275
+ Published releases are immutable. Create and publish a new
276
+ release to change this configuration.
277
+ </Text>
278
+ <Textarea
279
+ value={JSON.stringify(strategy.config ?? {}, null, 2)}
280
+ readOnly
281
+ minH="70vh"
282
+ fontFamily="mono"
283
+ fontSize="sm"
284
+ />
285
+ </Drawer.Body>
286
+ <Drawer.Footer>
287
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
288
+ Close
289
+ </Button>
290
+ </Drawer.Footer>
291
+ </Drawer.Content>
292
+ </Drawer.Positioner>
293
+ </Portal>
294
+ </Drawer.Root>
295
+ );
296
+ }
297
+
254
298
  return (
255
299
  <Drawer.Root
256
300
  size="lg"
@@ -137,7 +137,9 @@ export const StrategyEvidencePopover = ({
137
137
  )}.`
138
138
  : timeline.status === 'invalid'
139
139
  ? 'Evidence failed checksum, identity, or structure verification. No markers are rendered.'
140
- : 'No checksum-verified evidence artifact is available. No mutable fallback is used.'}
140
+ : timeline.status === 'not_attached'
141
+ ? 'The runtime release is published, but no checksum-verified research artifact is attached yet.'
142
+ : 'No checksum-verified evidence artifact is available. No mutable fallback is used.'}
141
143
  </Text>
142
144
  </Box>
143
145
 
@@ -0,0 +1,123 @@
1
+ import {
2
+ getRuntimeStrategyDraft,
3
+ listRuntimeStrategyReleases,
4
+ publishRuntimeStrategyRelease,
5
+ saveRuntimeStrategyDraft,
6
+ } from '@tradejs/infra/runtimeStrategyReleases';
7
+ import { getRuntimeStrategyPackageMetadata } from '@tradejs/node/runtimeStrategies';
8
+ import {
9
+ getAvailableStrategyNames,
10
+ getStrategyDefaults,
11
+ } from '@tradejs/node/strategies';
12
+ import type { StrategyConfig } from '@tradejs/types';
13
+
14
+ const OPERATIONAL_KEYS = [
15
+ 'ACCOUNT_ID',
16
+ 'AI_REPLAY_ANALYSES',
17
+ 'DEPLOYMENT_ID',
18
+ 'ENABLE',
19
+ 'ENV',
20
+ 'MAKE_ORDERS',
21
+ 'RECORD_RUNTIME_TRADES',
22
+ 'configId',
23
+ ];
24
+
25
+ const assertConfig = (value: unknown): StrategyConfig => {
26
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
27
+ throw new Error('Strategy config must be a JSON object');
28
+ }
29
+ const config = value as StrategyConfig;
30
+ const invalidKey = OPERATIONAL_KEYS.find((key) => config[key] !== undefined);
31
+ if (invalidKey) {
32
+ throw new Error(
33
+ `${invalidKey} is controlled by the deployment, not config`,
34
+ );
35
+ }
36
+ return config;
37
+ };
38
+
39
+ const assertKnownStrategy = async (
40
+ strategyName: string,
41
+ projectRoot: string,
42
+ ) => {
43
+ if (!(await getAvailableStrategyNames(projectRoot)).includes(strategyName)) {
44
+ throw new Error(`Unknown strategy: ${strategyName}`);
45
+ }
46
+ };
47
+
48
+ export const getRuntimeStrategyReleaseOptions = async ({
49
+ userName,
50
+ projectRoot,
51
+ }: {
52
+ userName: string;
53
+ projectRoot: string;
54
+ }) => {
55
+ const strategyNames = await getAvailableStrategyNames(projectRoot);
56
+ const strategies = await Promise.all(
57
+ strategyNames.map(async (strategyName) => ({
58
+ strategyName,
59
+ draft: await getRuntimeStrategyDraft(userName, strategyName),
60
+ releases: await listRuntimeStrategyReleases(userName, strategyName),
61
+ })),
62
+ );
63
+ return { strategyNames, strategies };
64
+ };
65
+
66
+ export const saveRuntimeStrategyReleaseDraftForUser = async ({
67
+ userName,
68
+ projectRoot,
69
+ input,
70
+ }: {
71
+ userName: string;
72
+ projectRoot: string;
73
+ input: Record<string, unknown>;
74
+ }) => {
75
+ const strategyName = String(input.strategyName ?? '').trim();
76
+ await assertKnownStrategy(strategyName, projectRoot);
77
+ return saveRuntimeStrategyDraft({
78
+ userName,
79
+ strategyName,
80
+ config: assertConfig(input.config),
81
+ baseReleaseVersion:
82
+ typeof input.baseReleaseVersion === 'number'
83
+ ? input.baseReleaseVersion
84
+ : null,
85
+ updatedBy: userName,
86
+ });
87
+ };
88
+
89
+ export const publishRuntimeStrategyReleaseForUser = async ({
90
+ userName,
91
+ projectRoot,
92
+ strategyName,
93
+ }: {
94
+ userName: string;
95
+ projectRoot: string;
96
+ strategyName: string;
97
+ }) => {
98
+ await assertKnownStrategy(strategyName, projectRoot);
99
+ const draft = await getRuntimeStrategyDraft(userName, strategyName);
100
+ if (!draft) throw new Error(`Draft not found: ${strategyName}`);
101
+ const defaults = (await getStrategyDefaults(strategyName, projectRoot)) ?? {};
102
+ const config: StrategyConfig = { ...defaults, ...draft.config };
103
+ for (const key of OPERATIONAL_KEYS) delete config[key];
104
+ const metadata = await getRuntimeStrategyPackageMetadata({
105
+ strategyName,
106
+ projectRoot,
107
+ });
108
+ const release = await publishRuntimeStrategyRelease({
109
+ userName,
110
+ strategyName,
111
+ config,
112
+ ...metadata,
113
+ createdBy: userName,
114
+ });
115
+ await saveRuntimeStrategyDraft({
116
+ userName,
117
+ strategyName,
118
+ config: release.config,
119
+ baseReleaseVersion: release.releaseVersion,
120
+ updatedBy: userName,
121
+ });
122
+ return release;
123
+ };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
4
- import { Box, Button, ClientOnly, Flex } from '@chakra-ui/react';
4
+ import { Box, ClientOnly, Flex } from '@chakra-ui/react';
5
5
  import { usePathname } from 'next/navigation';
6
6
  import { FiFolder } from 'react-icons/fi';
7
7
  import type {
@@ -16,7 +16,6 @@ import {
16
16
  } from '#actions/strategies';
17
17
  import { RuntimeStrategyCard } from '#components/Strategies/RuntimeStrategyCard';
18
18
  import { RuntimeStrategyCardSkeleton } from '#components/Strategies/RuntimeStrategyCardSkeleton';
19
- import { RuntimeStrategyConfigDrawer } from '#components/Strategies/RuntimeStrategyConfigDrawer';
20
19
  import { StrategySnapshotList } from '#components/Strategies/StrategySnapshotList';
21
20
  import { BacktestResultsPageClient } from '#components/Backtest/ResultsPageClient';
22
21
  import {
@@ -85,7 +84,6 @@ const RuntimeStrategiesContent = () => {
85
84
  const [error, setError] = useState('');
86
85
  const [runtimeData, setRuntimeData] =
87
86
  useState<RuntimeStrategiesResponse | null>(null);
88
- const [createRuntimeConfigOpen, setCreateRuntimeConfigOpen] = useState(false);
89
87
  const [snapshotData, setSnapshotData] =
90
88
  useState<StrategyChartsSnapshotResponse | null>(null);
91
89
  const isSnapshotMode = mode === 'replay' || mode === 'ai';
@@ -519,23 +517,8 @@ const RuntimeStrategiesContent = () => {
519
517
  />
520
518
  ) : null}
521
519
  </Flex>
522
- {mode === 'runtime' ? (
523
- <Button
524
- ml="auto"
525
- colorPalette="teal"
526
- onClick={() => setCreateRuntimeConfigOpen(true)}
527
- >
528
- Create
529
- </Button>
530
- ) : null}
531
520
  </Flex>
532
521
 
533
- <RuntimeStrategyConfigDrawer
534
- open={createRuntimeConfigOpen}
535
- onOpenChange={setCreateRuntimeConfigOpen}
536
- onSaved={load}
537
- />
538
-
539
522
  {isSnapshotMode ? (
540
523
  <BulkDeleteToolbar
541
524
  selectedCount={selectedFilteredCount}