@tradejs/app 2.0.11 → 2.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/app",
3
- "version": "2.0.11",
3
+ "version": "2.0.13",
4
4
  "description": "Installable Next.js UI for the TradeJS TypeScript framework: dashboards, backtests, charts, and runtime data.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -51,12 +51,12 @@
51
51
  "@emotion/react": "^11.14.0",
52
52
  "@langchain/core": "^1.2.3",
53
53
  "@langchain/openai": "^1.5.5",
54
- "@tradejs/connectors": "^2.0.11",
55
- "@tradejs/core": "^2.0.11",
56
- "@tradejs/indicators": "^2.0.11",
57
- "@tradejs/infra": "^2.0.11",
58
- "@tradejs/node": "^2.0.11",
59
- "@tradejs/types": "^2.0.11",
54
+ "@tradejs/connectors": "^2.0.13",
55
+ "@tradejs/core": "^2.0.13",
56
+ "@tradejs/indicators": "^2.0.13",
57
+ "@tradejs/infra": "^2.0.13",
58
+ "@tradejs/node": "^2.0.13",
59
+ "@tradejs/types": "^2.0.13",
60
60
  "@types/bcryptjs": "2.4.6",
61
61
  "@types/lodash": "4.17.24",
62
62
  "@types/node": "24.13.3",
@@ -7,7 +7,6 @@ import {
7
7
  LineChart,
8
8
  CartesianGrid,
9
9
  Line,
10
- XAxis,
11
10
  YAxis,
12
11
  Tooltip,
13
12
  ReferenceLine,
@@ -18,6 +17,8 @@ import { useTestContext } from '../context';
18
17
  import { TestCompareList } from '@tradejs/types';
19
18
  import { mapOrderLogToChartData, getChartData } from './utils';
20
19
  import { getFormatted, getTimeline } from '@tradejs/core/backtest';
20
+ import { formatTimeSeriesTooltipTimestamp } from '#app/lib/timeSeriesChart';
21
+ import { TimeSeriesXAxis } from '#shared/Charts/TimeSeriesXAxis';
21
22
 
22
23
  interface TestCardChartProps {
23
24
  height?: string | number;
@@ -58,6 +59,9 @@ export const TestCardChart = ({ height = '350px' }: TestCardChartProps) => {
58
59
 
59
60
  const { formatted: maxAmount } = getFormatted(stat, 'maxAmount');
60
61
  const { formatted: minAmount } = getFormatted(stat, 'minAmount');
62
+ const startTimestamp = test.options.start ?? testResult.orderLog[0]?.[0] ?? 0;
63
+ const endTimestamp =
64
+ test.options.end ?? testResult.orderLog.at(-1)?.[0] ?? startTimestamp;
61
65
 
62
66
  return (
63
67
  <Box w="100%" minW="600px" h={height} pr={2}>
@@ -92,12 +96,19 @@ export const TestCardChart = ({ height = '350px' }: TestCardChartProps) => {
92
96
  position: 'bottom',
93
97
  }}
94
98
  />
95
- <XAxis dataKey="timestamp" />
99
+ <TimeSeriesXAxis
100
+ startTimestamp={startTimestamp}
101
+ endTimestamp={endTimestamp}
102
+ />
96
103
  <YAxis tickCount={10} domain={[stat.minAmount - 10, 'auto']} />
97
104
  <Tooltip
98
105
  animationDuration={100}
99
106
  cursor={false}
100
- content={<Chart.Tooltip />}
107
+ content={
108
+ <Chart.Tooltip
109
+ labelFormatter={formatTimeSeriesTooltipTimestamp}
110
+ />
111
+ }
101
112
  />
102
113
 
103
114
  {chart.series.map((item) => (
@@ -108,6 +119,7 @@ export const TestCardChart = ({ height = '350px' }: TestCardChartProps) => {
108
119
  stroke={chart.color(item.color)}
109
120
  strokeWidth={2}
110
121
  dot={false}
122
+ activeDot={{ r: 5, strokeWidth: 2 }}
111
123
  />
112
124
  ))}
113
125
  </LineChart>
@@ -1,4 +1,3 @@
1
- import { format } from 'date-fns';
2
1
  import {
3
2
  SimpleOrderLogData,
4
3
  TestResult,
@@ -11,7 +10,7 @@ const getLineName = (testResult: TestResult) =>
11
10
  export const mapOrderLogToChartData = (testResult: TestResult) => {
12
11
  const data = testResult.orderLog.map(([timestamp, amount]) => ({
13
12
  [getLineName(testResult)]: amount,
14
- timestamp: format(timestamp, 'dd.MM'),
13
+ timestamp,
15
14
  }));
16
15
 
17
16
  const series = [
@@ -52,8 +51,6 @@ export const getChartData = (testList: TestCompareList, timeline: number[]) => {
52
51
  const values: Record<string, number> = {};
53
52
 
54
53
  const data = timeline.map((timestamp, ind) => {
55
- const formattedTimestamp = format(timestamp, 'dd.MM');
56
-
57
54
  testList.forEach(({ testResult }) => {
58
55
  values[getLineName(testResult)] = getAmountFromOrderLog(
59
56
  ind,
@@ -65,7 +62,7 @@ export const getChartData = (testList: TestCompareList, timeline: number[]) => {
65
62
 
66
63
  return {
67
64
  ...values,
68
- timestamp: formattedTimestamp,
65
+ timestamp,
69
66
  };
70
67
  });
71
68
 
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import { useMemo } from 'react';
4
+ import { XAxis } from 'recharts';
5
+ import {
6
+ buildTimeSeriesTicks,
7
+ formatTimeSeriesAxisTimestamp,
8
+ } from '#app/lib/timeSeriesChart';
9
+
10
+ export const TimeSeriesXAxis = ({
11
+ startTimestamp,
12
+ endTimestamp,
13
+ tickCount = 7,
14
+ minTickGap = 24,
15
+ includeTime,
16
+ }: {
17
+ startTimestamp: number;
18
+ endTimestamp: number;
19
+ tickCount?: number;
20
+ minTickGap?: number;
21
+ includeTime?: boolean;
22
+ }) => {
23
+ const start = Math.min(startTimestamp, endTimestamp);
24
+ const end = Math.max(startTimestamp, endTimestamp);
25
+ const ticks = useMemo(
26
+ () => buildTimeSeriesTicks(start, end, tickCount),
27
+ [end, start, tickCount],
28
+ );
29
+
30
+ return (
31
+ <XAxis
32
+ dataKey="timestamp"
33
+ type="number"
34
+ scale="time"
35
+ domain={[start, end]}
36
+ ticks={ticks}
37
+ tickFormatter={(timestamp) =>
38
+ formatTimeSeriesAxisTimestamp({
39
+ timestamp,
40
+ startTimestamp: start,
41
+ endTimestamp: end,
42
+ includeTime,
43
+ })
44
+ }
45
+ tickCount={tickCount}
46
+ interval="preserveStartEnd"
47
+ minTickGap={minTickGap}
48
+ allowDataOverflow
49
+ />
50
+ );
51
+ };
@@ -1434,10 +1434,14 @@ const mapRuntimeOrder = (order: RuntimeOrderView): OrdersDrawerOrder => {
1434
1434
  export const RuntimeStrategyCard = ({
1435
1435
  strategy,
1436
1436
  provider,
1437
+ startTimestamp,
1438
+ endTimestamp,
1437
1439
  onUpdated,
1438
1440
  }: {
1439
1441
  strategy: RuntimeStrategyView;
1440
1442
  provider: string;
1443
+ startTimestamp: number;
1444
+ endTimestamp: number;
1441
1445
  onUpdated: () => Promise<void> | void;
1442
1446
  }) => {
1443
1447
  const [configOpen, setConfigOpen] = useState(false);
@@ -2179,6 +2183,8 @@ export const RuntimeStrategyCard = ({
2179
2183
  orderLog={strategy.orderLog}
2180
2184
  stat={strategy.stat}
2181
2185
  aiGateChanges={strategy.aiGateChanges}
2186
+ startTimestamp={startTimestamp}
2187
+ endTimestamp={endTimestamp}
2182
2188
  />
2183
2189
 
2184
2190
  <SimpleGrid columns={{ base: 4, md: 8 }} p={4}>
@@ -10,18 +10,20 @@ import {
10
10
  ReferenceLine,
11
11
  ResponsiveContainer,
12
12
  Tooltip,
13
- XAxis,
14
13
  YAxis,
15
14
  } from 'recharts';
16
- import { format } from 'date-fns';
17
15
  import { getFormatted } from '@tradejs/core/backtest';
18
16
  import type { SimpleOrderLogData, TestStat } from '@tradejs/types';
19
17
  import type { RuntimeStrategyAiGateChange } from '#app/lib/runtimeStrategies';
18
+ import { formatTimeSeriesTooltipTimestamp } from '#app/lib/timeSeriesChart';
19
+ import { TimeSeriesXAxis } from '#shared/Charts/TimeSeriesXAxis';
20
20
 
21
21
  interface RuntimeStrategyChartProps {
22
22
  orderLog: SimpleOrderLogData;
23
23
  stat: TestStat;
24
24
  aiGateChanges: RuntimeStrategyAiGateChange[];
25
+ startTimestamp: number;
26
+ endTimestamp: number;
25
27
  height?: string | number;
26
28
  }
27
29
 
@@ -29,6 +31,8 @@ export const RuntimeStrategyChart = ({
29
31
  orderLog,
30
32
  stat,
31
33
  aiGateChanges,
34
+ startTimestamp,
35
+ endTimestamp,
32
36
  height = '350px',
33
37
  }: RuntimeStrategyChartProps) => {
34
38
  const chartData = useMemo(
@@ -116,12 +120,9 @@ export const RuntimeStrategyChart = ({
116
120
  }}
117
121
  />
118
122
  ))}
119
- <XAxis
120
- dataKey="timestamp"
121
- type="number"
122
- scale="time"
123
- domain={['dataMin', 'dataMax']}
124
- tickFormatter={(timestamp) => format(timestamp, 'dd.MM')}
123
+ <TimeSeriesXAxis
124
+ startTimestamp={startTimestamp}
125
+ endTimestamp={endTimestamp}
125
126
  />
126
127
  <YAxis tickCount={10} domain={[stat.minAmount - 10, 'auto']} />
127
128
  <Tooltip
@@ -129,9 +130,7 @@ export const RuntimeStrategyChart = ({
129
130
  cursor={false}
130
131
  content={
131
132
  <Chart.Tooltip
132
- labelFormatter={(timestamp) =>
133
- format(Number(timestamp), 'dd.MM.yyyy HH:mm')
134
- }
133
+ labelFormatter={formatTimeSeriesTooltipTimestamp}
135
134
  />
136
135
  }
137
136
  />
@@ -9,11 +9,11 @@ import {
9
9
  LineChart,
10
10
  ResponsiveContainer,
11
11
  Tooltip,
12
- XAxis,
13
12
  YAxis,
14
13
  } from 'recharts';
15
- import { format } from 'date-fns';
16
14
  import type { SimpleOrderLogData } from '@tradejs/types';
15
+ import { formatTimeSeriesTooltipTimestamp } from '#app/lib/timeSeriesChart';
16
+ import { TimeSeriesXAxis } from '#shared/Charts/TimeSeriesXAxis';
17
17
 
18
18
  export const StrategySnapshotChart = ({
19
19
  orderLog,
@@ -27,7 +27,7 @@ export const StrategySnapshotChart = ({
27
27
  const chartData = useMemo(
28
28
  () => ({
29
29
  data: orderLog.map(([timestamp, amount]) => ({
30
- timestamp: format(timestamp, 'dd.MM'),
30
+ timestamp,
31
31
  equity: amount,
32
32
  })),
33
33
  series: [
@@ -59,6 +59,8 @@ export const StrategySnapshotChart = ({
59
59
 
60
60
  const values = orderLog.map(([, amount]) => amount);
61
61
  const minValue = Math.min(...values);
62
+ const startTimestamp = orderLog[0][0];
63
+ const endTimestamp = orderLog[orderLog.length - 1][0];
62
64
 
63
65
  return (
64
66
  <Box w="100%" minW="600px" h={height} pr={2}>
@@ -66,7 +68,10 @@ export const StrategySnapshotChart = ({
66
68
  <Chart.Root maxH="md" chart={chart}>
67
69
  <LineChart data={chart.data}>
68
70
  <CartesianGrid stroke={chart.color('border')} vertical={false} />
69
- <XAxis dataKey="timestamp" />
71
+ <TimeSeriesXAxis
72
+ startTimestamp={startTimestamp}
73
+ endTimestamp={endTimestamp}
74
+ />
70
75
  <YAxis
71
76
  tickCount={10}
72
77
  domain={[Math.min(minValue - 10, 0), 'auto']}
@@ -74,7 +79,11 @@ export const StrategySnapshotChart = ({
74
79
  <Tooltip
75
80
  animationDuration={100}
76
81
  cursor={false}
77
- content={<Chart.Tooltip />}
82
+ content={
83
+ <Chart.Tooltip
84
+ labelFormatter={formatTimeSeriesTooltipTimestamp}
85
+ />
86
+ }
78
87
  />
79
88
  {chart.series.map((item) => (
80
89
  <Line
@@ -84,6 +93,7 @@ export const StrategySnapshotChart = ({
84
93
  stroke={chart.color(item.color)}
85
94
  strokeWidth={2}
86
95
  dot={false}
96
+ activeDot={{ r: 5, strokeWidth: 2 }}
87
97
  />
88
98
  ))}
89
99
  </LineChart>
@@ -0,0 +1,100 @@
1
+ import { format } from 'date-fns';
2
+
3
+ const SHORT_TIME_RANGE_MS = 48 * 60 * 60 * 1000;
4
+
5
+ type TooltipPayloadEntry = {
6
+ payload?: unknown;
7
+ };
8
+
9
+ const getValidDate = (value: unknown) => {
10
+ if (
11
+ value === null ||
12
+ value === undefined ||
13
+ typeof value === 'boolean' ||
14
+ (typeof value === 'string' && value.trim() === '')
15
+ ) {
16
+ return null;
17
+ }
18
+
19
+ let timestamp: number;
20
+ try {
21
+ timestamp = Number(value);
22
+ } catch {
23
+ return null;
24
+ }
25
+
26
+ if (!Number.isFinite(timestamp)) {
27
+ return null;
28
+ }
29
+
30
+ const date = new Date(timestamp);
31
+ return Number.isNaN(date.getTime()) ? null : date;
32
+ };
33
+
34
+ export const buildTimeSeriesTicks = (
35
+ startTimestamp: number,
36
+ endTimestamp: number,
37
+ tickCount = 7,
38
+ ) => {
39
+ if (!getValidDate(startTimestamp) || !getValidDate(endTimestamp)) {
40
+ return [];
41
+ }
42
+
43
+ const start = Math.min(startTimestamp, endTimestamp);
44
+ const end = Math.max(startTimestamp, endTimestamp);
45
+ if (start === end) {
46
+ return [start];
47
+ }
48
+
49
+ const count = Math.max(2, Math.trunc(tickCount));
50
+ const ticks = Array.from({ length: count }, (_, index) =>
51
+ index === count - 1
52
+ ? end
53
+ : Math.round(start + ((end - start) * index) / (count - 1)),
54
+ );
55
+
56
+ return [...new Set(ticks)];
57
+ };
58
+
59
+ export const formatTimeSeriesAxisTimestamp = ({
60
+ timestamp,
61
+ startTimestamp,
62
+ endTimestamp,
63
+ includeTime,
64
+ }: {
65
+ timestamp: number;
66
+ startTimestamp: number;
67
+ endTimestamp: number;
68
+ includeTime?: boolean;
69
+ }) => {
70
+ const date = getValidDate(timestamp);
71
+ if (!date) {
72
+ return String(timestamp);
73
+ }
74
+
75
+ return format(
76
+ date,
77
+ includeTime ?? endTimestamp - startTimestamp <= SHORT_TIME_RANGE_MS
78
+ ? 'dd.MM HH:mm'
79
+ : 'dd.MM',
80
+ );
81
+ };
82
+
83
+ export const formatTimeSeriesTooltipTimestamp = (
84
+ value: unknown,
85
+ payload: ReadonlyArray<TooltipPayloadEntry> = [],
86
+ ) => {
87
+ const payloadTimestamp = payload.reduce<unknown>((timestamp, item) => {
88
+ if (timestamp !== undefined) {
89
+ return timestamp;
90
+ }
91
+
92
+ const row = item.payload;
93
+ return row && typeof row === 'object' && 'timestamp' in row
94
+ ? row.timestamp
95
+ : undefined;
96
+ }, undefined);
97
+ const date = getValidDate(value) ?? getValidDate(payloadTimestamp);
98
+
99
+ return date ? format(date, 'dd.MM.yyyy HH:mm') : String(value ?? '');
100
+ };
@@ -33,13 +33,14 @@ import {
33
33
  ReferenceLine,
34
34
  ResponsiveContainer,
35
35
  Tooltip,
36
- XAxis,
37
36
  YAxis,
38
37
  } from 'recharts';
39
38
  import { format } from 'date-fns';
40
39
  import { FiBarChart2 } from 'react-icons/fi';
41
40
  import { API } from '@tradejs/core/api';
42
41
  import { buildKlinePath } from '#app/lib/marketRoutes';
42
+ import { formatTimeSeriesTooltipTimestamp } from '#app/lib/timeSeriesChart';
43
+ import { TimeSeriesXAxis } from '#shared/Charts/TimeSeriesXAxis';
43
44
  import { EmptyState, Segment, Select, toaster } from '#ui';
44
45
 
45
46
  type SummaryItem = {
@@ -91,6 +92,11 @@ type PriceResponse = {
91
92
  data?: PriceRow[];
92
93
  };
93
94
 
95
+ type ChartWindow = {
96
+ startTimestamp: number;
97
+ endTimestamp: number;
98
+ };
99
+
94
100
  type BiasTone = 'teal' | 'green' | 'red' | 'orange' | 'gray';
95
101
 
96
102
  type SymbolMetrics = {
@@ -224,12 +230,6 @@ const getChartDomain = (
224
230
  return [min - basePadding, max + basePadding];
225
231
  };
226
232
 
227
- const formatTimeLabel = (value: string) => {
228
- const parsed = new Date(value);
229
- if (Number.isNaN(parsed.getTime())) return value;
230
- return format(parsed, 'dd.MM HH:mm');
231
- };
232
-
233
233
  const formatFullTime = (value: string | null | undefined) => {
234
234
  if (!value) return 'n/a';
235
235
  const parsed = new Date(value);
@@ -572,7 +572,7 @@ const ChartCard = ({
572
572
 
573
573
  const mapRowsToChartRows = (rows: DetailRow[]) =>
574
574
  rows.map((row) => ({
575
- timestamp: formatTimeLabel(row.ts),
575
+ timestamp: new Date(row.ts).getTime(),
576
576
  openInterest: toFiniteNumber(row.open_interest) ?? 0,
577
577
  funding: (toFiniteNumber(row.funding_rate) ?? 0) * 10_000,
578
578
  longLiquidations: -(toFiniteNumber(row.liq_long) ?? 0),
@@ -582,15 +582,17 @@ const mapRowsToChartRows = (rows: DetailRow[]) =>
582
582
  const mapPriceRowsToChartRows = (rows: PriceRow[]) =>
583
583
  rows.map((row) => ({
584
584
  price: toFiniteNumber(row.close) ?? 0,
585
- timestamp: formatTimeLabel(new Date(row.timestamp).toISOString()),
585
+ timestamp: row.timestamp,
586
586
  }));
587
587
 
588
588
  const SymbolPriceCard = ({
589
589
  symbol,
590
590
  rows,
591
+ window,
591
592
  }: {
592
593
  symbol: string;
593
594
  rows: PriceRow[];
595
+ window: ChartWindow;
594
596
  }) => {
595
597
  const theme = SYMBOL_THEMES[symbol];
596
598
  const symbolLabel = getSymbolLabel(symbol);
@@ -644,9 +646,21 @@ const SymbolPriceCard = ({
644
646
  stroke={priceChart.color('border')}
645
647
  vertical={false}
646
648
  />
647
- <XAxis dataKey="timestamp" minTickGap={36} />
649
+ <TimeSeriesXAxis
650
+ startTimestamp={window.startTimestamp}
651
+ endTimestamp={window.endTimestamp}
652
+ tickCount={5}
653
+ minTickGap={36}
654
+ />
648
655
  <YAxis domain={priceDomain} tickFormatter={formatPrice} />
649
- <Tooltip cursor={false} content={<Chart.Tooltip />} />
656
+ <Tooltip
657
+ cursor={false}
658
+ content={
659
+ <Chart.Tooltip
660
+ labelFormatter={formatTimeSeriesTooltipTimestamp}
661
+ />
662
+ }
663
+ />
650
664
  <Area
651
665
  type="monotone"
652
666
  dataKey={priceChart.key('price') as string}
@@ -667,9 +681,11 @@ const SymbolPriceCard = ({
667
681
  const SymbolOpenInterestCard = ({
668
682
  symbol,
669
683
  rows,
684
+ window,
670
685
  }: {
671
686
  symbol: string;
672
687
  rows: DetailRow[];
688
+ window: ChartWindow;
673
689
  }) => {
674
690
  const theme = SYMBOL_THEMES[symbol];
675
691
  const symbolLabel = getSymbolLabel(symbol);
@@ -722,9 +738,21 @@ const SymbolOpenInterestCard = ({
722
738
  stroke={oiChart.color('border')}
723
739
  vertical={false}
724
740
  />
725
- <XAxis dataKey="timestamp" minTickGap={36} />
741
+ <TimeSeriesXAxis
742
+ startTimestamp={window.startTimestamp}
743
+ endTimestamp={window.endTimestamp}
744
+ tickCount={5}
745
+ minTickGap={36}
746
+ />
726
747
  <YAxis domain={oiDomain} tickFormatter={formatAxisCompact} />
727
- <Tooltip cursor={false} content={<Chart.Tooltip />} />
748
+ <Tooltip
749
+ cursor={false}
750
+ content={
751
+ <Chart.Tooltip
752
+ labelFormatter={formatTimeSeriesTooltipTimestamp}
753
+ />
754
+ }
755
+ />
728
756
  <Area
729
757
  type="monotone"
730
758
  dataKey={oiChart.key('openInterest') as string}
@@ -745,9 +773,11 @@ const SymbolOpenInterestCard = ({
745
773
  const SymbolFundingCard = ({
746
774
  symbol,
747
775
  rows,
776
+ window,
748
777
  }: {
749
778
  symbol: string;
750
779
  rows: DetailRow[];
780
+ window: ChartWindow;
751
781
  }) => {
752
782
  const theme = SYMBOL_THEMES[symbol];
753
783
  const symbolLabel = getSymbolLabel(symbol);
@@ -781,9 +811,21 @@ const SymbolFundingCard = ({
781
811
  stroke={fundingChart.color('gray.600')}
782
812
  strokeDasharray="4 4"
783
813
  />
784
- <XAxis dataKey="timestamp" minTickGap={36} />
814
+ <TimeSeriesXAxis
815
+ startTimestamp={window.startTimestamp}
816
+ endTimestamp={window.endTimestamp}
817
+ tickCount={5}
818
+ minTickGap={36}
819
+ />
785
820
  <YAxis tickFormatter={(value) => `${value} bps`} />
786
- <Tooltip cursor={false} content={<Chart.Tooltip />} />
821
+ <Tooltip
822
+ cursor={false}
823
+ content={
824
+ <Chart.Tooltip
825
+ labelFormatter={formatTimeSeriesTooltipTimestamp}
826
+ />
827
+ }
828
+ />
787
829
  <Bar
788
830
  dataKey={fundingChart.key('funding') as string}
789
831
  isAnimationActive={false}
@@ -810,9 +852,11 @@ const SymbolFundingCard = ({
810
852
  const SymbolLiquidationCard = ({
811
853
  symbol,
812
854
  rows,
855
+ window,
813
856
  }: {
814
857
  symbol: string;
815
858
  rows: DetailRow[];
859
+ window: ChartWindow;
816
860
  }) => {
817
861
  const theme = SYMBOL_THEMES[symbol];
818
862
  const symbolLabel = getSymbolLabel(symbol);
@@ -849,9 +893,21 @@ const SymbolLiquidationCard = ({
849
893
  stroke={liquidationChart.color('gray.600')}
850
894
  strokeDasharray="4 4"
851
895
  />
852
- <XAxis dataKey="timestamp" minTickGap={28} />
896
+ <TimeSeriesXAxis
897
+ startTimestamp={window.startTimestamp}
898
+ endTimestamp={window.endTimestamp}
899
+ tickCount={5}
900
+ minTickGap={28}
901
+ />
853
902
  <YAxis tickFormatter={formatAxisCompact} />
854
- <Tooltip cursor={false} content={<Chart.Tooltip />} />
903
+ <Tooltip
904
+ cursor={false}
905
+ content={
906
+ <Chart.Tooltip
907
+ labelFormatter={formatTimeSeriesTooltipTimestamp}
908
+ />
909
+ }
910
+ />
855
911
  <Legend />
856
912
  <Bar
857
913
  dataKey={liquidationChart.key('longLiquidations') as string}
@@ -883,6 +939,13 @@ const DerivativesPage = () => {
883
939
  const [pricesBySymbol, setPricesBySymbol] = useState<
884
940
  Record<string, PriceRow[]>
885
941
  >({});
942
+ const [chartWindow, setChartWindow] = useState<ChartWindow>(() => {
943
+ const endTimestamp = Date.now();
944
+ return {
945
+ startTimestamp: endTimestamp - 24 * 60 * 60 * 1000,
946
+ endTimestamp,
947
+ };
948
+ });
886
949
  const [summaryLoading, setSummaryLoading] = useState(false);
887
950
  const [detailLoading, setDetailLoading] = useState(false);
888
951
  const [summaryError, setSummaryError] = useState('');
@@ -959,6 +1022,7 @@ const DerivativesPage = () => {
959
1022
  responses.map(([symbol, payload]) => [symbol, payload.priceRows]),
960
1023
  ),
961
1024
  );
1025
+ setChartWindow({ startTimestamp: from, endTimestamp: now });
962
1026
  } catch (err) {
963
1027
  const message =
964
1028
  (err as Error)?.message ||
@@ -1101,6 +1165,7 @@ const DerivativesPage = () => {
1101
1165
  key={`${symbol}:price`}
1102
1166
  symbol={symbol}
1103
1167
  rows={pricesBySymbol[symbol] ?? []}
1168
+ window={chartWindow}
1104
1169
  />
1105
1170
  ))}
1106
1171
  </SimpleGrid>
@@ -1111,6 +1176,7 @@ const DerivativesPage = () => {
1111
1176
  key={`${symbol}:oi`}
1112
1177
  symbol={symbol}
1113
1178
  rows={detailsBySymbol[symbol] ?? []}
1179
+ window={chartWindow}
1114
1180
  />
1115
1181
  ))}
1116
1182
  </SimpleGrid>
@@ -1121,6 +1187,7 @@ const DerivativesPage = () => {
1121
1187
  key={`${symbol}:funding`}
1122
1188
  symbol={symbol}
1123
1189
  rows={detailsBySymbol[symbol] ?? []}
1190
+ window={chartWindow}
1124
1191
  />
1125
1192
  ))}
1126
1193
  </SimpleGrid>
@@ -1131,6 +1198,7 @@ const DerivativesPage = () => {
1131
1198
  key={`${symbol}:liq`}
1132
1199
  symbol={symbol}
1133
1200
  rows={detailsBySymbol[symbol] ?? []}
1201
+ window={chartWindow}
1134
1202
  />
1135
1203
  ))}
1136
1204
  </SimpleGrid>
@@ -89,6 +89,17 @@ const RuntimeStrategiesContent = () => {
89
89
  const isSnapshotMode = mode === 'replay' || mode === 'ai';
90
90
  const snapshotModeLabel = mode === 'replay' ? 'Replay' : 'AI';
91
91
  const snapshotModeLabelLower = snapshotModeLabel.toLowerCase();
92
+ const runtimeChartWindow = useMemo(() => {
93
+ if (!runtimeData) {
94
+ return null;
95
+ }
96
+
97
+ return {
98
+ startTimestamp:
99
+ runtimeData.generatedAt - runtimeData.hours * 60 * 60 * 1000,
100
+ endTimestamp: runtimeData.generatedAt,
101
+ };
102
+ }, [runtimeData]);
92
103
 
93
104
  useEffect(() => {
94
105
  setMode(routeMode);
@@ -557,11 +568,14 @@ const RuntimeStrategiesContent = () => {
557
568
 
558
569
  {!loading &&
559
570
  mode === 'runtime' &&
571
+ runtimeChartWindow &&
560
572
  filteredRuntimeStrategies.map((strategy) => (
561
573
  <RuntimeStrategyCard
562
574
  key={strategy.runtimeKey}
563
575
  strategy={strategy}
564
576
  provider={runtimeData?.provider || 'bybit'}
577
+ startTimestamp={runtimeChartWindow.startTimestamp}
578
+ endTimestamp={runtimeChartWindow.endTimestamp}
565
579
  onUpdated={load}
566
580
  />
567
581
  ))}