@tradejs/app 1.0.9 → 1.0.10

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 (119) hide show
  1. package/README.md +7 -2
  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/kline/[provider]/[universe]/[symbol]/[interval]/route.ts +1 -0
  20. package/src/app/api/kline/{[provider]/[symbol]/[interval]/route.ts → handler.ts} +61 -39
  21. package/src/app/api/scanner/[provider]/[universe]/route.ts +1 -0
  22. package/src/app/api/scanner/[provider]/route.ts +30 -12
  23. package/src/app/api/scanner/route.ts +3 -3
  24. package/src/app/api/signal/[symbol]/[signalId]/route.ts +1 -1
  25. package/src/app/api/strategies/ai/[cardId]/route.ts +22 -0
  26. package/src/app/api/strategies/ai/route.ts +139 -0
  27. package/src/app/api/strategies/replay/[cardId]/route.ts +18 -0
  28. package/src/app/api/strategies/replay/route.ts +44 -0
  29. package/src/app/api/strategies/runtime/route.ts +831 -0
  30. package/src/app/api/user/runtime-deployments/[deploymentId]/route.ts +25 -0
  31. package/src/app/api/user/runtime-deployments/route.ts +94 -0
  32. package/src/app/api/user/runtime-strategy-configs/route.ts +241 -0
  33. package/src/app/api/user/settings/route.ts +18 -1
  34. package/src/app/api/user/trading-accounts/[accountId]/route.ts +35 -0
  35. package/src/app/api/user/trading-accounts/route.ts +134 -0
  36. package/src/app/components/Backtest/CompareList/index.tsx +1 -1
  37. package/src/app/components/Backtest/ResultsPageClient.tsx +376 -0
  38. package/src/app/components/Backtest/TestCard/ActionsMenu/index.tsx +123 -0
  39. package/src/app/components/Backtest/TestCard/Chart/index.tsx +1 -1
  40. package/src/app/components/Backtest/TestCard/CompareButton/index.tsx +1 -1
  41. package/src/app/components/Backtest/TestCard/ConfigDrawer/index.tsx +28 -12
  42. package/src/app/components/Backtest/TestCard/DeleteButton/index.tsx +33 -17
  43. package/src/app/components/Backtest/TestCard/FavoriteIndicator/index.tsx +2 -2
  44. package/src/app/components/Backtest/TestCard/OpenDashboardButton/index.tsx +10 -1
  45. package/src/app/components/Backtest/TestCard/OrdersDrawer/index.tsx +128 -0
  46. package/src/app/components/Backtest/TestCard/Root/index.tsx +1 -1
  47. package/src/app/components/Backtest/TestCard/Stat/index.tsx +2 -0
  48. package/src/app/components/Backtest/TestCard/StatDrawer/index.tsx +66 -0
  49. package/src/app/components/Backtest/TestCard/index.ts +6 -2
  50. package/src/app/components/Backtest/TestList/index.tsx +4 -9
  51. package/src/app/components/Dashboard/AiDrawer/index.tsx +1 -1
  52. package/src/app/components/Dashboard/KlineChart/hooks/useBacktest.ts +1 -1
  53. package/src/app/components/Dashboard/KlineChart/hooks/useBbIndicator.ts +10 -5
  54. package/src/app/components/Dashboard/KlineChart/hooks/useBtcCorrelation.ts +1 -1
  55. package/src/app/components/Dashboard/KlineChart/hooks/useBtcIndicator.ts +4 -1
  56. package/src/app/components/Dashboard/KlineChart/hooks/useEmaIndicator.ts +3 -1
  57. package/src/app/components/Dashboard/KlineChart/hooks/usePluginIndicators.ts +1 -1
  58. package/src/app/components/Dashboard/KlineChart/hooks/useSetup.ts +1 -1
  59. package/src/app/components/Dashboard/KlineChart/hooks/useSignal.ts +1 -1
  60. package/src/app/components/Dashboard/KlineChart/hooks/useSpreadIndicator.ts +1 -1
  61. package/src/app/components/Dashboard/KlineChart/hooks/useTrendLine.ts +1 -1
  62. package/src/app/components/Dashboard/KlineChart/hooks/useWmaIndicator.ts +3 -1
  63. package/src/app/components/Dashboard/KlineChart/index.tsx +16 -6
  64. package/src/app/components/Dashboard/MainChart/index.tsx +3 -22
  65. package/src/app/components/Shared/AppShell.tsx +1 -1
  66. package/src/app/components/Shared/BulkSelection/index.tsx +140 -0
  67. package/src/app/components/Shared/BulkSelection/useBulkSelection.ts +97 -0
  68. package/src/app/components/Shared/BulkSelection/utils.ts +126 -0
  69. package/src/app/components/Shared/Filters/Backtest/index.tsx +33 -48
  70. package/src/app/components/Shared/Filters/FavoriteIndicator/index.tsx +5 -3
  71. package/src/app/components/Shared/Filters/Indicators/index.tsx +2 -2
  72. package/src/app/components/Shared/Filters/Interval/index.tsx +1 -1
  73. package/src/app/components/Shared/Filters/Provider/index.tsx +8 -2
  74. package/src/app/components/Shared/Filters/Symbol/index.tsx +4 -3
  75. package/src/app/components/Shared/Filters/Universe/index.tsx +36 -0
  76. package/src/app/components/Shared/Filters/index.ts +2 -0
  77. package/src/app/components/Shared/OrdersDrawer.tsx +629 -0
  78. package/src/app/components/Shared/Sidebar/AccountSettingsDrawer.tsx +55 -28
  79. package/src/app/components/Shared/Sidebar/TradingAccountsPanel.tsx +445 -0
  80. package/src/app/components/Shared/Sidebar/index.tsx +17 -3
  81. package/src/app/components/Strategies/AdvancedMetricsPanel.tsx +727 -0
  82. package/src/app/components/Strategies/RuntimeStrategyCard.tsx +2204 -0
  83. package/src/app/components/Strategies/RuntimeStrategyCardSkeleton.tsx +40 -0
  84. package/src/app/components/Strategies/RuntimeStrategyChart.tsx +121 -0
  85. package/src/app/components/Strategies/RuntimeStrategyConfigDrawer.tsx +458 -0
  86. package/src/app/components/Strategies/StrategySnapshotCard.tsx +2890 -0
  87. package/src/app/components/Strategies/StrategySnapshotChart.tsx +94 -0
  88. package/src/app/components/Strategies/StrategySnapshotList.tsx +82 -0
  89. package/src/app/components/UI/Segment/index.tsx +8 -1
  90. package/src/app/components/UI/Select/index.tsx +24 -13
  91. package/src/app/components/UI/SelectWithSearch/index.tsx +46 -5
  92. package/src/app/layout.tsx +1 -1
  93. package/src/app/lib/backtestJobs.ts +792 -0
  94. package/src/app/lib/cardPageLayout.ts +1 -0
  95. package/src/app/lib/connectorCreator.ts +28 -0
  96. package/src/app/lib/currentUser.ts +1 -1
  97. package/src/app/lib/errorMessage.ts +34 -0
  98. package/src/app/lib/marketDefaults.ts +12 -0
  99. package/src/app/lib/marketRoutes.ts +58 -0
  100. package/src/app/lib/runtimeStrategies.ts +1376 -0
  101. package/src/app/lib/runtimeStrategyConfigForm.ts +86 -0
  102. package/src/app/provider.tsx +1 -1
  103. package/src/app/routes/backtest/page.tsx +1051 -304
  104. package/src/app/routes/dashboard/{[provider]/[symbol]/[interval]/page.tsx → Dashboard.tsx} +35 -15
  105. package/src/app/routes/dashboard/[provider]/[universe]/[symbol]/[interval]/page.tsx +1 -0
  106. package/src/app/routes/dashboard/page.tsx +15 -3
  107. package/src/app/routes/derivatives/page.tsx +1222 -165
  108. package/src/app/routes/strategies/StrategiesPageClient.tsx +593 -0
  109. package/src/app/routes/strategies/[mode]/page.tsx +20 -0
  110. package/src/app/routes/strategies/page.tsx +7 -0
  111. package/src/app/store/ai.ts +1 -1
  112. package/src/app/store/data.ts +73 -17
  113. package/src/app/store/filters.ts +1 -0
  114. package/src/app/store/marketKlineStream.ts +87 -0
  115. package/src/app/store/tests.ts +1 -1
  116. package/src/app/store/tickers.ts +59 -22
  117. package/tsconfig.json +17 -12
  118. package/src/app/components/Backtest/TestCard/OpenReportButton/index.tsx +0 -24
  119. package/src/app/routes/backtest/[test]/page.tsx +0 -33
@@ -0,0 +1,140 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import {
5
+ Button,
6
+ Checkbox,
7
+ CloseButton,
8
+ Dialog,
9
+ Flex,
10
+ Portal,
11
+ Text,
12
+ } from '@chakra-ui/react';
13
+ import { useBulkSelection } from './useBulkSelection';
14
+ import type { BulkCheckboxState } from './utils';
15
+
16
+ interface BulkDeleteToolbarProps {
17
+ selectedCount: number;
18
+ checkboxState: BulkCheckboxState;
19
+ hasSelection: boolean;
20
+ isDeleting: boolean;
21
+ dialogOpen: boolean;
22
+ deleteTitle: ReactNode;
23
+ deleteDescription: ReactNode;
24
+ deleteHint?: ReactNode;
25
+ selectAllLabel?: string;
26
+ selectedLabel?: ReactNode;
27
+ onDialogOpenChange: (open: boolean) => void;
28
+ onToggleAll: (checked: boolean) => void;
29
+ onRequestDelete?: () => void;
30
+ onConfirmDelete: () => void;
31
+ }
32
+
33
+ export const BulkDeleteToolbar = ({
34
+ selectedCount,
35
+ checkboxState,
36
+ hasSelection,
37
+ isDeleting,
38
+ dialogOpen,
39
+ deleteTitle,
40
+ deleteDescription,
41
+ deleteHint = 'This action cannot be undone.',
42
+ selectAllLabel = 'Select filtered items',
43
+ selectedLabel,
44
+ onDialogOpenChange,
45
+ onToggleAll,
46
+ onRequestDelete,
47
+ onConfirmDelete,
48
+ }: BulkDeleteToolbarProps) => (
49
+ <Flex mb={4} gap={4} alignItems="center" w="full" minH="32px">
50
+ <Checkbox.Root
51
+ size="sm"
52
+ colorPalette="teal"
53
+ checked={checkboxState}
54
+ onCheckedChange={(details) => onToggleAll(details.checked === true)}
55
+ >
56
+ <Checkbox.HiddenInput aria-label={selectAllLabel} />
57
+ <Checkbox.Control bg="gray.800" borderColor="gray.500" />
58
+ </Checkbox.Root>
59
+ <Text color="gray.200" fontWeight="semibold">
60
+ {selectedLabel ?? `Selected: ${selectedCount}`}
61
+ </Text>
62
+
63
+ <Button
64
+ type="button"
65
+ size="sm"
66
+ colorPalette="red"
67
+ variant="outline"
68
+ aria-label="Open delete confirmation"
69
+ disabled={!hasSelection || isDeleting}
70
+ onClick={() => {
71
+ if (!hasSelection || isDeleting) {
72
+ return;
73
+ }
74
+
75
+ if (onRequestDelete) {
76
+ onRequestDelete();
77
+ return;
78
+ }
79
+
80
+ onDialogOpenChange(true);
81
+ }}
82
+ >
83
+ Delete
84
+ </Button>
85
+
86
+ <Dialog.Root
87
+ open={dialogOpen}
88
+ onOpenChange={(event) => onDialogOpenChange(event.open)}
89
+ >
90
+ <Portal>
91
+ <Dialog.Backdrop />
92
+ <Dialog.Positioner>
93
+ <Dialog.Content>
94
+ <Dialog.Header>
95
+ <Dialog.Title>{deleteTitle}</Dialog.Title>
96
+ <Dialog.CloseTrigger asChild>
97
+ <CloseButton position="absolute" right="3" top="3" />
98
+ </Dialog.CloseTrigger>
99
+ </Dialog.Header>
100
+ <Dialog.Body>
101
+ <Text fontSize="sm" color="gray.200">
102
+ {deleteDescription}
103
+ </Text>
104
+ {deleteHint ? (
105
+ <Text fontSize="sm" color="gray.400" mt={2}>
106
+ {deleteHint}
107
+ </Text>
108
+ ) : null}
109
+ </Dialog.Body>
110
+ <Dialog.Footer>
111
+ <Dialog.ActionTrigger asChild>
112
+ <Button
113
+ type="button"
114
+ variant="outline"
115
+ size="sm"
116
+ disabled={isDeleting}
117
+ >
118
+ Cancel
119
+ </Button>
120
+ </Dialog.ActionTrigger>
121
+ <Button
122
+ type="button"
123
+ colorPalette="red"
124
+ size="sm"
125
+ aria-label="Confirm delete"
126
+ onClick={onConfirmDelete}
127
+ loading={isDeleting}
128
+ >
129
+ Delete
130
+ </Button>
131
+ </Dialog.Footer>
132
+ </Dialog.Content>
133
+ </Dialog.Positioner>
134
+ </Portal>
135
+ </Dialog.Root>
136
+ </Flex>
137
+ );
138
+
139
+ export { useBulkSelection };
140
+ export type { BulkCheckboxState };
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from 'react';
4
+ import {
5
+ getBulkSelectionState,
6
+ pruneSelection,
7
+ removeSelectedIds,
8
+ sameIds,
9
+ setSelectionForId,
10
+ setSelectionForScope,
11
+ } from './utils';
12
+
13
+ interface UseBulkSelectionOptions {
14
+ scopeIds: readonly string[];
15
+ validIds?: readonly string[];
16
+ enabled?: boolean;
17
+ }
18
+
19
+ export const useBulkSelection = ({
20
+ scopeIds,
21
+ validIds = scopeIds,
22
+ enabled = true,
23
+ }: UseBulkSelectionOptions) => {
24
+ const [selectedIds, setSelectedIds] = useState<string[]>([]);
25
+
26
+ useEffect(() => {
27
+ setSelectedIds((prev) => {
28
+ if (!enabled) {
29
+ return prev.length === 0 ? prev : [];
30
+ }
31
+
32
+ const next = pruneSelection({ selectedIds: prev, validIds });
33
+ return sameIds(prev, next) ? prev : next;
34
+ });
35
+ }, [enabled, validIds]);
36
+
37
+ const state = useMemo(
38
+ () =>
39
+ getBulkSelectionState({
40
+ selectedIds,
41
+ scopeIds: enabled ? scopeIds : [],
42
+ }),
43
+ [enabled, scopeIds, selectedIds],
44
+ );
45
+
46
+ const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
47
+
48
+ const toggleSelection = useCallback(
49
+ (id: string, checked: boolean) => {
50
+ if (!enabled) {
51
+ return;
52
+ }
53
+
54
+ setSelectedIds((prev) =>
55
+ setSelectionForId({ selectedIds: prev, id, checked }),
56
+ );
57
+ },
58
+ [enabled],
59
+ );
60
+
61
+ const setScopeSelected = useCallback(
62
+ (checked: boolean) => {
63
+ if (!enabled) {
64
+ return;
65
+ }
66
+
67
+ setSelectedIds((prev) =>
68
+ setSelectionForScope({
69
+ selectedIds: prev,
70
+ scopeIds,
71
+ checked,
72
+ }),
73
+ );
74
+ },
75
+ [enabled, scopeIds],
76
+ );
77
+
78
+ const removeSelection = useCallback((idsToRemove: readonly string[]) => {
79
+ setSelectedIds((prev) =>
80
+ removeSelectedIds({ selectedIds: prev, idsToRemove }),
81
+ );
82
+ }, []);
83
+
84
+ const clearSelection = useCallback(() => {
85
+ setSelectedIds((prev) => (prev.length === 0 ? prev : []));
86
+ }, []);
87
+
88
+ return {
89
+ selectedIds,
90
+ selectedIdSet,
91
+ ...state,
92
+ toggleSelection,
93
+ setScopeSelected,
94
+ removeSelection,
95
+ clearSelection,
96
+ };
97
+ };
@@ -0,0 +1,126 @@
1
+ export type BulkCheckboxState = boolean | 'indeterminate';
2
+
3
+ export const uniqueIds = (ids: readonly string[]) => Array.from(new Set(ids));
4
+
5
+ export const sameIds = (left: readonly string[], right: readonly string[]) =>
6
+ left.length === right.length &&
7
+ left.every((value, index) => value === right[index]);
8
+
9
+ export const getScopedSelectedIds = ({
10
+ selectedIds,
11
+ scopeIds,
12
+ }: {
13
+ selectedIds: readonly string[];
14
+ scopeIds: readonly string[];
15
+ }) => {
16
+ const scopeSet = new Set(scopeIds);
17
+
18
+ return uniqueIds(selectedIds).filter((id) => scopeSet.has(id));
19
+ };
20
+
21
+ export const getBulkSelectionState = ({
22
+ selectedIds,
23
+ scopeIds,
24
+ }: {
25
+ selectedIds: readonly string[];
26
+ scopeIds: readonly string[];
27
+ }) => {
28
+ const uniqueScopeIds = uniqueIds(scopeIds);
29
+ const selectedScopedIds = getScopedSelectedIds({
30
+ selectedIds,
31
+ scopeIds: uniqueScopeIds,
32
+ });
33
+ const selectedScopedCount = selectedScopedIds.length;
34
+ const allScopedSelected =
35
+ uniqueScopeIds.length > 0 && selectedScopedCount === uniqueScopeIds.length;
36
+ const hasScopedSelection = selectedScopedCount > 0;
37
+ const checkboxState: BulkCheckboxState = allScopedSelected
38
+ ? true
39
+ : hasScopedSelection
40
+ ? 'indeterminate'
41
+ : false;
42
+
43
+ return {
44
+ selectedScopedIds,
45
+ selectedScopedCount,
46
+ allScopedSelected,
47
+ hasScopedSelection,
48
+ checkboxState,
49
+ };
50
+ };
51
+
52
+ export const setSelectionForId = ({
53
+ selectedIds,
54
+ id,
55
+ checked,
56
+ }: {
57
+ selectedIds: readonly string[];
58
+ id: string;
59
+ checked: boolean;
60
+ }) => {
61
+ const uniqueSelectedIds = uniqueIds(selectedIds);
62
+
63
+ if (!checked) {
64
+ return uniqueSelectedIds.filter((selectedId) => selectedId !== id);
65
+ }
66
+
67
+ if (uniqueSelectedIds.includes(id)) {
68
+ return uniqueSelectedIds;
69
+ }
70
+
71
+ return [...uniqueSelectedIds, id];
72
+ };
73
+
74
+ export const setSelectionForScope = ({
75
+ selectedIds,
76
+ scopeIds,
77
+ checked,
78
+ }: {
79
+ selectedIds: readonly string[];
80
+ scopeIds: readonly string[];
81
+ checked: boolean;
82
+ }) => {
83
+ const uniqueSelectedIds = uniqueIds(selectedIds);
84
+ const uniqueScopeIds = uniqueIds(scopeIds);
85
+ const scopeSet = new Set(uniqueScopeIds);
86
+
87
+ if (!checked) {
88
+ return uniqueSelectedIds.filter((id) => !scopeSet.has(id));
89
+ }
90
+
91
+ const nextSet = new Set(uniqueSelectedIds);
92
+ const nextIds = [...uniqueSelectedIds];
93
+
94
+ for (const id of uniqueScopeIds) {
95
+ if (!nextSet.has(id)) {
96
+ nextSet.add(id);
97
+ nextIds.push(id);
98
+ }
99
+ }
100
+
101
+ return nextIds;
102
+ };
103
+
104
+ export const removeSelectedIds = ({
105
+ selectedIds,
106
+ idsToRemove,
107
+ }: {
108
+ selectedIds: readonly string[];
109
+ idsToRemove: readonly string[];
110
+ }) => {
111
+ const removedSet = new Set(idsToRemove);
112
+
113
+ return uniqueIds(selectedIds).filter((id) => !removedSet.has(id));
114
+ };
115
+
116
+ export const pruneSelection = ({
117
+ selectedIds,
118
+ validIds,
119
+ }: {
120
+ selectedIds: readonly string[];
121
+ validIds: readonly string[];
122
+ }) => {
123
+ const validSet = new Set(validIds);
124
+
125
+ return uniqueIds(selectedIds).filter((id) => validSet.has(id));
126
+ };
@@ -2,13 +2,12 @@
2
2
 
3
3
  import _ from 'lodash';
4
4
  import { useEffect, useMemo, useState } from 'react';
5
- import { Select } from '@UI';
5
+ import { Select } from '#ui';
6
6
  import { useFiltersContext } from '../context';
7
7
 
8
8
  export const SelectBacktest = () => {
9
9
  const { filters, backtestFiles, onChangeFilters, ensureBacktestsLoaded } =
10
10
  useFiltersContext();
11
- const STORAGE_KEY = 'backtest-strategy';
12
11
 
13
12
  const tests = useMemo(
14
13
  () => backtestFiles.filter((file) => file.value.startsWith(filters.symbol)),
@@ -33,6 +32,16 @@ export const SelectBacktest = () => {
33
32
  }, [tests]);
34
33
 
35
34
  const [selectedStrategy, setSelectedStrategy] = useState<string>('');
35
+ const selectedTestStrategy = useMemo(() => {
36
+ if (!filters.backtestId) return null;
37
+
38
+ const strategyName = tests.find((test) => test.value === filters.backtestId)
39
+ ?.data?.strategyName;
40
+
41
+ return typeof strategyName === 'string' && strategyName
42
+ ? strategyName
43
+ : null;
44
+ }, [filters.backtestId, tests]);
36
45
 
37
46
  useEffect(() => {
38
47
  if (_.isEmpty(strategyItems)) {
@@ -40,59 +49,34 @@ export const SelectBacktest = () => {
40
49
  return;
41
50
  }
42
51
 
43
- if (
44
- filters.backtestStrategy &&
45
- strategyItems.some((item) => item.value === filters.backtestStrategy) &&
46
- selectedStrategy !== filters.backtestStrategy
47
- ) {
48
- setSelectedStrategy(filters.backtestStrategy);
49
- return;
50
- }
52
+ const requestedStrategy = filters.backtestStrategy ?? selectedTestStrategy;
51
53
 
52
54
  if (
53
- selectedStrategy &&
54
- strategyItems.some((item) => item.value === selectedStrategy)
55
+ requestedStrategy &&
56
+ strategyItems.some((item) => item.value === requestedStrategy) &&
57
+ selectedStrategy !== requestedStrategy
55
58
  ) {
59
+ setSelectedStrategy(requestedStrategy);
56
60
  return;
57
61
  }
58
62
 
59
- const saved =
60
- typeof window !== 'undefined'
61
- ? window.localStorage.getItem(STORAGE_KEY)
62
- : null;
63
-
64
- const defaultStrategy =
65
- saved && strategyItems.some((item) => item.value === saved)
66
- ? saved
67
- : strategyItems[0]?.value || '';
68
-
69
- if (defaultStrategy) {
70
- setSelectedStrategy(defaultStrategy);
63
+ if (!requestedStrategy && selectedStrategy) {
64
+ setSelectedStrategy('');
65
+ return;
71
66
  }
72
- }, [strategyItems, filters.backtestStrategy, selectedStrategy]);
73
-
74
- useEffect(() => {
75
- if (!filters.backtestId) return;
76
-
77
- const selectedTest = tests.find(
78
- (test) => test.value === filters.backtestId,
79
- );
80
- const strategyName = selectedTest?.data?.strategyName;
81
67
 
82
68
  if (
83
- typeof strategyName === 'string' &&
84
- strategyName &&
85
- selectedStrategy !== strategyName
69
+ selectedStrategy &&
70
+ !strategyItems.some((item) => item.value === selectedStrategy)
86
71
  ) {
87
- setSelectedStrategy(strategyName);
88
- }
89
- }, [filters.backtestId, tests, selectedStrategy]);
90
-
91
- useEffect(() => {
92
- if (selectedStrategy && typeof window !== 'undefined') {
93
- window.localStorage.setItem(STORAGE_KEY, selectedStrategy);
72
+ setSelectedStrategy('');
94
73
  }
95
- }, [selectedStrategy]);
74
+ }, [
75
+ filters.backtestStrategy,
76
+ selectedStrategy,
77
+ selectedTestStrategy,
78
+ strategyItems,
79
+ ]);
96
80
 
97
81
  const onChange = (value: string[]) => {
98
82
  onChangeFilters?.({
@@ -103,9 +87,6 @@ export const SelectBacktest = () => {
103
87
  const onChangeStrategy = (value: string[]) => {
104
88
  const nextStrategy = value[0] || '';
105
89
  setSelectedStrategy(nextStrategy);
106
- if (nextStrategy && typeof window !== 'undefined') {
107
- window.localStorage.setItem(STORAGE_KEY, nextStrategy);
108
- }
109
90
 
110
91
  const hasSelectedTest =
111
92
  filters.backtestId &&
@@ -131,11 +112,13 @@ export const SelectBacktest = () => {
131
112
  const strategyTests = tests.filter(
132
113
  (test) => test.data?.strategyName === selectedStrategy,
133
114
  );
115
+ const hasStrategyItems = !_.isEmpty(strategyItems);
134
116
 
135
117
  return (
136
118
  <>
137
119
  <Select
138
- placeholder="Strategy"
120
+ placeholder={hasStrategyItems ? 'Strategy' : 'No strategies'}
121
+ emptyState="No strategies for this symbol"
139
122
  defaultValue={[selectedStrategy]}
140
123
  value={[selectedStrategy]}
141
124
  onChange={onChangeStrategy}
@@ -145,6 +128,7 @@ export const SelectBacktest = () => {
145
128
  }
146
129
  }}
147
130
  items={strategyItems}
131
+ disabled={!hasStrategyItems}
148
132
  width="220px"
149
133
  />
150
134
  <Select
@@ -164,6 +148,7 @@ export const SelectBacktest = () => {
164
148
  },
165
149
  ...strategyTests,
166
150
  ]}
151
+ disabled={!selectedStrategy}
167
152
  width="240px"
168
153
  />
169
154
  </>
@@ -1,12 +1,14 @@
1
1
  import { useFiltersContext } from '../context';
2
- import { FavoriteButton } from '@shared/FavoriteButton';
3
- import { useTickers } from '@store';
2
+ import { FavoriteButton } from '#shared/FavoriteButton';
3
+ import { useTickers } from '#store';
4
4
 
5
5
  export const FavoriteIndicator = () => {
6
6
  const {
7
7
  filters: { symbol, provider },
8
8
  } = useFiltersContext();
9
- const { checkIsFavorite, toggleFavorite } = useTickers(provider || 'bybit');
9
+ const { checkIsFavorite, toggleFavorite } = useTickers(provider || 'bybit', {
10
+ enabled: false,
11
+ });
10
12
  const isFavorite = checkIsFavorite(symbol);
11
13
 
12
14
  return (
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import _ from 'lodash';
4
- import { useIndicators } from '@store';
5
- import { Select } from '@UI';
4
+ import { useIndicators } from '#store';
5
+ import { Select } from '#ui';
6
6
 
7
7
  export const SelectIndicator = () => {
8
8
  const { selectedIndicators, indicatorsItems, setEnabledIndicators } =
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { Segment } from '@UI';
3
+ import { Segment } from '#ui';
4
4
  import { Interval } from '@tradejs/types';
5
5
  import { intervals } from './intervals';
6
6
  import { useFiltersContext } from '../context';
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { Select } from '@UI';
3
+ import { Select } from '#ui';
4
+ import { getDefaultMarketSymbol } from '#app/lib/marketDefaults';
4
5
  import { useFiltersContext } from '../context';
5
6
 
6
7
  const items = [
@@ -14,8 +15,13 @@ export const SelectProvider = () => {
14
15
 
15
16
  const onChange = (value: string[]) => {
16
17
  if (!value[0]) return;
18
+ const provider = value[0] as 'bybit' | 'binance' | 'coinbase';
19
+ const universe =
20
+ provider === 'bybit' ? filters.universe ?? 'crypto' : 'crypto';
17
21
  onChangeFilters?.({
18
- provider: value[0] as 'bybit' | 'binance' | 'coinbase',
22
+ provider,
23
+ universe,
24
+ symbol: getDefaultMarketSymbol(provider, universe),
19
25
  backtestId: null,
20
26
  backtestStrategy: null,
21
27
  });
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import _ from 'lodash';
4
- import { SelectWithSearch } from '@UI';
4
+ import { SelectWithSearch } from '#ui';
5
5
  import { useFiltersContext } from '../context';
6
6
 
7
7
  interface SelectSymbolProps {}
@@ -11,7 +11,7 @@ export const SelectSymbol = ({}: SelectSymbolProps) => {
11
11
  useFiltersContext();
12
12
  const defaultInputValue =
13
13
  tickers.find(({ value }) => value === filters.symbol)?.label ||
14
- filters.symbol;
14
+ filters.symbol.replace(/USDT$/i, '');
15
15
 
16
16
  const onChange = (value: string[]) => {
17
17
  if (_.isEmpty(value)) {
@@ -29,12 +29,13 @@ export const SelectSymbol = ({}: SelectSymbolProps) => {
29
29
 
30
30
  return (
31
31
  <SelectWithSearch
32
+ key={`${filters.provider}:${filters.universe}:${filters.symbol}`}
32
33
  defaultValue={[filters.symbol]}
33
34
  defaultInputValue={defaultInputValue}
34
35
  onChange={onChange}
35
36
  onOpenChange={(open) => {
36
37
  if (open) {
37
- void ensureTickersLoaded?.();
38
+ void Promise.resolve(ensureTickersLoaded?.()).catch(() => undefined);
38
39
  }
39
40
  }}
40
41
  items={tickers}
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+
3
+ import { Select } from '#ui';
4
+ import type { MarketUniverse } from '@tradejs/types';
5
+ import { getDefaultMarketSymbol } from '#app/lib/marketDefaults';
6
+ import { useFiltersContext } from '../context';
7
+
8
+ export const SelectUniverse = () => {
9
+ const { filters, onChangeFilters } = useFiltersContext();
10
+ const items =
11
+ filters.provider === 'bybit'
12
+ ? [
13
+ { value: 'crypto', label: 'Crypto' },
14
+ { value: 'tradfi', label: 'TradFi' },
15
+ ]
16
+ : [{ value: 'crypto', label: 'Crypto' }];
17
+
18
+ return (
19
+ <Select
20
+ key={`${filters.provider}:${filters.universe}`}
21
+ defaultValue={[filters.universe ?? 'crypto']}
22
+ onChange={(value: string[]) => {
23
+ if (!value[0]) return;
24
+ const universe = value[0] as MarketUniverse;
25
+ onChangeFilters?.({
26
+ universe,
27
+ symbol: getDefaultMarketSymbol(filters.provider ?? 'bybit', universe),
28
+ backtestId: null,
29
+ backtestStrategy: null,
30
+ });
31
+ }}
32
+ items={items}
33
+ width="120px"
34
+ />
35
+ );
36
+ };
@@ -5,10 +5,12 @@ import { SelectSymbol } from './Symbol';
5
5
  import { SelectBacktest } from './Backtest';
6
6
  import { SelectIndicator } from './Indicators';
7
7
  import { FavoriteIndicator } from './FavoriteIndicator';
8
+ import { SelectUniverse } from './Universe';
8
9
 
9
10
  export const Filters = {
10
11
  Root,
11
12
  SelectProvider,
13
+ SelectUniverse,
12
14
  SelectInterval,
13
15
  SelectSymbol,
14
16
  SelectBacktest,