@tradejs/app 1.0.9 → 1.0.11

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.
Files changed (125) hide show
  1. package/README.md +16 -3
  2. package/bin/tradejs-app.mjs +146 -2
  3. package/next.config.mjs +0 -14
  4. package/package.json +35 -19
  5. package/src/app/actions/backtest.ts +54 -0
  6. package/src/app/actions/kline.ts +10 -5
  7. package/src/app/actions/scanner.ts +8 -3
  8. package/src/app/actions/strategies.ts +39 -0
  9. package/src/app/api/ai/route.ts +5 -4
  10. package/src/app/api/auth/[...nextauth]/route.ts +1 -1
  11. package/src/app/api/backtest/configs/route.ts +25 -0
  12. package/src/app/api/backtest/files/route.ts +5 -2
  13. package/src/app/api/backtest/order-log/[strategy]/[name]/route.ts +26 -2
  14. package/src/app/api/backtest/result/[strategy]/[name]/route.ts +27 -2
  15. package/src/app/api/backtest/runs/[jobId]/route.ts +124 -0
  16. package/src/app/api/backtest/runs/route.ts +45 -0
  17. package/src/app/api/backtest/test/[strategy]/[name]/route.ts +19 -2
  18. package/src/app/api/derivatives/summary/route.ts +8 -1
  19. package/src/app/api/install/route.ts +51 -0
  20. package/src/app/api/kline/[provider]/[universe]/[symbol]/[interval]/route.ts +1 -0
  21. package/src/app/api/kline/{[provider]/[symbol]/[interval]/route.ts → handler.ts} +61 -39
  22. package/src/app/api/scanner/[provider]/[universe]/route.ts +1 -0
  23. package/src/app/api/scanner/[provider]/route.ts +30 -12
  24. package/src/app/api/scanner/route.ts +3 -3
  25. package/src/app/api/signal/[symbol]/[signalId]/route.ts +1 -1
  26. package/src/app/api/strategies/ai/[cardId]/route.ts +22 -0
  27. package/src/app/api/strategies/ai/route.ts +139 -0
  28. package/src/app/api/strategies/replay/[cardId]/route.ts +18 -0
  29. package/src/app/api/strategies/replay/route.ts +44 -0
  30. package/src/app/api/strategies/runtime/route.ts +831 -0
  31. package/src/app/api/user/runtime-deployments/[deploymentId]/route.ts +25 -0
  32. package/src/app/api/user/runtime-deployments/route.ts +94 -0
  33. package/src/app/api/user/runtime-strategy-configs/route.ts +241 -0
  34. package/src/app/api/user/settings/route.ts +18 -1
  35. package/src/app/api/user/trading-accounts/[accountId]/route.ts +35 -0
  36. package/src/app/api/user/trading-accounts/route.ts +134 -0
  37. package/src/app/auth.ts +1 -19
  38. package/src/app/components/Backtest/CompareList/index.tsx +1 -1
  39. package/src/app/components/Backtest/ResultsPageClient.tsx +376 -0
  40. package/src/app/components/Backtest/TestCard/ActionsMenu/index.tsx +123 -0
  41. package/src/app/components/Backtest/TestCard/Chart/index.tsx +1 -1
  42. package/src/app/components/Backtest/TestCard/CompareButton/index.tsx +1 -1
  43. package/src/app/components/Backtest/TestCard/ConfigDrawer/index.tsx +28 -12
  44. package/src/app/components/Backtest/TestCard/DeleteButton/index.tsx +33 -17
  45. package/src/app/components/Backtest/TestCard/FavoriteIndicator/index.tsx +2 -2
  46. package/src/app/components/Backtest/TestCard/OpenDashboardButton/index.tsx +10 -1
  47. package/src/app/components/Backtest/TestCard/OrdersDrawer/index.tsx +128 -0
  48. package/src/app/components/Backtest/TestCard/Root/index.tsx +18 -4
  49. package/src/app/components/Backtest/TestCard/Stat/index.tsx +2 -0
  50. package/src/app/components/Backtest/TestCard/StatDrawer/index.tsx +66 -0
  51. package/src/app/components/Backtest/TestCard/index.ts +6 -2
  52. package/src/app/components/Backtest/TestList/index.tsx +4 -9
  53. package/src/app/components/Dashboard/AiDrawer/index.tsx +1 -1
  54. package/src/app/components/Dashboard/KlineChart/hooks/useBacktest.ts +1 -1
  55. package/src/app/components/Dashboard/KlineChart/hooks/useBbIndicator.ts +10 -5
  56. package/src/app/components/Dashboard/KlineChart/hooks/useBtcCorrelation.ts +1 -1
  57. package/src/app/components/Dashboard/KlineChart/hooks/useBtcIndicator.ts +4 -1
  58. package/src/app/components/Dashboard/KlineChart/hooks/useEmaIndicator.ts +3 -1
  59. package/src/app/components/Dashboard/KlineChart/hooks/usePluginIndicators.ts +1 -1
  60. package/src/app/components/Dashboard/KlineChart/hooks/useSetup.ts +1 -1
  61. package/src/app/components/Dashboard/KlineChart/hooks/useSignal.ts +1 -1
  62. package/src/app/components/Dashboard/KlineChart/hooks/useSpreadIndicator.ts +1 -1
  63. package/src/app/components/Dashboard/KlineChart/hooks/useTrendLine.ts +1 -1
  64. package/src/app/components/Dashboard/KlineChart/hooks/useWmaIndicator.ts +3 -1
  65. package/src/app/components/Dashboard/KlineChart/index.tsx +21 -7
  66. package/src/app/components/Dashboard/MainChart/index.tsx +3 -22
  67. package/src/app/components/Shared/AppShell.tsx +2 -2
  68. package/src/app/components/Shared/BulkSelection/index.tsx +140 -0
  69. package/src/app/components/Shared/BulkSelection/useBulkSelection.ts +97 -0
  70. package/src/app/components/Shared/BulkSelection/utils.ts +126 -0
  71. package/src/app/components/Shared/Filters/Backtest/index.tsx +51 -51
  72. package/src/app/components/Shared/Filters/FavoriteIndicator/index.tsx +5 -3
  73. package/src/app/components/Shared/Filters/Indicators/index.tsx +2 -2
  74. package/src/app/components/Shared/Filters/Interval/index.tsx +1 -1
  75. package/src/app/components/Shared/Filters/Provider/index.tsx +8 -2
  76. package/src/app/components/Shared/Filters/Symbol/index.tsx +4 -3
  77. package/src/app/components/Shared/Filters/Universe/index.tsx +36 -0
  78. package/src/app/components/Shared/Filters/index.ts +2 -0
  79. package/src/app/components/Shared/OrdersDrawer.tsx +629 -0
  80. package/src/app/components/Shared/Sidebar/AccountSettingsDrawer.tsx +55 -28
  81. package/src/app/components/Shared/Sidebar/TradingAccountsPanel.tsx +445 -0
  82. package/src/app/components/Shared/Sidebar/index.tsx +17 -3
  83. package/src/app/components/Strategies/AdvancedMetricsPanel.tsx +727 -0
  84. package/src/app/components/Strategies/RuntimeStrategyCard.tsx +2204 -0
  85. package/src/app/components/Strategies/RuntimeStrategyCardSkeleton.tsx +40 -0
  86. package/src/app/components/Strategies/RuntimeStrategyChart.tsx +121 -0
  87. package/src/app/components/Strategies/RuntimeStrategyConfigDrawer.tsx +458 -0
  88. package/src/app/components/Strategies/StrategySnapshotCard.tsx +2890 -0
  89. package/src/app/components/Strategies/StrategySnapshotChart.tsx +94 -0
  90. package/src/app/components/Strategies/StrategySnapshotList.tsx +82 -0
  91. package/src/app/components/UI/Segment/index.tsx +8 -1
  92. package/src/app/components/UI/Select/index.tsx +24 -13
  93. package/src/app/components/UI/SelectWithSearch/index.tsx +46 -5
  94. package/src/app/layout.tsx +1 -1
  95. package/src/app/lib/backtestJobs.ts +925 -0
  96. package/src/app/lib/cardPageLayout.ts +1 -0
  97. package/src/app/lib/connectorCreator.ts +28 -0
  98. package/src/app/lib/currentUser.ts +1 -1
  99. package/src/app/lib/errorMessage.ts +34 -0
  100. package/src/app/lib/installation.ts +83 -0
  101. package/src/app/lib/marketDefaults.ts +12 -0
  102. package/src/app/lib/marketRoutes.ts +58 -0
  103. package/src/app/lib/runtimeStrategies.ts +1376 -0
  104. package/src/app/lib/runtimeStrategyConfigForm.ts +86 -0
  105. package/src/app/provider.tsx +1 -1
  106. package/src/app/routes/backtest/page.tsx +1100 -302
  107. package/src/app/routes/dashboard/{[provider]/[symbol]/[interval]/page.tsx → Dashboard.tsx} +76 -35
  108. package/src/app/routes/dashboard/[provider]/[universe]/[symbol]/[interval]/page.tsx +1 -0
  109. package/src/app/routes/dashboard/page.tsx +15 -3
  110. package/src/app/routes/derivatives/page.tsx +1222 -165
  111. package/src/app/routes/install/page.tsx +175 -0
  112. package/src/app/routes/signin/page.tsx +7 -0
  113. package/src/app/routes/strategies/StrategiesPageClient.tsx +593 -0
  114. package/src/app/routes/strategies/[mode]/page.tsx +20 -0
  115. package/src/app/routes/strategies/page.tsx +7 -0
  116. package/src/app/store/ai.ts +1 -1
  117. package/src/app/store/data.ts +73 -17
  118. package/src/app/store/filters.ts +1 -0
  119. package/src/app/store/marketKlineStream.ts +87 -0
  120. package/src/app/store/tests.ts +83 -45
  121. package/src/app/store/tickers.ts +59 -22
  122. package/src/proxy.ts +4 -0
  123. package/tsconfig.json +17 -12
  124. package/src/app/components/Backtest/TestCard/OpenReportButton/index.tsx +0 -24
  125. package/src/app/routes/backtest/[test]/page.tsx +0 -33
@@ -0,0 +1,139 @@
1
+ import { NextResponse } from 'next/server';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import type {
5
+ StrategyChartSnapshot,
6
+ StrategyChartsSnapshotResponse,
7
+ } from '@tradejs/types';
8
+ import { toFileToken } from '@tradejs/infra/ai';
9
+ import { getData, getKeys, redisKeys } from '@tradejs/infra/redis';
10
+ import { getCurrentUserName } from '#app/lib/currentUser';
11
+
12
+ export const dynamic = 'force-dynamic';
13
+
14
+ const EMPTY_RESPONSE: StrategyChartsSnapshotResponse = {
15
+ mode: 'ai',
16
+ generatedAt: 0,
17
+ runLabel: '',
18
+ strategies: [],
19
+ };
20
+
21
+ const DATASET_FILE_RE = /^ai-dataset-(.+)-merged-(\d+)(?:-part\d+)?\.jsonl$/;
22
+
23
+ const getProjectRoot = () =>
24
+ String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
25
+
26
+ const compareNumericString = (left: string, right: string) =>
27
+ left.length - right.length || left.localeCompare(right);
28
+
29
+ const resolveTotalPnl = (card: StrategyChartSnapshot) => {
30
+ const statNetProfit = Number(card.stat?.netProfit ?? Number.NaN);
31
+ if (Number.isFinite(statNetProfit)) {
32
+ return statNetProfit;
33
+ }
34
+
35
+ const firstAmount = card.orderLog[0]?.[1];
36
+ const lastAmount = card.orderLog.at(-1)?.[1];
37
+ if (
38
+ typeof firstAmount === 'number' &&
39
+ typeof lastAmount === 'number' &&
40
+ Number.isFinite(firstAmount) &&
41
+ Number.isFinite(lastAmount)
42
+ ) {
43
+ return lastAmount - firstAmount;
44
+ }
45
+
46
+ return 0;
47
+ };
48
+
49
+ const loadLatestDatasetIds = async () => {
50
+ const idsByStrategy = new Map<string, string>();
51
+ let entries: string[] = [];
52
+
53
+ try {
54
+ entries = await fs.readdir(path.join(getProjectRoot(), 'data/ai/export'));
55
+ } catch {
56
+ return idsByStrategy;
57
+ }
58
+
59
+ for (const entry of entries) {
60
+ const match = entry.match(DATASET_FILE_RE);
61
+ if (!match) {
62
+ continue;
63
+ }
64
+
65
+ const strategyToken = match[1];
66
+ const datasetId = match[2];
67
+ if (!strategyToken || !datasetId) {
68
+ continue;
69
+ }
70
+
71
+ const current = idsByStrategy.get(strategyToken);
72
+ if (!current || compareNumericString(current, datasetId) < 0) {
73
+ idsByStrategy.set(strategyToken, datasetId);
74
+ }
75
+ }
76
+
77
+ return idsByStrategy;
78
+ };
79
+
80
+ const attachLegacyDatasetIds = async (strategies: StrategyChartSnapshot[]) => {
81
+ if (strategies.every((strategy) => strategy.datasetId)) {
82
+ return strategies;
83
+ }
84
+
85
+ const latestDatasetIds = await loadLatestDatasetIds();
86
+ if (!latestDatasetIds.size) {
87
+ return strategies;
88
+ }
89
+
90
+ return strategies.map((strategy) =>
91
+ strategy.datasetId
92
+ ? strategy
93
+ : {
94
+ ...strategy,
95
+ datasetId: latestDatasetIds.get(toFileToken(strategy.strategyName)),
96
+ },
97
+ );
98
+ };
99
+
100
+ const normalizeStrategyChartCard = (
101
+ card: StrategyChartSnapshot,
102
+ ): StrategyChartSnapshot => ({
103
+ ...card,
104
+ orders: Array.isArray(card.orders) ? card.orders : [],
105
+ });
106
+
107
+ export async function GET() {
108
+ const userName = await getCurrentUserName();
109
+ if (!userName) {
110
+ return NextResponse.json(EMPTY_RESPONSE);
111
+ }
112
+
113
+ const keys = await getKeys(redisKeys.strategyChartCards(userName, 'ai'));
114
+ const cards = (
115
+ await Promise.all(
116
+ keys.map(
117
+ (key) => getData(key, null) as Promise<StrategyChartSnapshot | null>,
118
+ ),
119
+ )
120
+ )
121
+ .filter((card): card is StrategyChartSnapshot => Boolean(card))
122
+ .map(normalizeStrategyChartCard)
123
+ .sort(
124
+ (left, right) =>
125
+ resolveTotalPnl(right) - resolveTotalPnl(left) ||
126
+ right.generatedAt - left.generatedAt ||
127
+ left.title.localeCompare(right.title),
128
+ );
129
+ const strategies = await attachLegacyDatasetIds(cards);
130
+
131
+ const data: StrategyChartsSnapshotResponse = {
132
+ mode: 'ai',
133
+ generatedAt: strategies[0]?.generatedAt ?? 0,
134
+ runLabel: '',
135
+ strategies,
136
+ };
137
+
138
+ return NextResponse.json(data);
139
+ }
@@ -0,0 +1,18 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { delKey, redisKeys } from '@tradejs/infra/redis';
3
+ import { getCurrentUserName } from '#app/lib/currentUser';
4
+
5
+ export const dynamic = 'force-dynamic';
6
+
7
+ export async function DELETE(
8
+ _req: Request,
9
+ { params }: { params: Promise<{ cardId: string }> },
10
+ ) {
11
+ const userName = (await getCurrentUserName()) || 'root';
12
+ const { cardId } = await params;
13
+ const deleted = await delKey(
14
+ redisKeys.strategyChartCard(userName, 'replay', decodeURIComponent(cardId)),
15
+ );
16
+
17
+ return NextResponse.json({ deleted });
18
+ }
@@ -0,0 +1,44 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type {
3
+ StrategyChartSnapshot,
4
+ StrategyChartsSnapshotResponse,
5
+ } from '@tradejs/types';
6
+ import { getData, getKeys, redisKeys } from '@tradejs/infra/redis';
7
+ import { getCurrentUserName } from '#app/lib/currentUser';
8
+
9
+ export const dynamic = 'force-dynamic';
10
+
11
+ const normalizeStrategyChartCard = (
12
+ card: StrategyChartSnapshot,
13
+ ): StrategyChartSnapshot => ({
14
+ ...card,
15
+ orders: Array.isArray(card.orders) ? card.orders : [],
16
+ });
17
+
18
+ export async function GET() {
19
+ const userName = (await getCurrentUserName()) || 'root';
20
+ const keys = await getKeys(redisKeys.strategyChartCards(userName, 'replay'));
21
+ const strategies = (
22
+ await Promise.all(
23
+ keys.map(
24
+ (key) => getData(key, null) as Promise<StrategyChartSnapshot | null>,
25
+ ),
26
+ )
27
+ )
28
+ .filter((card): card is StrategyChartSnapshot => Boolean(card))
29
+ .map(normalizeStrategyChartCard)
30
+ .sort(
31
+ (left, right) =>
32
+ right.generatedAt - left.generatedAt ||
33
+ left.title.localeCompare(right.title),
34
+ );
35
+
36
+ const data: StrategyChartsSnapshotResponse = {
37
+ mode: 'replay',
38
+ generatedAt: strategies[0]?.generatedAt ?? 0,
39
+ runLabel: '',
40
+ strategies,
41
+ };
42
+
43
+ return NextResponse.json(data);
44
+ }