@tradejs/app 1.0.5 → 1.0.8
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/bin/tradejs-app.mjs +129 -20
- package/package.json +13 -12
- package/public/auth-bg.jpg +0 -0
- package/public/next.svg +1 -0
- package/public/og-image-source.svg +91 -0
- package/public/og-image.png +0 -0
- package/public/vercel.svg +1 -0
- package/src/app/api/ai/route.ts +84 -20
- package/src/app/api/backtest/files/route.ts +12 -1
- package/src/app/api/backtest/test/[strategy]/[name]/route.ts +18 -1
- package/src/app/api/kline/[provider]/[symbol]/[interval]/route.ts +357 -29
- package/src/app/api/scanner/[provider]/route.ts +7 -1
- package/src/app/api/scanner/route.ts +7 -1
- package/src/app/api/signal/[symbol]/[signalId]/route.ts +6 -0
- package/src/app/api/user/settings/route.ts +244 -0
- package/src/app/components/Dashboard/AiDrawer/index.tsx +38 -51
- package/src/app/components/Shared/Filters/Backtest/index.tsx +12 -5
- package/src/app/components/Shared/Filters/Root/index.tsx +12 -1
- package/src/app/components/Shared/Filters/Symbol/index.tsx +14 -19
- package/src/app/components/Shared/Filters/context.ts +2 -0
- package/src/app/components/Shared/Sidebar/AccountSettingsDrawer.tsx +948 -0
- package/src/app/components/Shared/Sidebar/index.tsx +13 -9
- package/src/app/components/UI/ColorMode/index.tsx +62 -15
- package/src/app/components/UI/Select/index.tsx +3 -0
- package/src/app/components/UI/SelectWithSearch/index.tsx +3 -0
- package/src/app/globals.css +11 -0
- package/src/app/layout.tsx +50 -11
- package/src/app/lib/currentUser.ts +27 -0
- package/src/app/lib/klineWindow.ts +17 -0
- package/src/app/routes/dashboard/[provider]/[symbol]/[interval]/page.tsx +10 -2
- package/src/app/routes/signin/page.tsx +11 -2
- package/src/app/store/ai.ts +174 -0
- package/src/app/store/data.ts +219 -88
- package/src/app/store/index.ts +1 -0
- package/src/app/store/tests.ts +96 -9
- package/src/app/store/tickers.ts +113 -17
- package/src/proxy.ts +23 -50
package/src/app/store/data.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { useEffect,
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { create } from 'zustand';
|
|
3
|
-
import _ from 'lodash';
|
|
4
3
|
import { get, set } from 'idb-keyval';
|
|
5
4
|
import { useSearchParams } from 'next/navigation';
|
|
6
5
|
import { KlineChartData, Interval, Filters, Provider } from '@tradejs/types';
|
|
7
6
|
import { kline } from '@actions/kline';
|
|
8
7
|
import { isWrongData, mergeData } from '@tradejs/core/data';
|
|
8
|
+
import { normalizeEndToIntervalBoundary } from '@app/lib/klineWindow';
|
|
9
9
|
|
|
10
10
|
interface DataState {
|
|
11
11
|
data: Map<string, KlineChartData | null>;
|
|
@@ -17,8 +17,61 @@ interface DataState {
|
|
|
17
17
|
) => void;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
interface DataRequest {
|
|
21
|
+
key: string;
|
|
22
|
+
provider: Provider;
|
|
23
|
+
symbol: string;
|
|
24
|
+
interval: Interval;
|
|
25
|
+
start: number;
|
|
26
|
+
end: number;
|
|
27
|
+
cacheBucketEnd: number;
|
|
28
|
+
cacheOnly: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const MIN_CACHED_CANDLES = 2;
|
|
32
|
+
|
|
33
|
+
const getKey = (provider: Provider, symbol: string, interval: Interval) =>
|
|
34
|
+
`${provider}_${symbol}_${interval}`;
|
|
35
|
+
|
|
36
|
+
const getProvider = (provider?: Provider): Provider => provider || 'bybit';
|
|
37
|
+
|
|
38
|
+
const toRequest = (filters: Filters, cacheOnly: boolean): DataRequest => {
|
|
39
|
+
const provider = getProvider(filters.provider);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
key: getKey(provider, filters.symbol, filters.interval),
|
|
43
|
+
provider,
|
|
44
|
+
symbol: filters.symbol,
|
|
45
|
+
interval: filters.interval,
|
|
46
|
+
start: filters.start,
|
|
47
|
+
end: filters.end,
|
|
48
|
+
cacheBucketEnd: normalizeEndToIntervalBoundary(
|
|
49
|
+
filters.end,
|
|
50
|
+
filters.interval,
|
|
51
|
+
),
|
|
52
|
+
cacheOnly,
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const getRequestKey = ({
|
|
57
|
+
key,
|
|
58
|
+
start,
|
|
59
|
+
cacheBucketEnd,
|
|
60
|
+
cacheOnly,
|
|
61
|
+
}: DataRequest) =>
|
|
62
|
+
`${key}_${start}_${cacheBucketEnd}_${cacheOnly ? 'cache' : 'live'}`;
|
|
63
|
+
|
|
64
|
+
const filterDataToWindow = (
|
|
65
|
+
data: KlineChartData,
|
|
66
|
+
start: number,
|
|
67
|
+
end: number,
|
|
68
|
+
): KlineChartData =>
|
|
69
|
+
data.filter((candle) => candle.timestamp >= start && candle.timestamp <= end);
|
|
70
|
+
|
|
71
|
+
const hasContinuityData = (data: KlineChartData) =>
|
|
72
|
+
data.length > MIN_CACHED_CANDLES;
|
|
73
|
+
|
|
74
|
+
const inFlightRequests = new Map<string, Promise<KlineChartData>>();
|
|
22
75
|
|
|
23
76
|
const useDataStore = create<DataState>((set) => ({
|
|
24
77
|
data: new Map<string, KlineChartData | null>(),
|
|
@@ -26,7 +79,7 @@ const useDataStore = create<DataState>((set) => ({
|
|
|
26
79
|
set(({ data }) => {
|
|
27
80
|
const next = new Map(data);
|
|
28
81
|
|
|
29
|
-
next.set(getKey(
|
|
82
|
+
next.set(getKey(provider, symbol, interval), newData);
|
|
30
83
|
|
|
31
84
|
return {
|
|
32
85
|
data: next,
|
|
@@ -34,111 +87,189 @@ const useDataStore = create<DataState>((set) => ({
|
|
|
34
87
|
}),
|
|
35
88
|
}));
|
|
36
89
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const prevKey = useRef(key);
|
|
40
|
-
const retried = useRef(false);
|
|
41
|
-
const [fulfilled, setFulfilled] = useState(false);
|
|
42
|
-
const storedData = useDataStore((s) => s.data.get(key));
|
|
43
|
-
const setData = useDataStore((s) => s.setData);
|
|
90
|
+
const loadCachedData = async (key: string) =>
|
|
91
|
+
((await get(key)) as KlineChartData | null) ?? [];
|
|
44
92
|
|
|
45
|
-
|
|
46
|
-
|
|
93
|
+
const clearCachedData = async (key: string) => {
|
|
94
|
+
await set(key, []);
|
|
95
|
+
};
|
|
47
96
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
97
|
+
const getFetchStart = (start: number, data: KlineChartData) =>
|
|
98
|
+
Math.max(
|
|
99
|
+
start,
|
|
100
|
+
hasContinuityData(data) ? data[data.length - 2]?.timestamp || 0 : 0,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const requestKline = async (
|
|
104
|
+
request: Pick<
|
|
105
|
+
DataRequest,
|
|
106
|
+
'provider' | 'symbol' | 'interval' | 'end' | 'cacheOnly'
|
|
107
|
+
> & { start: number },
|
|
108
|
+
) =>
|
|
109
|
+
kline({
|
|
110
|
+
provider: request.provider,
|
|
111
|
+
symbol: request.symbol,
|
|
112
|
+
interval: request.interval,
|
|
113
|
+
start: request.start,
|
|
114
|
+
end: request.end,
|
|
115
|
+
cacheOnly: request.cacheOnly,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const loadCurrentData = async ({
|
|
119
|
+
key,
|
|
120
|
+
symbol,
|
|
121
|
+
interval,
|
|
122
|
+
}: Pick<DataRequest, 'key' | 'symbol' | 'interval'>) => {
|
|
123
|
+
let currentData = [
|
|
124
|
+
...(useDataStore.getState().data.get(key) ?? []),
|
|
125
|
+
] as KlineChartData;
|
|
126
|
+
|
|
127
|
+
if (currentData.length < MIN_CACHED_CANDLES) {
|
|
128
|
+
const cachedData = await loadCachedData(key);
|
|
129
|
+
|
|
130
|
+
if (hasContinuityData(cachedData)) {
|
|
131
|
+
currentData = [...cachedData];
|
|
53
132
|
}
|
|
133
|
+
}
|
|
54
134
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
let currentData = [...(storedData ?? [])];
|
|
135
|
+
if (hasContinuityData(currentData) && isWrongData(interval, currentData)) {
|
|
136
|
+
console.warn('Wrong kline continuity, drop cache', symbol, interval);
|
|
137
|
+
await clearCachedData(key);
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
64
140
|
|
|
65
|
-
|
|
66
|
-
|
|
141
|
+
return currentData;
|
|
142
|
+
};
|
|
67
143
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
144
|
+
const mergeFreshData = async (
|
|
145
|
+
dataRequest: DataRequest,
|
|
146
|
+
currentData: KlineChartData,
|
|
147
|
+
) => {
|
|
148
|
+
const incrementalData = await requestKline({
|
|
149
|
+
...dataRequest,
|
|
150
|
+
start: getFetchStart(dataRequest.start, currentData),
|
|
151
|
+
});
|
|
72
152
|
|
|
73
|
-
|
|
74
|
-
console.warn('Wrong kline continuity, drop cache', symbol, interval);
|
|
75
|
-
currentData = [];
|
|
76
|
-
set(key, []);
|
|
77
|
-
}
|
|
153
|
+
const mergedData = mergeData(currentData, incrementalData);
|
|
78
154
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
155
|
+
if (
|
|
156
|
+
dataRequest.cacheOnly ||
|
|
157
|
+
!hasContinuityData(mergedData) ||
|
|
158
|
+
!isWrongData(dataRequest.interval, mergedData)
|
|
159
|
+
) {
|
|
160
|
+
return mergedData;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.warn(
|
|
164
|
+
'Wrong kline continuity after merge, refetch full',
|
|
165
|
+
dataRequest.symbol,
|
|
166
|
+
dataRequest.interval,
|
|
167
|
+
);
|
|
168
|
+
await clearCachedData(dataRequest.key);
|
|
169
|
+
|
|
170
|
+
const fullData = await requestKline({
|
|
171
|
+
...dataRequest,
|
|
172
|
+
start: dataRequest.start,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return mergeData([], fullData);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const persistData = async (
|
|
179
|
+
{
|
|
180
|
+
provider,
|
|
181
|
+
symbol,
|
|
182
|
+
interval,
|
|
183
|
+
key,
|
|
184
|
+
}: Pick<DataRequest, 'provider' | 'symbol' | 'interval' | 'key'>,
|
|
185
|
+
data: KlineChartData,
|
|
186
|
+
) => {
|
|
187
|
+
useDataStore.getState().setData(provider, symbol, interval, data);
|
|
188
|
+
await set(key, data);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const fetchAndStoreData = async (dataRequest: DataRequest) => {
|
|
192
|
+
const requestKey = getRequestKey(dataRequest);
|
|
193
|
+
const existingRequest = inFlightRequests.get(requestKey);
|
|
194
|
+
|
|
195
|
+
if (existingRequest) {
|
|
196
|
+
return existingRequest;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const pendingRequest = (async () => {
|
|
200
|
+
const currentData = await loadCurrentData(dataRequest);
|
|
201
|
+
const finalData = await mergeFreshData(dataRequest, currentData);
|
|
202
|
+
await persistData(dataRequest, finalData);
|
|
203
|
+
|
|
204
|
+
return finalData;
|
|
205
|
+
})().finally(() => {
|
|
206
|
+
inFlightRequests.delete(requestKey);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
inFlightRequests.set(requestKey, pendingRequest);
|
|
210
|
+
|
|
211
|
+
return pendingRequest;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export const useData = (filters: Filters) => {
|
|
215
|
+
const searchParams = useSearchParams();
|
|
216
|
+
const cacheOnly = Boolean(searchParams.get('cacheOnly')) ?? false;
|
|
217
|
+
const { end, interval, provider, start, symbol } = filters;
|
|
218
|
+
const dataRequest = useMemo(
|
|
219
|
+
() =>
|
|
220
|
+
toRequest(
|
|
221
|
+
{
|
|
222
|
+
end,
|
|
106
223
|
interval,
|
|
107
|
-
);
|
|
108
|
-
retried.current = true;
|
|
109
|
-
set(key, []);
|
|
110
|
-
const refetchData = await kline({
|
|
111
224
|
provider,
|
|
112
|
-
symbol,
|
|
113
|
-
interval,
|
|
114
225
|
start,
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
226
|
+
symbol,
|
|
227
|
+
} as Filters,
|
|
228
|
+
cacheOnly,
|
|
229
|
+
),
|
|
230
|
+
[cacheOnly, end, interval, provider, start, symbol],
|
|
231
|
+
);
|
|
232
|
+
const requestKey = useMemo(() => getRequestKey(dataRequest), [dataRequest]);
|
|
233
|
+
const [fulfilledRequestKey, setFulfilledRequestKey] = useState<string | null>(
|
|
234
|
+
null,
|
|
235
|
+
);
|
|
236
|
+
const storedData = useDataStore((s) => s.data.get(dataRequest.key));
|
|
237
|
+
const windowedData = useMemo(
|
|
238
|
+
() =>
|
|
239
|
+
filterDataToWindow(storedData ?? [], dataRequest.start, dataRequest.end),
|
|
240
|
+
[dataRequest.end, dataRequest.start, storedData],
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const fulfilled = fulfilledRequestKey === requestKey;
|
|
244
|
+
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
let cancelled = false;
|
|
247
|
+
|
|
248
|
+
const updateData = async () => {
|
|
249
|
+
if (!dataRequest.symbol) {
|
|
250
|
+
if (!cancelled) {
|
|
251
|
+
setFulfilledRequestKey(requestKey);
|
|
122
252
|
}
|
|
123
|
-
set(key, cleaned);
|
|
124
253
|
return;
|
|
125
254
|
}
|
|
126
255
|
|
|
127
|
-
|
|
256
|
+
await fetchAndStoreData(dataRequest);
|
|
128
257
|
|
|
129
|
-
if (!
|
|
130
|
-
|
|
258
|
+
if (!cancelled) {
|
|
259
|
+
setFulfilledRequestKey(requestKey);
|
|
131
260
|
}
|
|
132
|
-
|
|
133
|
-
set(key, finalData);
|
|
134
261
|
};
|
|
135
262
|
|
|
136
263
|
void updateData();
|
|
137
|
-
|
|
264
|
+
|
|
265
|
+
return () => {
|
|
266
|
+
cancelled = true;
|
|
267
|
+
};
|
|
268
|
+
}, [dataRequest, requestKey]);
|
|
138
269
|
|
|
139
270
|
return {
|
|
140
|
-
key,
|
|
141
|
-
data:
|
|
271
|
+
key: dataRequest.key,
|
|
272
|
+
data: windowedData,
|
|
142
273
|
fulfilled,
|
|
143
274
|
};
|
|
144
275
|
};
|
package/src/app/store/index.ts
CHANGED
package/src/app/store/tests.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { parseTestName } from '@tradejs/core/backtest';
|
|
|
16
16
|
|
|
17
17
|
const COMPARE_LOCAL_STORAGE_KEY = 'compare';
|
|
18
18
|
const FAVORITE_LOCAL_STORAGE_KEY = 'favorite';
|
|
19
|
+
const BACKTEST_FILES_CACHE_KEY = 'backtest-files';
|
|
20
|
+
const BACKTEST_FILES_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
19
21
|
|
|
20
22
|
const COLORS = [
|
|
21
23
|
'purple',
|
|
@@ -28,6 +30,14 @@ const COLORS = [
|
|
|
28
30
|
'green',
|
|
29
31
|
];
|
|
30
32
|
|
|
33
|
+
type BacktestFilesCacheRecord = {
|
|
34
|
+
savedAt: number;
|
|
35
|
+
items: Items;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const isFresh = (savedAt: number) =>
|
|
39
|
+
Date.now() - savedAt < BACKTEST_FILES_CACHE_TTL_MS;
|
|
40
|
+
|
|
31
41
|
interface BacktestState {
|
|
32
42
|
backtests: Map<string, OrderLogData | null>;
|
|
33
43
|
setBacktest: (id: string, backtest: OrderLogData) => void;
|
|
@@ -100,15 +110,26 @@ const useFavoriteTetstsStore = create<FavotiteTestsState>()(
|
|
|
100
110
|
|
|
101
111
|
interface TestListState {
|
|
102
112
|
tests: Items;
|
|
103
|
-
|
|
113
|
+
loadedAt: number;
|
|
114
|
+
inFlight?: Promise<Items>;
|
|
115
|
+
setTest: (tests: Items, loadedAt?: number) => void;
|
|
116
|
+
setInFlight: (request?: Promise<Items>) => void;
|
|
104
117
|
removeTest: (testName: string) => void;
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
const useTestListStore = create<TestListState>((set) => ({
|
|
108
121
|
tests: [],
|
|
109
|
-
|
|
122
|
+
loadedAt: 0,
|
|
123
|
+
inFlight: undefined,
|
|
124
|
+
setTest: (tests, loadedAt = Date.now()) =>
|
|
110
125
|
set(() => ({
|
|
111
126
|
tests,
|
|
127
|
+
loadedAt,
|
|
128
|
+
inFlight: undefined,
|
|
129
|
+
})),
|
|
130
|
+
setInFlight: (request) =>
|
|
131
|
+
set(() => ({
|
|
132
|
+
inFlight: request,
|
|
112
133
|
})),
|
|
113
134
|
removeTest: (testName) =>
|
|
114
135
|
set(({ tests }) => ({
|
|
@@ -184,6 +205,44 @@ const useTestsStore = create<TestsState>((set) => ({
|
|
|
184
205
|
}),
|
|
185
206
|
}));
|
|
186
207
|
|
|
208
|
+
const loadBacktestFilesList = async () => {
|
|
209
|
+
const { tests, loadedAt, inFlight, setInFlight, setTest } =
|
|
210
|
+
useTestListStore.getState();
|
|
211
|
+
|
|
212
|
+
if (loadedAt > 0 && isFresh(loadedAt)) {
|
|
213
|
+
return tests;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (inFlight) {
|
|
217
|
+
return inFlight;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const pending = (async () => {
|
|
221
|
+
const cached = (await get(
|
|
222
|
+
BACKTEST_FILES_CACHE_KEY,
|
|
223
|
+
)) as BacktestFilesCacheRecord | null;
|
|
224
|
+
|
|
225
|
+
if (cached?.savedAt && isFresh(cached.savedAt)) {
|
|
226
|
+
setTest(cached.items, cached.savedAt);
|
|
227
|
+
return cached.items;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const newTests = await getBacktestFiles();
|
|
231
|
+
const savedAt = Date.now();
|
|
232
|
+
setTest(newTests, savedAt);
|
|
233
|
+
await set(BACKTEST_FILES_CACHE_KEY, {
|
|
234
|
+
savedAt,
|
|
235
|
+
items: newTests,
|
|
236
|
+
} satisfies BacktestFilesCacheRecord);
|
|
237
|
+
return newTests;
|
|
238
|
+
})().finally(() => {
|
|
239
|
+
useTestListStore.getState().setInFlight(undefined);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
setInFlight(pending);
|
|
243
|
+
return pending;
|
|
244
|
+
};
|
|
245
|
+
|
|
187
246
|
export const useFavoriteTests = () => {
|
|
188
247
|
const favotites = useFavoriteTetstsStore((s) => s.tests);
|
|
189
248
|
const toggleFavorite = useFavoriteTetstsStore((s) => s.toggleFavorite);
|
|
@@ -215,14 +274,15 @@ export const useFavoriteTests = () => {
|
|
|
215
274
|
|
|
216
275
|
interface TestListProps {
|
|
217
276
|
symbol?: string;
|
|
277
|
+
enabled?: boolean;
|
|
218
278
|
}
|
|
219
279
|
|
|
220
280
|
export const useTestList = (filters: TestListProps = {}) => {
|
|
281
|
+
const { enabled = true } = filters;
|
|
221
282
|
const [loadding, setLoading] = useState(false);
|
|
222
283
|
const [fulFilled, setFulfilled] = useState(false);
|
|
223
284
|
const [error, setError] = useState<unknown>(null);
|
|
224
285
|
const tests = useTestListStore((s) => s.tests);
|
|
225
|
-
const setTest = useTestListStore((s) => s.setTest);
|
|
226
286
|
const { favoriteItems } = useFavoriteTests();
|
|
227
287
|
|
|
228
288
|
const testStrategyMap = new Map(
|
|
@@ -266,22 +326,24 @@ export const useTestList = (filters: TestListProps = {}) => {
|
|
|
266
326
|
const noData = _.isEmpty(testItems);
|
|
267
327
|
|
|
268
328
|
useEffect(() => {
|
|
329
|
+
if (!enabled) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
269
333
|
const loadData = async () => {
|
|
270
334
|
try {
|
|
271
335
|
setLoading(true);
|
|
272
336
|
await delay();
|
|
273
|
-
|
|
337
|
+
await loadBacktestFilesList();
|
|
274
338
|
setLoading(false);
|
|
275
339
|
setFulfilled(true);
|
|
276
|
-
|
|
277
|
-
setTest(newTests);
|
|
278
340
|
} catch (err) {
|
|
279
341
|
setError(err);
|
|
280
342
|
}
|
|
281
343
|
};
|
|
282
344
|
|
|
283
345
|
void loadData();
|
|
284
|
-
}, [
|
|
346
|
+
}, [enabled]);
|
|
285
347
|
|
|
286
348
|
return {
|
|
287
349
|
loadding,
|
|
@@ -289,6 +351,7 @@ export const useTestList = (filters: TestListProps = {}) => {
|
|
|
289
351
|
error,
|
|
290
352
|
noData,
|
|
291
353
|
tests: testItems,
|
|
354
|
+
ensureLoaded: loadBacktestFilesList,
|
|
292
355
|
};
|
|
293
356
|
};
|
|
294
357
|
|
|
@@ -318,7 +381,7 @@ export const useTest = (testName: string) => {
|
|
|
318
381
|
|
|
319
382
|
let resolvedStrategy = strategyName;
|
|
320
383
|
if (!resolvedStrategy) {
|
|
321
|
-
const newTests = await
|
|
384
|
+
const newTests = await loadBacktestFilesList();
|
|
322
385
|
setTestList(newTests);
|
|
323
386
|
resolvedStrategy = newTests.find((item) => item.value === testName)
|
|
324
387
|
?.data?.strategyName as string | undefined;
|
|
@@ -400,6 +463,16 @@ export const useBacktestMutations = () => {
|
|
|
400
463
|
removeFavorite(testName);
|
|
401
464
|
removeFromCompare(testName);
|
|
402
465
|
|
|
466
|
+
const cache = (await get(
|
|
467
|
+
BACKTEST_FILES_CACHE_KEY,
|
|
468
|
+
)) as BacktestFilesCacheRecord | null;
|
|
469
|
+
if (cache?.items) {
|
|
470
|
+
await set(BACKTEST_FILES_CACHE_KEY, {
|
|
471
|
+
...cache,
|
|
472
|
+
items: cache.items.filter((item) => item.value !== testName),
|
|
473
|
+
} satisfies BacktestFilesCacheRecord);
|
|
474
|
+
}
|
|
475
|
+
|
|
403
476
|
await Promise.all([del(`test-${testName}`), del(`backtest-${testName}`)]);
|
|
404
477
|
};
|
|
405
478
|
|
|
@@ -438,7 +511,7 @@ export const useBacktest = (id: string | undefined) => {
|
|
|
438
511
|
|
|
439
512
|
let resolvedStrategy = strategyName;
|
|
440
513
|
if (!resolvedStrategy) {
|
|
441
|
-
const newTests = await
|
|
514
|
+
const newTests = await loadBacktestFilesList();
|
|
442
515
|
setTestList(newTests);
|
|
443
516
|
resolvedStrategy = newTests.find((item) => item.value === id)?.data
|
|
444
517
|
?.strategyName as string | undefined;
|
|
@@ -462,3 +535,17 @@ export const useBacktest = (id: string | undefined) => {
|
|
|
462
535
|
loading,
|
|
463
536
|
};
|
|
464
537
|
};
|
|
538
|
+
|
|
539
|
+
export const resetTestsStoreForTests = () => {
|
|
540
|
+
useDataStore.setState({
|
|
541
|
+
backtests: new Map<string, OrderLogData | null>(),
|
|
542
|
+
});
|
|
543
|
+
useTestListStore.setState({
|
|
544
|
+
tests: [],
|
|
545
|
+
loadedAt: 0,
|
|
546
|
+
inFlight: undefined,
|
|
547
|
+
});
|
|
548
|
+
useTestsStore.setState({
|
|
549
|
+
tests: new Map<string, TestResult | null>(),
|
|
550
|
+
});
|
|
551
|
+
};
|