@tradejs/app 1.0.0

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 (126) hide show
  1. package/README.md +12 -0
  2. package/bin/tradejs-app.mjs +54 -0
  3. package/next-env.d.ts +6 -0
  4. package/next.config.mjs +31 -0
  5. package/package.json +60 -0
  6. package/src/app/actions/ai.ts +33 -0
  7. package/src/app/actions/backtest.ts +55 -0
  8. package/src/app/actions/kline.ts +18 -0
  9. package/src/app/actions/scanner.ts +10 -0
  10. package/src/app/actions/signal.ts +19 -0
  11. package/src/app/api/ai/route.ts +151 -0
  12. package/src/app/api/auth/[...nextauth]/route.ts +5 -0
  13. package/src/app/api/backtest/files/route.ts +60 -0
  14. package/src/app/api/backtest/order-log/[strategy]/[name]/route.ts +47 -0
  15. package/src/app/api/backtest/result/[strategy]/[name]/route.ts +63 -0
  16. package/src/app/api/backtest/test/[strategy]/[name]/route.ts +57 -0
  17. package/src/app/api/cron/route.ts +4 -0
  18. package/src/app/api/derivatives/[symbol]/[interval]/route.ts +57 -0
  19. package/src/app/api/derivatives/summary/route.ts +20 -0
  20. package/src/app/api/files/screenshot/[name]/route.ts +42 -0
  21. package/src/app/api/indicators/route.ts +24 -0
  22. package/src/app/api/kline/[provider]/[symbol]/[interval]/route.ts +123 -0
  23. package/src/app/api/scanner/[provider]/route.ts +41 -0
  24. package/src/app/api/scanner/route.ts +31 -0
  25. package/src/app/api/signal/[symbol]/[signalId]/route.ts +42 -0
  26. package/src/app/api/spread/[symbol]/[interval]/route.ts +57 -0
  27. package/src/app/api/spread/summary/route.ts +20 -0
  28. package/src/app/auth.ts +76 -0
  29. package/src/app/components/Backtest/CompareList/index.tsx +34 -0
  30. package/src/app/components/Backtest/TestCard/Chart/index.tsx +118 -0
  31. package/src/app/components/Backtest/TestCard/Chart/utils/index.ts +81 -0
  32. package/src/app/components/Backtest/TestCard/CompareButton/index.tsx +21 -0
  33. package/src/app/components/Backtest/TestCard/ConfigDrawer/JsonCodeBlock.tsx +46 -0
  34. package/src/app/components/Backtest/TestCard/ConfigDrawer/index.tsx +94 -0
  35. package/src/app/components/Backtest/TestCard/DeleteButton/index.tsx +128 -0
  36. package/src/app/components/Backtest/TestCard/FavoriteIndicator/index.tsx +18 -0
  37. package/src/app/components/Backtest/TestCard/OpenDashboardButton/index.tsx +40 -0
  38. package/src/app/components/Backtest/TestCard/OpenReportButton/index.tsx +24 -0
  39. package/src/app/components/Backtest/TestCard/Root/index.tsx +55 -0
  40. package/src/app/components/Backtest/TestCard/Skeleton/index.tsx +21 -0
  41. package/src/app/components/Backtest/TestCard/Stat/index.tsx +119 -0
  42. package/src/app/components/Backtest/TestCard/Title/index.tsx +84 -0
  43. package/src/app/components/Backtest/TestCard/context.ts +14 -0
  44. package/src/app/components/Backtest/TestCard/index.ts +28 -0
  45. package/src/app/components/Backtest/TestList/index.tsx +124 -0
  46. package/src/app/components/Dashboard/AiDrawer/Message.tsx +34 -0
  47. package/src/app/components/Dashboard/AiDrawer/index.tsx +163 -0
  48. package/src/app/components/Dashboard/KlineChart/figures/backtestFigureTypes.ts +7 -0
  49. package/src/app/components/Dashboard/KlineChart/figures/backtestMarkersPointFigure.ts +76 -0
  50. package/src/app/components/Dashboard/KlineChart/figures/circle.ts +15 -0
  51. package/src/app/components/Dashboard/KlineChart/figures/diamond.ts +25 -0
  52. package/src/app/components/Dashboard/KlineChart/figures/entryLinePointFigure.ts +1 -0
  53. package/src/app/components/Dashboard/KlineChart/figures/entryPointsPointFigure.ts +1 -0
  54. package/src/app/components/Dashboard/KlineChart/figures/entryZonePointFigure.ts +1 -0
  55. package/src/app/components/Dashboard/KlineChart/figures/index.ts +213 -0
  56. package/src/app/components/Dashboard/KlineChart/figures/label.ts +14 -0
  57. package/src/app/components/Dashboard/KlineChart/figures/rectangle.ts +20 -0
  58. package/src/app/components/Dashboard/KlineChart/figures/square.ts +21 -0
  59. package/src/app/components/Dashboard/KlineChart/figures/star.ts +39 -0
  60. package/src/app/components/Dashboard/KlineChart/figures/tradeZonePointFigure.ts +44 -0
  61. package/src/app/components/Dashboard/KlineChart/figures/trendLinePointFigure.ts +37 -0
  62. package/src/app/components/Dashboard/KlineChart/figures/trendLinePointsPointFigure.ts +26 -0
  63. package/src/app/components/Dashboard/KlineChart/figures/triangle.ts +23 -0
  64. package/src/app/components/Dashboard/KlineChart/hooks/index.ts +14 -0
  65. package/src/app/components/Dashboard/KlineChart/hooks/indicatorShared.ts +30 -0
  66. package/src/app/components/Dashboard/KlineChart/hooks/useAtrIndicator.ts +75 -0
  67. package/src/app/components/Dashboard/KlineChart/hooks/useBacktest.ts +533 -0
  68. package/src/app/components/Dashboard/KlineChart/hooks/useBbIndicator.ts +74 -0
  69. package/src/app/components/Dashboard/KlineChart/hooks/useBtcCorrelation.ts +155 -0
  70. package/src/app/components/Dashboard/KlineChart/hooks/useBtcIndicator.ts +185 -0
  71. package/src/app/components/Dashboard/KlineChart/hooks/useEmaIndicator.ts +62 -0
  72. package/src/app/components/Dashboard/KlineChart/hooks/useMaIndicator.ts +62 -0
  73. package/src/app/components/Dashboard/KlineChart/hooks/useManagedIndicator.ts +140 -0
  74. package/src/app/components/Dashboard/KlineChart/hooks/usePluginIndicators.ts +212 -0
  75. package/src/app/components/Dashboard/KlineChart/hooks/useResize.ts +29 -0
  76. package/src/app/components/Dashboard/KlineChart/hooks/useSetup.ts +122 -0
  77. package/src/app/components/Dashboard/KlineChart/hooks/useSignal.ts +85 -0
  78. package/src/app/components/Dashboard/KlineChart/hooks/useSpreadIndicator.ts +243 -0
  79. package/src/app/components/Dashboard/KlineChart/hooks/useSupportResistanceLines.ts +125 -0
  80. package/src/app/components/Dashboard/KlineChart/hooks/useTrendLine.ts +139 -0
  81. package/src/app/components/Dashboard/KlineChart/hooks/useVolIndicator.ts +18 -0
  82. package/src/app/components/Dashboard/KlineChart/hooks/useWmaIndicator.ts +62 -0
  83. package/src/app/components/Dashboard/KlineChart/index.tsx +169 -0
  84. package/src/app/components/Dashboard/KlineChart/styles.ts +70 -0
  85. package/src/app/components/Dashboard/MainChart/index.tsx +35 -0
  86. package/src/app/components/Shared/AppShell.tsx +28 -0
  87. package/src/app/components/Shared/FavoriteButton/index.tsx +23 -0
  88. package/src/app/components/Shared/Filters/Backtest/index.tsx +164 -0
  89. package/src/app/components/Shared/Filters/FavoriteIndicator/index.tsx +18 -0
  90. package/src/app/components/Shared/Filters/Indicators/index.tsx +21 -0
  91. package/src/app/components/Shared/Filters/Interval/index.tsx +31 -0
  92. package/src/app/components/Shared/Filters/Interval/intervals.ts +6 -0
  93. package/src/app/components/Shared/Filters/Provider/index.tsx +32 -0
  94. package/src/app/components/Shared/Filters/Root/index.tsx +28 -0
  95. package/src/app/components/Shared/Filters/Symbol/index.tsx +49 -0
  96. package/src/app/components/Shared/Filters/context.ts +17 -0
  97. package/src/app/components/Shared/Filters/index.ts +17 -0
  98. package/src/app/components/Shared/Sidebar/index.tsx +72 -0
  99. package/src/app/components/UI/ColorMode/index.tsx +112 -0
  100. package/src/app/components/UI/EmptyState/index.tsx +28 -0
  101. package/src/app/components/UI/OverlaySpinner/index.tsx +23 -0
  102. package/src/app/components/UI/Segment/index.tsx +23 -0
  103. package/src/app/components/UI/Select/index.tsx +81 -0
  104. package/src/app/components/UI/SelectWithSearch/index.tsx +104 -0
  105. package/src/app/components/UI/Switcher/index.tsx +24 -0
  106. package/src/app/components/UI/Toaster/index.tsx +45 -0
  107. package/src/app/components/UI/index.ts +8 -0
  108. package/src/app/favicon.ico +0 -0
  109. package/src/app/globals.css +5 -0
  110. package/src/app/layout.tsx +31 -0
  111. package/src/app/page.tsx +14 -0
  112. package/src/app/provider.tsx +39 -0
  113. package/src/app/routes/backtest/[test]/page.tsx +33 -0
  114. package/src/app/routes/backtest/page.tsx +374 -0
  115. package/src/app/routes/dashboard/[provider]/[symbol]/[interval]/page.tsx +124 -0
  116. package/src/app/routes/dashboard/page.tsx +20 -0
  117. package/src/app/routes/derivatives/page.tsx +202 -0
  118. package/src/app/routes/signin/page.tsx +155 -0
  119. package/src/app/store/data.ts +144 -0
  120. package/src/app/store/filters.ts +29 -0
  121. package/src/app/store/index.ts +13 -0
  122. package/src/app/store/indicators.ts +229 -0
  123. package/src/app/store/tests.ts +464 -0
  124. package/src/app/store/tickers.ts +89 -0
  125. package/src/proxy.ts +142 -0
  126. package/tsconfig.json +40 -0
@@ -0,0 +1,229 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import _ from 'lodash';
3
+ import { create } from 'zustand';
4
+ import { persist } from 'zustand/middleware';
5
+ import { Indicators, Items } from '@tradejs/types';
6
+
7
+ const LOCAL_STORAGE_KEY = 'indicators';
8
+
9
+ export interface IndicatorRendererFigure {
10
+ key: string;
11
+ title?: string;
12
+ type?: 'line' | 'bar';
13
+ color?: string;
14
+ lineWidth?: number;
15
+ dashed?: boolean;
16
+ constant?: number;
17
+ }
18
+
19
+ export interface IndicatorRendererConfig {
20
+ indicatorName?: string;
21
+ shortName?: string;
22
+ paneId?: string;
23
+ minHeight?: number;
24
+ figures: IndicatorRendererFigure[];
25
+ }
26
+
27
+ export interface IndicatorRendererDescriptor {
28
+ indicatorId: string;
29
+ renderer: IndicatorRendererConfig;
30
+ }
31
+
32
+ interface IndicatorsState {
33
+ indicators: Indicators;
34
+ indicatorRenderers: Record<string, IndicatorRendererConfig>;
35
+ setEnabledIndicators: (values: string[]) => void;
36
+ upsertIndicators: (items: Indicators) => void;
37
+ setIndicatorRenderers: (items: IndicatorRendererDescriptor[]) => void;
38
+ }
39
+
40
+ const useStore = create<IndicatorsState>()(
41
+ persist(
42
+ (set) => ({
43
+ indicators: [
44
+ {
45
+ id: 'btc',
46
+ label: 'BTC',
47
+ enabled: false,
48
+ },
49
+ {
50
+ id: 'btcCorrelation',
51
+ label: 'BTC Correlation',
52
+ enabled: false,
53
+ },
54
+ {
55
+ id: 'spread',
56
+ label: 'Spread',
57
+ enabled: false,
58
+ },
59
+ {
60
+ id: 'vol',
61
+ label: 'Vol',
62
+ enabled: true,
63
+ },
64
+ {
65
+ id: 'resistant',
66
+ label: 'resistant',
67
+ enabled: false,
68
+ },
69
+ {
70
+ id: 'atr',
71
+ label: 'ATR',
72
+ enabled: false,
73
+ periods: [14],
74
+ },
75
+ {
76
+ id: 'bb',
77
+ label: 'BB',
78
+ enabled: false,
79
+ periods: [20],
80
+ },
81
+ {
82
+ id: 'ma',
83
+ label: 'MA',
84
+ enabled: false,
85
+ periods: [49, 200],
86
+ },
87
+ {
88
+ id: 'ema',
89
+ label: 'EMA',
90
+ enabled: false,
91
+ periods: [49, 200],
92
+ },
93
+ {
94
+ id: 'wma',
95
+ label: 'WMA',
96
+ enabled: false,
97
+ periods: [49, 200],
98
+ },
99
+ ] as Indicators,
100
+ indicatorRenderers: {},
101
+ setEnabledIndicators: (values: string[]) =>
102
+ set((state) => {
103
+ const clonedState = structuredClone(state.indicators);
104
+
105
+ clonedState.forEach((indicator, i) => {
106
+ clonedState[i].enabled = values.includes(indicator.id);
107
+ });
108
+
109
+ return { indicators: clonedState };
110
+ }),
111
+ upsertIndicators: (items: Indicators) =>
112
+ set((state) => {
113
+ if (!items.length) {
114
+ return state;
115
+ }
116
+
117
+ const next = structuredClone(state.indicators);
118
+ const indexById = new Map(
119
+ next.map((indicator, index) => [indicator.id, index]),
120
+ );
121
+
122
+ for (const incoming of items) {
123
+ if (!incoming?.id) continue;
124
+
125
+ const existingIndex = indexById.get(incoming.id);
126
+ if (existingIndex == null) {
127
+ next.push({
128
+ id: incoming.id,
129
+ label: incoming.label,
130
+ enabled: Boolean(incoming.enabled),
131
+ periods: incoming.periods,
132
+ });
133
+ indexById.set(incoming.id, next.length - 1);
134
+ continue;
135
+ }
136
+
137
+ const existing = next[existingIndex];
138
+ next[existingIndex] = {
139
+ ...existing,
140
+ label: incoming.label || existing.label,
141
+ periods: incoming.periods || existing.periods,
142
+ };
143
+ }
144
+
145
+ return { indicators: next };
146
+ }),
147
+ setIndicatorRenderers: (items: IndicatorRendererDescriptor[]) =>
148
+ set(() => {
149
+ const indicatorRenderers = items.reduce<
150
+ Record<string, IndicatorRendererConfig>
151
+ >((acc, item) => {
152
+ if (!item?.indicatorId || !item.renderer) return acc;
153
+ acc[item.indicatorId] = item.renderer;
154
+ return acc;
155
+ }, {});
156
+
157
+ return { indicatorRenderers };
158
+ }),
159
+ }),
160
+ {
161
+ name: LOCAL_STORAGE_KEY,
162
+ },
163
+ ),
164
+ );
165
+
166
+ export const useIndicators = () => {
167
+ const indicators = useStore((s) => s.indicators);
168
+ const setEnabledIndicators = useStore((s) => s.setEnabledIndicators);
169
+ const upsertIndicators = useStore((s) => s.upsertIndicators);
170
+ const indicatorRenderers = useStore((s) => s.indicatorRenderers);
171
+ const setIndicatorRenderers = useStore((s) => s.setIndicatorRenderers);
172
+ const catalogRequestedRef = useRef(false);
173
+
174
+ useEffect(() => {
175
+ if (catalogRequestedRef.current) return;
176
+ catalogRequestedRef.current = true;
177
+
178
+ fetch('/api/indicators')
179
+ .then(async (response) => {
180
+ if (!response.ok) {
181
+ return { data: [], renderers: [] };
182
+ }
183
+ const payload = await response.json();
184
+ return {
185
+ data: Array.isArray(payload?.data)
186
+ ? (payload.data as Indicators)
187
+ : [],
188
+ renderers: Array.isArray(payload?.renderers)
189
+ ? (payload.renderers as IndicatorRendererDescriptor[])
190
+ : [],
191
+ };
192
+ })
193
+ .then(({ data: items, renderers }) => {
194
+ if (items.length) {
195
+ upsertIndicators(items);
196
+ }
197
+ setIndicatorRenderers(renderers);
198
+ })
199
+ .catch(() => undefined);
200
+ }, [upsertIndicators, setIndicatorRenderers]);
201
+
202
+ const selectedIndicators = useMemo(
203
+ () => indicators.filter((ind) => ind.enabled).map(({ id }) => id),
204
+ [indicators],
205
+ );
206
+
207
+ const indicatorsItems = useMemo(
208
+ () =>
209
+ indicators.map(({ id, label }) => ({
210
+ value: id,
211
+ label,
212
+ })),
213
+ [indicators],
214
+ ) as Items;
215
+
216
+ const indicatorsByKey = useMemo(
217
+ () => _.keyBy(indicators, 'id'),
218
+ [indicators],
219
+ );
220
+
221
+ return {
222
+ indicators,
223
+ selectedIndicators,
224
+ indicatorsItems,
225
+ indicatorsByKey,
226
+ indicatorRenderers,
227
+ setEnabledIndicators,
228
+ };
229
+ };
@@ -0,0 +1,464 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { create } from 'zustand';
3
+ import { persist } from 'zustand/middleware';
4
+ import _ from 'lodash';
5
+ import { del, get, set } from 'idb-keyval';
6
+ import { getBacktest, getBacktestFiles, getOrderLog } from '@actions/backtest';
7
+ import {
8
+ TestResult,
9
+ TestCompareList,
10
+ OrderLogData,
11
+ OnChangeCompare,
12
+ Items,
13
+ } from '@tradejs/types';
14
+ import { delay } from '@tradejs/core/async';
15
+ import { parseTestName } from '@tradejs/core/backtest';
16
+
17
+ const COMPARE_LOCAL_STORAGE_KEY = 'compare';
18
+ const FAVORITE_LOCAL_STORAGE_KEY = 'favorite';
19
+
20
+ const COLORS = [
21
+ 'purple',
22
+ 'orange',
23
+ 'red',
24
+ 'pink',
25
+ 'cyan',
26
+ 'yellow',
27
+ 'blue',
28
+ 'green',
29
+ ];
30
+
31
+ interface BacktestState {
32
+ backtests: Map<string, OrderLogData | null>;
33
+ setBacktest: (id: string, backtest: OrderLogData) => void;
34
+ removeBacktest: (id: string) => void;
35
+ }
36
+
37
+ const useDataStore = create<BacktestState>((set) => ({
38
+ backtests: new Map<string, OrderLogData | null>(),
39
+ setBacktest: (id, backtest) =>
40
+ set(({ backtests }) => {
41
+ const next = new Map(backtests);
42
+ next.set(id, backtest);
43
+
44
+ return {
45
+ backtests: next,
46
+ };
47
+ }),
48
+ removeBacktest: (id) =>
49
+ set(({ backtests }) => {
50
+ const next = new Map(backtests);
51
+ next.delete(id);
52
+
53
+ return {
54
+ backtests: next,
55
+ };
56
+ }),
57
+ }));
58
+
59
+ interface FavotiteTestsState {
60
+ tests: {
61
+ testName: string;
62
+ netProfit: number;
63
+ }[];
64
+ toggleFavorite: (testName: string, netProfit: number) => void;
65
+ removeFavorite: (testName: string) => void;
66
+ }
67
+
68
+ const useFavoriteTetstsStore = create<FavotiteTestsState>()(
69
+ persist(
70
+ (set) => ({
71
+ tests: [],
72
+ toggleFavorite: (testName, netProfit) =>
73
+ set(({ tests }) => {
74
+ if (tests.some((t) => t.testName === testName)) {
75
+ return {
76
+ tests: tests.filter((t) => t.testName !== testName),
77
+ };
78
+ }
79
+
80
+ return {
81
+ tests: [
82
+ ...tests,
83
+ {
84
+ testName,
85
+ netProfit,
86
+ },
87
+ ],
88
+ };
89
+ }),
90
+ removeFavorite: (testName) =>
91
+ set(({ tests }) => ({
92
+ tests: tests.filter((t) => t.testName !== testName),
93
+ })),
94
+ }),
95
+ {
96
+ name: FAVORITE_LOCAL_STORAGE_KEY,
97
+ },
98
+ ),
99
+ );
100
+
101
+ interface TestListState {
102
+ tests: Items;
103
+ setTest: (tests: Items) => void;
104
+ removeTest: (testName: string) => void;
105
+ }
106
+
107
+ const useTestListStore = create<TestListState>((set) => ({
108
+ tests: [],
109
+ setTest: (tests) =>
110
+ set(() => ({
111
+ tests,
112
+ })),
113
+ removeTest: (testName) =>
114
+ set(({ tests }) => ({
115
+ tests: tests.filter((t) => t.value !== testName),
116
+ })),
117
+ }));
118
+
119
+ interface TestsCompareState {
120
+ compareList: {
121
+ testName: string;
122
+ color: string;
123
+ }[];
124
+ onChangeCompare: OnChangeCompare;
125
+ removeFromCompare: (testName: string) => void;
126
+ }
127
+
128
+ const useTestsCompareStore = create<TestsCompareState>()(
129
+ persist(
130
+ (set) => ({
131
+ compareList: [],
132
+ onChangeCompare: (testName) =>
133
+ set(({ compareList }) => {
134
+ if (!compareList.some((t) => t.testName === testName)) {
135
+ const newState = [
136
+ ...compareList,
137
+ { testName, color: COLORS[compareList.length] },
138
+ ];
139
+ if (newState.length > COLORS.length) {
140
+ newState.shift();
141
+ }
142
+ return { compareList: newState };
143
+ }
144
+ return {
145
+ compareList: compareList.filter((t) => t.testName !== testName),
146
+ };
147
+ }),
148
+ removeFromCompare: (testName) =>
149
+ set(({ compareList }) => ({
150
+ compareList: compareList.filter((t) => t.testName !== testName),
151
+ })),
152
+ }),
153
+ {
154
+ name: COMPARE_LOCAL_STORAGE_KEY,
155
+ },
156
+ ),
157
+ );
158
+
159
+ interface TestsState {
160
+ tests: Map<string, TestResult | null>;
161
+ setTest: (test: TestResult) => void;
162
+ removeTest: (testName: string) => void;
163
+ }
164
+
165
+ const useTestsStore = create<TestsState>((set) => ({
166
+ tests: new Map<string, TestResult | null>(),
167
+ setTest: (testResult) =>
168
+ set(({ tests }) => {
169
+ const next = new Map(tests);
170
+ next.set(testResult.test.name, testResult);
171
+
172
+ return {
173
+ tests: next,
174
+ };
175
+ }),
176
+ removeTest: (testName) =>
177
+ set(({ tests }) => {
178
+ const next = new Map(tests);
179
+ next.delete(testName);
180
+
181
+ return {
182
+ tests: next,
183
+ };
184
+ }),
185
+ }));
186
+
187
+ export const useFavoriteTests = () => {
188
+ const favotites = useFavoriteTetstsStore((s) => s.tests);
189
+ const toggleFavorite = useFavoriteTetstsStore((s) => s.toggleFavorite);
190
+ const removeFavorite = useFavoriteTetstsStore((s) => s.removeFavorite);
191
+ const checkIsFavorite = (testName: string) =>
192
+ favotites.some((t) => t.testName === testName);
193
+
194
+ const favoriteItems: Items = favotites.map((t) => {
195
+ const { symbol, testId } = parseTestName(t.testName);
196
+
197
+ return {
198
+ value: t.testName,
199
+ label: `${symbol}_${testId}`,
200
+ description: `${t.netProfit}$`,
201
+ data: {
202
+ netProfit: t.netProfit || 0,
203
+ },
204
+ };
205
+ });
206
+
207
+ return {
208
+ favotites,
209
+ favoriteItems,
210
+ toggleFavorite,
211
+ removeFavorite,
212
+ checkIsFavorite,
213
+ };
214
+ };
215
+
216
+ interface TestListProps {
217
+ symbol?: string;
218
+ }
219
+
220
+ export const useTestList = (filters: TestListProps = {}) => {
221
+ const [loadding, setLoading] = useState(false);
222
+ const [fulFilled, setFulfilled] = useState(false);
223
+ const [error, setError] = useState<unknown>(null);
224
+ const tests = useTestListStore((s) => s.tests);
225
+ const setTest = useTestListStore((s) => s.setTest);
226
+ const { favoriteItems } = useFavoriteTests();
227
+
228
+ const testStrategyMap = new Map(
229
+ tests
230
+ .filter((item) => typeof item.data?.strategyName === 'string')
231
+ .map((item) => [item.value, item.data?.strategyName as string]),
232
+ );
233
+
234
+ const testItems: Items = _.chain([...favoriteItems, ...tests])
235
+ .map((item) => {
236
+ if (item.data?.strategyName) {
237
+ return item;
238
+ }
239
+
240
+ const strategyName = testStrategyMap.get(item.value);
241
+ if (!strategyName) {
242
+ return item;
243
+ }
244
+
245
+ return {
246
+ ...item,
247
+ data: {
248
+ ...(item.data || {}),
249
+ strategyName,
250
+ },
251
+ };
252
+ })
253
+ .filter((t) => {
254
+ const { symbol } = parseTestName(t.value);
255
+
256
+ if (filters.symbol && filters.symbol !== symbol) {
257
+ return false;
258
+ }
259
+
260
+ return true;
261
+ })
262
+ .sortBy((t) => -t.data?.netProfit! || 0)
263
+ .unionBy((t) => t.value)
264
+ .value();
265
+
266
+ const noData = _.isEmpty(testItems);
267
+
268
+ useEffect(() => {
269
+ const loadData = async () => {
270
+ try {
271
+ setLoading(true);
272
+ await delay();
273
+ const newTests = await getBacktestFiles();
274
+ setLoading(false);
275
+ setFulfilled(true);
276
+
277
+ setTest(newTests);
278
+ } catch (err) {
279
+ setError(err);
280
+ }
281
+ };
282
+
283
+ void loadData();
284
+ }, [setTest]);
285
+
286
+ return {
287
+ loadding,
288
+ fulFilled,
289
+ error,
290
+ noData,
291
+ tests: testItems,
292
+ };
293
+ };
294
+
295
+ export const useTest = (testName: string) => {
296
+ const testResult = useTestsStore((s) => s.tests.get(testName));
297
+ const setTest = useTestsStore((s) => s.setTest);
298
+ const tests = useTestListStore((s) => s.tests);
299
+ const setTestList = useTestListStore((s) => s.setTest);
300
+ const testItem = tests.find((item) => item.value === testName);
301
+ const strategyName = testItem?.data?.strategyName as string | undefined;
302
+
303
+ useEffect(() => {
304
+ const loadData = async () => {
305
+ if (!_.isEmpty(testResult)) {
306
+ return;
307
+ }
308
+
309
+ const key = `test-${testName}`;
310
+
311
+ const cachedResult = (await get(key)) as TestResult | null;
312
+
313
+ if (!_.isEmpty(cachedResult)) {
314
+ setTest(cachedResult);
315
+
316
+ return;
317
+ }
318
+
319
+ let resolvedStrategy = strategyName;
320
+ if (!resolvedStrategy) {
321
+ const newTests = await getBacktestFiles();
322
+ setTestList(newTests);
323
+ resolvedStrategy = newTests.find((item) => item.value === testName)
324
+ ?.data?.strategyName as string | undefined;
325
+ }
326
+
327
+ const test = await getBacktest(testName, resolvedStrategy);
328
+
329
+ if (!test) {
330
+ return;
331
+ }
332
+
333
+ setTest(test);
334
+
335
+ await set(key, test);
336
+ };
337
+
338
+ void loadData();
339
+ }, [setTest, setTestList, strategyName, testName, testResult]);
340
+
341
+ return testResult;
342
+ };
343
+
344
+ export const useTestsCompare = () => {
345
+ const tests = useTestsStore((s) => s.tests);
346
+ const setTest = useTestsStore((s) => s.setTest);
347
+ const compareList = useTestsCompareStore((s) => s.compareList);
348
+ const onChangeCompare = useTestsCompareStore((s) => s.onChangeCompare);
349
+ const removeFromCompare = useTestsCompareStore((s) => s.removeFromCompare);
350
+
351
+ useEffect(() => {
352
+ const loadData = async () => {
353
+ for (const { testName } of compareList) {
354
+ if (tests.has(testName)) {
355
+ continue;
356
+ }
357
+
358
+ const key = `test-${testName}`;
359
+ const cachedResult = (await get(key)) as TestResult | null;
360
+
361
+ if (!_.isEmpty(cachedResult)) {
362
+ setTest(cachedResult);
363
+ }
364
+ }
365
+ };
366
+
367
+ void loadData();
368
+ }, [compareList, setTest, tests]);
369
+
370
+ const checkIsCompared = (testName: string) =>
371
+ compareList.some((s) => s.testName === testName);
372
+
373
+ return {
374
+ compareList: compareList
375
+ .map(({ testName, color }) => {
376
+ const testResult = tests.get(testName);
377
+ return {
378
+ testResult,
379
+ color,
380
+ };
381
+ })
382
+ .filter((c) => !!c.testResult) as TestCompareList,
383
+ checkIsCompared,
384
+ onChangeCompare,
385
+ removeFromCompare,
386
+ };
387
+ };
388
+
389
+ export const useBacktestMutations = () => {
390
+ const removeTestFromList = useTestListStore((s) => s.removeTest);
391
+ const removeTestResult = useTestsStore((s) => s.removeTest);
392
+ const removeBacktest = useDataStore((s) => s.removeBacktest);
393
+ const removeFavorite = useFavoriteTetstsStore((s) => s.removeFavorite);
394
+ const removeFromCompare = useTestsCompareStore((s) => s.removeFromCompare);
395
+
396
+ const removeBacktestTest = async (testName: string) => {
397
+ removeTestFromList(testName);
398
+ removeTestResult(testName);
399
+ removeBacktest(testName);
400
+ removeFavorite(testName);
401
+ removeFromCompare(testName);
402
+
403
+ await Promise.all([del(`test-${testName}`), del(`backtest-${testName}`)]);
404
+ };
405
+
406
+ return {
407
+ removeBacktestTest,
408
+ };
409
+ };
410
+
411
+ export const useBacktest = (id: string | undefined) => {
412
+ const backtest = useDataStore((s) => s.backtests.get(id || 'empty'));
413
+ const setBacktest = useDataStore((s) => s.setBacktest);
414
+ const [loading, setLoading] = useState(false);
415
+ const tests = useTestListStore((s) => s.tests);
416
+ const setTestList = useTestListStore((s) => s.setTest);
417
+ const testItem = tests.find((item) => item.value === id);
418
+ const strategyName = testItem?.data?.strategyName as string | undefined;
419
+
420
+ useEffect(() => {
421
+ const updateBacktest = async () => {
422
+ if (!id) {
423
+ return;
424
+ }
425
+
426
+ const key = `backtest-${id}`;
427
+
428
+ setLoading(true);
429
+
430
+ const cachedResult = (await get(key)) as OrderLogData | null;
431
+
432
+ if (cachedResult && !_.isEmpty(cachedResult)) {
433
+ setBacktest(id, cachedResult);
434
+ setLoading(false);
435
+
436
+ return;
437
+ }
438
+
439
+ let resolvedStrategy = strategyName;
440
+ if (!resolvedStrategy) {
441
+ const newTests = await getBacktestFiles();
442
+ setTestList(newTests);
443
+ resolvedStrategy = newTests.find((item) => item.value === id)?.data
444
+ ?.strategyName as string | undefined;
445
+ }
446
+
447
+ const backtestData = await getOrderLog(id, resolvedStrategy);
448
+
449
+ if (backtestData && !_.isEmpty(backtestData)) {
450
+ setBacktest(id, backtestData);
451
+ await set(key, backtestData);
452
+ }
453
+
454
+ setLoading(false);
455
+ };
456
+
457
+ void updateBacktest();
458
+ }, [id, setBacktest, setTestList, strategyName]);
459
+
460
+ return {
461
+ backtest: backtest || [],
462
+ loading,
463
+ };
464
+ };