@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,94 @@
1
+ 'use client';
2
+
3
+ import { useMemo } from 'react';
4
+ import { Box, Text } from '@chakra-ui/react';
5
+ import { Chart, useChart } from '@chakra-ui/charts';
6
+ import {
7
+ CartesianGrid,
8
+ Line,
9
+ LineChart,
10
+ ResponsiveContainer,
11
+ Tooltip,
12
+ XAxis,
13
+ YAxis,
14
+ } from 'recharts';
15
+ import { format } from 'date-fns';
16
+ import type { SimpleOrderLogData } from '@tradejs/types';
17
+
18
+ export const StrategySnapshotChart = ({
19
+ orderLog,
20
+ height = '350px',
21
+ emptyText = 'No chart data for the selected run.',
22
+ }: {
23
+ orderLog: SimpleOrderLogData;
24
+ height?: string | number;
25
+ emptyText?: string;
26
+ }) => {
27
+ const chartData = useMemo(
28
+ () => ({
29
+ data: orderLog.map(([timestamp, amount]) => ({
30
+ timestamp: format(timestamp, 'dd.MM'),
31
+ equity: amount,
32
+ })),
33
+ series: [
34
+ {
35
+ name: 'equity',
36
+ color: 'teal.solid',
37
+ },
38
+ ],
39
+ }),
40
+ [orderLog],
41
+ );
42
+
43
+ const chart = useChart(chartData as any);
44
+
45
+ if (!orderLog.length) {
46
+ return (
47
+ <Box
48
+ w="100%"
49
+ minW="600px"
50
+ h={height}
51
+ display="flex"
52
+ alignItems="center"
53
+ justifyContent="center"
54
+ >
55
+ <Text color="gray.500">{emptyText}</Text>
56
+ </Box>
57
+ );
58
+ }
59
+
60
+ const values = orderLog.map(([, amount]) => amount);
61
+ const minValue = Math.min(...values);
62
+
63
+ return (
64
+ <Box w="100%" minW="600px" h={height} pr={2}>
65
+ <ResponsiveContainer width="100%" height="100%">
66
+ <Chart.Root maxH="md" chart={chart}>
67
+ <LineChart data={chart.data}>
68
+ <CartesianGrid stroke={chart.color('border')} vertical={false} />
69
+ <XAxis dataKey="timestamp" />
70
+ <YAxis
71
+ tickCount={10}
72
+ domain={[Math.min(minValue - 10, 0), 'auto']}
73
+ />
74
+ <Tooltip
75
+ animationDuration={100}
76
+ cursor={false}
77
+ content={<Chart.Tooltip />}
78
+ />
79
+ {chart.series.map((item) => (
80
+ <Line
81
+ key={item.name as string}
82
+ isAnimationActive={false}
83
+ dataKey={chart.key(item.name) as string}
84
+ stroke={chart.color(item.color)}
85
+ strokeWidth={2}
86
+ dot={false}
87
+ />
88
+ ))}
89
+ </LineChart>
90
+ </Chart.Root>
91
+ </ResponsiveContainer>
92
+ </Box>
93
+ );
94
+ };
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+
3
+ import { useCallback } from 'react';
4
+ import AutoSizer from 'react-virtualized-auto-sizer';
5
+ import { FixedSizeList, type ListChildComponentProps } from 'react-window';
6
+ import { Box } from '@chakra-ui/react';
7
+ import type { StrategyChartSnapshot } from '@tradejs/types';
8
+ import { StrategySnapshotCard } from './StrategySnapshotCard';
9
+
10
+ const SNAPSHOT_CARD_ROW_HEIGHT = 620;
11
+
12
+ interface StrategySnapshotListProps {
13
+ strategies: StrategyChartSnapshot[];
14
+ mode: 'replay' | 'ai';
15
+ selectedCardIds: ReadonlySet<string>;
16
+ emptyText: string;
17
+ onDeleted: (cardId: string) => void;
18
+ onToggleSelection: (cardId: string, checked: boolean) => void;
19
+ overscan?: number;
20
+ }
21
+
22
+ export const StrategySnapshotList = ({
23
+ strategies,
24
+ mode,
25
+ selectedCardIds,
26
+ emptyText,
27
+ onDeleted,
28
+ onToggleSelection,
29
+ overscan = 1,
30
+ }: StrategySnapshotListProps) => {
31
+ const itemKey = useCallback(
32
+ (index: number) => strategies[index]?.cardId ?? String(index),
33
+ [strategies],
34
+ );
35
+
36
+ const Row = useCallback(
37
+ ({ index, style }: ListChildComponentProps) => {
38
+ const strategy = strategies[index];
39
+ if (!strategy) {
40
+ return null;
41
+ }
42
+
43
+ return (
44
+ <Box style={style}>
45
+ <StrategySnapshotCard
46
+ snapshot={strategy}
47
+ mode={mode}
48
+ onDeleted={onDeleted}
49
+ selected={selectedCardIds.has(strategy.cardId)}
50
+ onToggleSelection={onToggleSelection}
51
+ emptyText={emptyText}
52
+ />
53
+ </Box>
54
+ );
55
+ },
56
+ [
57
+ emptyText,
58
+ mode,
59
+ onDeleted,
60
+ onToggleSelection,
61
+ selectedCardIds,
62
+ strategies,
63
+ ],
64
+ );
65
+
66
+ return (
67
+ <AutoSizer>
68
+ {({ height, width }) => (
69
+ <FixedSizeList
70
+ height={height}
71
+ width={width}
72
+ itemCount={strategies.length}
73
+ itemSize={SNAPSHOT_CARD_ROW_HEIGHT}
74
+ overscanCount={overscan}
75
+ itemKey={itemKey}
76
+ >
77
+ {Row}
78
+ </FixedSizeList>
79
+ )}
80
+ </AutoSizer>
81
+ );
82
+ };
@@ -5,14 +5,21 @@ import { Items } from '@tradejs/types';
5
5
 
6
6
  interface SegmentProps {
7
7
  defaultValue: string;
8
+ value?: string;
8
9
  items: Items;
9
10
  onChange?: (value: string | null) => void;
10
11
  }
11
12
 
12
- export const Segment = ({ defaultValue, items, onChange }: SegmentProps) => {
13
+ export const Segment = ({
14
+ defaultValue,
15
+ value,
16
+ items,
17
+ onChange,
18
+ }: SegmentProps) => {
13
19
  return (
14
20
  <SegmentGroup.Root
15
21
  size="md"
22
+ value={value}
16
23
  defaultValue={defaultValue}
17
24
  onValueChange={(e) => onChange?.(e.value)}
18
25
  >
@@ -15,8 +15,10 @@ interface SelectProps {
15
15
  value?: string[];
16
16
  items: Items;
17
17
  placeholder?: string;
18
+ emptyState?: string;
18
19
  width?: string | number;
19
20
  multiple?: boolean;
21
+ disabled?: boolean;
20
22
  size?: 'xs' | 'sm' | 'md' | 'lg';
21
23
  onChange?: (value: string[]) => void;
22
24
  onOpenChange?: (open: boolean) => void;
@@ -28,7 +30,9 @@ export const Select = ({
28
30
  items,
29
31
  multiple = false,
30
32
  placeholder = 'Select',
33
+ emptyState = 'No items found',
31
34
  width = '320px',
35
+ disabled = false,
32
36
  size = 'sm',
33
37
  onChange,
34
38
  onOpenChange,
@@ -50,6 +54,7 @@ export const Select = ({
50
54
  size={size}
51
55
  multiple={multiple}
52
56
  width={width}
57
+ disabled={disabled}
53
58
  >
54
59
  <UISelect.HiddenSelect />
55
60
  <UISelect.Control>
@@ -63,19 +68,25 @@ export const Select = ({
63
68
  <Portal>
64
69
  <UISelect.Positioner>
65
70
  <UISelect.Content>
66
- {collection.items.map((item) => (
67
- <UISelect.Item item={item} key={item.value}>
68
- <Stack gap="0">
69
- <UISelect.ItemText>{item.label}</UISelect.ItemText>
70
- {item.description && (
71
- <Span color="fg.muted" textStyle="xs">
72
- {item.description}
73
- </Span>
74
- )}
75
- </Stack>
76
- <UISelect.ItemIndicator />
77
- </UISelect.Item>
78
- ))}
71
+ {collection.items.length === 0 ? (
72
+ <Span color="fg.muted" px="3" py="2" textStyle="sm">
73
+ {emptyState}
74
+ </Span>
75
+ ) : (
76
+ collection.items.map((item) => (
77
+ <UISelect.Item item={item} key={item.value}>
78
+ <Stack gap="0">
79
+ <UISelect.ItemText>{item.label}</UISelect.ItemText>
80
+ {item.description && (
81
+ <Span color="fg.muted" textStyle="xs">
82
+ {item.description}
83
+ </Span>
84
+ )}
85
+ </Stack>
86
+ <UISelect.ItemIndicator />
87
+ </UISelect.Item>
88
+ ))
89
+ )}
79
90
  </UISelect.Content>
80
91
  </UISelect.Positioner>
81
92
  </Portal>
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import _ from 'lodash';
4
- import { useEffect, useState } from 'react';
4
+ import { useCallback, useEffect, useState } from 'react';
5
5
  import {
6
6
  Portal,
7
7
  Stack,
@@ -15,12 +15,14 @@ import { Items } from '@tradejs/types';
15
15
 
16
16
  interface SelectWithSearchProps {
17
17
  defaultValue: string[];
18
+ value?: string[];
18
19
  defaultInputValue?: string;
19
20
  items: Items;
20
21
  placeholder?: string;
21
22
  emptyState?: string;
22
23
  width?: string | number;
23
24
  multiple?: boolean;
25
+ disabled?: boolean;
24
26
  size?: 'xs' | 'sm' | 'md' | 'lg';
25
27
  onChange?: (value: string[]) => void;
26
28
  onOpenChange?: (open: boolean) => void;
@@ -28,9 +30,11 @@ interface SelectWithSearchProps {
28
30
 
29
31
  export const SelectWithSearch = ({
30
32
  defaultValue,
33
+ value,
31
34
  defaultInputValue,
32
35
  items,
33
36
  multiple = false,
37
+ disabled = false,
34
38
  placeholder = 'Select',
35
39
  emptyState = 'No items found',
36
40
  width = '320px',
@@ -39,8 +43,29 @@ export const SelectWithSearch = ({
39
43
  onOpenChange,
40
44
  }: SelectWithSearchProps) => {
41
45
  const { contains } = useFilter({ sensitivity: 'base' });
42
- const [inputValue, setInputValue] = useState(
43
- defaultInputValue ?? defaultValue?.[0],
46
+ const defaultSelectedValue = defaultValue[0];
47
+ const [selectedValue, setSelectedValue] = useState(defaultValue);
48
+ const [isOpen, setIsOpen] = useState(false);
49
+ const currentValue = value ?? selectedValue;
50
+ const getSelectedInputValue = useCallback(
51
+ (selected: string[]) => {
52
+ if (multiple) return '';
53
+
54
+ const selectedItem = selected[0];
55
+ if (!selectedItem) return '';
56
+
57
+ return (
58
+ items.find((item) => item.value === selectedItem)?.label ??
59
+ (selectedItem === defaultSelectedValue
60
+ ? defaultInputValue
61
+ : undefined) ??
62
+ selectedItem
63
+ );
64
+ },
65
+ [defaultInputValue, defaultSelectedValue, items, multiple],
66
+ );
67
+ const [inputValue, setInputValue] = useState(() =>
68
+ getSelectedInputValue(currentValue),
44
69
  );
45
70
 
46
71
  const { collection, filter, set } = useListCollection({
@@ -52,26 +77,42 @@ export const SelectWithSearch = ({
52
77
  set(items);
53
78
  }, [items, set]);
54
79
 
80
+ useEffect(() => {
81
+ if (!isOpen) {
82
+ setInputValue(getSelectedInputValue(currentValue));
83
+ }
84
+ }, [currentValue, getSelectedInputValue, isOpen]);
85
+
55
86
  return (
56
87
  <Combobox.Root
57
88
  collection={collection}
58
- defaultValue={defaultValue}
89
+ {...(value ? { value } : { defaultValue })}
59
90
  inputValue={inputValue}
60
- onValueChange={(details) => onChange?.(details.value)}
91
+ onValueChange={(details) => {
92
+ if (!value) {
93
+ setSelectedValue(details.value);
94
+ }
95
+ setInputValue(getSelectedInputValue(details.value));
96
+ onChange?.(details.value);
97
+ }}
61
98
  onInputValueChange={(e) => {
62
99
  filter(e.inputValue);
63
100
  setInputValue(e.inputValue);
64
101
  }}
65
102
  onOpenChange={(details) => {
103
+ setIsOpen(details.open);
66
104
  onOpenChange?.(details.open);
67
105
  if (details.open) {
68
106
  filter('');
69
107
  setInputValue('');
108
+ } else {
109
+ setInputValue(getSelectedInputValue(currentValue));
70
110
  }
71
111
  }}
72
112
  width={width}
73
113
  multiple={multiple}
74
114
  size={size}
115
+ disabled={disabled}
75
116
  openOnClick
76
117
  >
77
118
  <Combobox.Control>
@@ -1,5 +1,5 @@
1
1
  import type { Metadata } from 'next';
2
- import { AppShell } from '@shared/AppShell';
2
+ import { AppShell } from '#shared/AppShell';
3
3
  import Provider from './provider';
4
4
  import './globals.css';
5
5