@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.
- package/README.md +12 -0
- package/bin/tradejs-app.mjs +54 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +31 -0
- package/package.json +60 -0
- package/src/app/actions/ai.ts +33 -0
- package/src/app/actions/backtest.ts +55 -0
- package/src/app/actions/kline.ts +18 -0
- package/src/app/actions/scanner.ts +10 -0
- package/src/app/actions/signal.ts +19 -0
- package/src/app/api/ai/route.ts +151 -0
- package/src/app/api/auth/[...nextauth]/route.ts +5 -0
- package/src/app/api/backtest/files/route.ts +60 -0
- package/src/app/api/backtest/order-log/[strategy]/[name]/route.ts +47 -0
- package/src/app/api/backtest/result/[strategy]/[name]/route.ts +63 -0
- package/src/app/api/backtest/test/[strategy]/[name]/route.ts +57 -0
- package/src/app/api/cron/route.ts +4 -0
- package/src/app/api/derivatives/[symbol]/[interval]/route.ts +57 -0
- package/src/app/api/derivatives/summary/route.ts +20 -0
- package/src/app/api/files/screenshot/[name]/route.ts +42 -0
- package/src/app/api/indicators/route.ts +24 -0
- package/src/app/api/kline/[provider]/[symbol]/[interval]/route.ts +123 -0
- package/src/app/api/scanner/[provider]/route.ts +41 -0
- package/src/app/api/scanner/route.ts +31 -0
- package/src/app/api/signal/[symbol]/[signalId]/route.ts +42 -0
- package/src/app/api/spread/[symbol]/[interval]/route.ts +57 -0
- package/src/app/api/spread/summary/route.ts +20 -0
- package/src/app/auth.ts +76 -0
- package/src/app/components/Backtest/CompareList/index.tsx +34 -0
- package/src/app/components/Backtest/TestCard/Chart/index.tsx +118 -0
- package/src/app/components/Backtest/TestCard/Chart/utils/index.ts +81 -0
- package/src/app/components/Backtest/TestCard/CompareButton/index.tsx +21 -0
- package/src/app/components/Backtest/TestCard/ConfigDrawer/JsonCodeBlock.tsx +46 -0
- package/src/app/components/Backtest/TestCard/ConfigDrawer/index.tsx +94 -0
- package/src/app/components/Backtest/TestCard/DeleteButton/index.tsx +128 -0
- package/src/app/components/Backtest/TestCard/FavoriteIndicator/index.tsx +18 -0
- package/src/app/components/Backtest/TestCard/OpenDashboardButton/index.tsx +40 -0
- package/src/app/components/Backtest/TestCard/OpenReportButton/index.tsx +24 -0
- package/src/app/components/Backtest/TestCard/Root/index.tsx +55 -0
- package/src/app/components/Backtest/TestCard/Skeleton/index.tsx +21 -0
- package/src/app/components/Backtest/TestCard/Stat/index.tsx +119 -0
- package/src/app/components/Backtest/TestCard/Title/index.tsx +84 -0
- package/src/app/components/Backtest/TestCard/context.ts +14 -0
- package/src/app/components/Backtest/TestCard/index.ts +28 -0
- package/src/app/components/Backtest/TestList/index.tsx +124 -0
- package/src/app/components/Dashboard/AiDrawer/Message.tsx +34 -0
- package/src/app/components/Dashboard/AiDrawer/index.tsx +163 -0
- package/src/app/components/Dashboard/KlineChart/figures/backtestFigureTypes.ts +7 -0
- package/src/app/components/Dashboard/KlineChart/figures/backtestMarkersPointFigure.ts +76 -0
- package/src/app/components/Dashboard/KlineChart/figures/circle.ts +15 -0
- package/src/app/components/Dashboard/KlineChart/figures/diamond.ts +25 -0
- package/src/app/components/Dashboard/KlineChart/figures/entryLinePointFigure.ts +1 -0
- package/src/app/components/Dashboard/KlineChart/figures/entryPointsPointFigure.ts +1 -0
- package/src/app/components/Dashboard/KlineChart/figures/entryZonePointFigure.ts +1 -0
- package/src/app/components/Dashboard/KlineChart/figures/index.ts +213 -0
- package/src/app/components/Dashboard/KlineChart/figures/label.ts +14 -0
- package/src/app/components/Dashboard/KlineChart/figures/rectangle.ts +20 -0
- package/src/app/components/Dashboard/KlineChart/figures/square.ts +21 -0
- package/src/app/components/Dashboard/KlineChart/figures/star.ts +39 -0
- package/src/app/components/Dashboard/KlineChart/figures/tradeZonePointFigure.ts +44 -0
- package/src/app/components/Dashboard/KlineChart/figures/trendLinePointFigure.ts +37 -0
- package/src/app/components/Dashboard/KlineChart/figures/trendLinePointsPointFigure.ts +26 -0
- package/src/app/components/Dashboard/KlineChart/figures/triangle.ts +23 -0
- package/src/app/components/Dashboard/KlineChart/hooks/index.ts +14 -0
- package/src/app/components/Dashboard/KlineChart/hooks/indicatorShared.ts +30 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useAtrIndicator.ts +75 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useBacktest.ts +533 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useBbIndicator.ts +74 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useBtcCorrelation.ts +155 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useBtcIndicator.ts +185 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useEmaIndicator.ts +62 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useMaIndicator.ts +62 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useManagedIndicator.ts +140 -0
- package/src/app/components/Dashboard/KlineChart/hooks/usePluginIndicators.ts +212 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useResize.ts +29 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useSetup.ts +122 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useSignal.ts +85 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useSpreadIndicator.ts +243 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useSupportResistanceLines.ts +125 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useTrendLine.ts +139 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useVolIndicator.ts +18 -0
- package/src/app/components/Dashboard/KlineChart/hooks/useWmaIndicator.ts +62 -0
- package/src/app/components/Dashboard/KlineChart/index.tsx +169 -0
- package/src/app/components/Dashboard/KlineChart/styles.ts +70 -0
- package/src/app/components/Dashboard/MainChart/index.tsx +35 -0
- package/src/app/components/Shared/AppShell.tsx +28 -0
- package/src/app/components/Shared/FavoriteButton/index.tsx +23 -0
- package/src/app/components/Shared/Filters/Backtest/index.tsx +164 -0
- package/src/app/components/Shared/Filters/FavoriteIndicator/index.tsx +18 -0
- package/src/app/components/Shared/Filters/Indicators/index.tsx +21 -0
- package/src/app/components/Shared/Filters/Interval/index.tsx +31 -0
- package/src/app/components/Shared/Filters/Interval/intervals.ts +6 -0
- package/src/app/components/Shared/Filters/Provider/index.tsx +32 -0
- package/src/app/components/Shared/Filters/Root/index.tsx +28 -0
- package/src/app/components/Shared/Filters/Symbol/index.tsx +49 -0
- package/src/app/components/Shared/Filters/context.ts +17 -0
- package/src/app/components/Shared/Filters/index.ts +17 -0
- package/src/app/components/Shared/Sidebar/index.tsx +72 -0
- package/src/app/components/UI/ColorMode/index.tsx +112 -0
- package/src/app/components/UI/EmptyState/index.tsx +28 -0
- package/src/app/components/UI/OverlaySpinner/index.tsx +23 -0
- package/src/app/components/UI/Segment/index.tsx +23 -0
- package/src/app/components/UI/Select/index.tsx +81 -0
- package/src/app/components/UI/SelectWithSearch/index.tsx +104 -0
- package/src/app/components/UI/Switcher/index.tsx +24 -0
- package/src/app/components/UI/Toaster/index.tsx +45 -0
- package/src/app/components/UI/index.ts +8 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +5 -0
- package/src/app/layout.tsx +31 -0
- package/src/app/page.tsx +14 -0
- package/src/app/provider.tsx +39 -0
- package/src/app/routes/backtest/[test]/page.tsx +33 -0
- package/src/app/routes/backtest/page.tsx +374 -0
- package/src/app/routes/dashboard/[provider]/[symbol]/[interval]/page.tsx +124 -0
- package/src/app/routes/dashboard/page.tsx +20 -0
- package/src/app/routes/derivatives/page.tsx +202 -0
- package/src/app/routes/signin/page.tsx +155 -0
- package/src/app/store/data.ts +144 -0
- package/src/app/store/filters.ts +29 -0
- package/src/app/store/index.ts +13 -0
- package/src/app/store/indicators.ts +229 -0
- package/src/app/store/tests.ts +464 -0
- package/src/app/store/tickers.ts +89 -0
- package/src/proxy.ts +142 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
DerivativesInterval,
|
|
4
|
+
getDerivativesRangeForSymbols,
|
|
5
|
+
} from '@tradejs/infra/timescale';
|
|
6
|
+
import { logger } from '@tradejs/infra/logger';
|
|
7
|
+
|
|
8
|
+
export const dynamic = 'force-dynamic';
|
|
9
|
+
|
|
10
|
+
interface Params {
|
|
11
|
+
symbol: string;
|
|
12
|
+
interval: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const asInterval = (value: string): DerivativesInterval | null => {
|
|
16
|
+
if (value === '15m' || value === '1h') return value;
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const GET = async (
|
|
21
|
+
request: NextRequest,
|
|
22
|
+
{ params }: { params: Promise<Params> },
|
|
23
|
+
) => {
|
|
24
|
+
try {
|
|
25
|
+
const { symbol, interval } = await params;
|
|
26
|
+
const tf = asInterval(interval);
|
|
27
|
+
if (!tf) {
|
|
28
|
+
return NextResponse.json({ error: 'Invalid interval' }, { status: 400 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fromRaw = Number(request.nextUrl.searchParams.get('from') ?? 0);
|
|
32
|
+
const toRaw = Number(request.nextUrl.searchParams.get('to') ?? Date.now());
|
|
33
|
+
const startMs =
|
|
34
|
+
Number.isFinite(fromRaw) && fromRaw > 0
|
|
35
|
+
? fromRaw
|
|
36
|
+
: Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
37
|
+
const endMs = Number.isFinite(toRaw) ? toRaw : Date.now();
|
|
38
|
+
|
|
39
|
+
const rows = await getDerivativesRangeForSymbols(
|
|
40
|
+
[String(symbol).toUpperCase()],
|
|
41
|
+
tf,
|
|
42
|
+
startMs,
|
|
43
|
+
endMs,
|
|
44
|
+
);
|
|
45
|
+
return NextResponse.json({
|
|
46
|
+
rows,
|
|
47
|
+
symbol: String(symbol).toUpperCase(),
|
|
48
|
+
interval: tf,
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.log('error', 'Derivatives range error: %o', error);
|
|
52
|
+
return NextResponse.json(
|
|
53
|
+
{ error: 'Failed to load derivatives data' },
|
|
54
|
+
{ status: 500 },
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { logger } from '@tradejs/infra/logger';
|
|
3
|
+
import { getDerivativesSummary } from '@tradejs/infra/timescale';
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
export const GET = async (request: NextRequest) => {
|
|
8
|
+
try {
|
|
9
|
+
const hoursRaw = Number(request.nextUrl.searchParams.get('hours') ?? 24);
|
|
10
|
+
const limitRaw = Number(request.nextUrl.searchParams.get('limit') ?? 500);
|
|
11
|
+
const summary = await getDerivativesSummary(hoursRaw, limitRaw);
|
|
12
|
+
return NextResponse.json(summary);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
logger.log('error', 'Derivatives summary error: %o', error);
|
|
15
|
+
return NextResponse.json(
|
|
16
|
+
{ error: 'Failed to load derivatives summary' },
|
|
17
|
+
{ status: 500 },
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export const runtime = 'nodejs';
|
|
6
|
+
|
|
7
|
+
const getProjectRoot = (): string => {
|
|
8
|
+
const fromEnv = String(process.env.PROJECT_CWD || '').trim();
|
|
9
|
+
return fromEnv ? path.resolve(fromEnv) : process.cwd();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
interface Params {
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function GET(
|
|
17
|
+
_req: Request,
|
|
18
|
+
{ params }: { params: Promise<Params> },
|
|
19
|
+
) {
|
|
20
|
+
try {
|
|
21
|
+
const { name } = await params;
|
|
22
|
+
const filePath = path.join(
|
|
23
|
+
getProjectRoot(),
|
|
24
|
+
'data',
|
|
25
|
+
'screenshots',
|
|
26
|
+
`${name}.png`,
|
|
27
|
+
);
|
|
28
|
+
const file = await fs.readFile(filePath);
|
|
29
|
+
|
|
30
|
+
const body = new Uint8Array(file);
|
|
31
|
+
|
|
32
|
+
return new NextResponse(body, {
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'image/png',
|
|
35
|
+
'Content-Length': String(file.byteLength),
|
|
36
|
+
'Cache-Control': 'public, max-age=60, immutable',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
} catch {
|
|
40
|
+
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
getPluginIndicatorCatalog,
|
|
4
|
+
getPluginIndicatorRenderers,
|
|
5
|
+
} from '@tradejs/core/indicators';
|
|
6
|
+
import { ensureIndicatorPluginsLoaded } from '@tradejs/node/registry';
|
|
7
|
+
import { logger } from '@tradejs/infra/logger';
|
|
8
|
+
|
|
9
|
+
export const dynamic = 'force-dynamic';
|
|
10
|
+
|
|
11
|
+
export const GET = async () => {
|
|
12
|
+
try {
|
|
13
|
+
await ensureIndicatorPluginsLoaded();
|
|
14
|
+
const data = getPluginIndicatorCatalog();
|
|
15
|
+
const renderers = getPluginIndicatorRenderers();
|
|
16
|
+
return NextResponse.json({ data, renderers });
|
|
17
|
+
} catch (error) {
|
|
18
|
+
logger.log('error', 'Indicators catalog error: %o', error);
|
|
19
|
+
return NextResponse.json(
|
|
20
|
+
{ error: 'Internal Server Error' },
|
|
21
|
+
{ status: 500 },
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
createIndicators,
|
|
4
|
+
getRegisteredIndicatorEntries,
|
|
5
|
+
} from '@tradejs/core/indicators';
|
|
6
|
+
import { getConnectorCreatorByProvider } from '@tradejs/node/connectors';
|
|
7
|
+
import { ensureIndicatorPluginsLoaded } from '@tradejs/node/registry';
|
|
8
|
+
import { logger } from '@tradejs/infra/logger';
|
|
9
|
+
import {
|
|
10
|
+
KlineChartData,
|
|
11
|
+
KlineRequest,
|
|
12
|
+
Interval,
|
|
13
|
+
ConnectorCreator,
|
|
14
|
+
} from '@tradejs/types';
|
|
15
|
+
|
|
16
|
+
export const dynamic = 'force-dynamic';
|
|
17
|
+
|
|
18
|
+
interface Params {
|
|
19
|
+
provider: string;
|
|
20
|
+
symbol: string;
|
|
21
|
+
interval: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const enrichWithPluginIndicators = (
|
|
25
|
+
data: KlineChartData,
|
|
26
|
+
btcData: KlineChartData,
|
|
27
|
+
pluginKeys: string[],
|
|
28
|
+
): KlineChartData => {
|
|
29
|
+
if (!pluginKeys.length || !data.length) {
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const history = createIndicators(data, btcData, {
|
|
34
|
+
includeMlPayload: false,
|
|
35
|
+
}).result() as Record<string, number[]>;
|
|
36
|
+
|
|
37
|
+
const nextData = data.map((candle) => ({ ...candle }));
|
|
38
|
+
|
|
39
|
+
for (const pluginKey of pluginKeys) {
|
|
40
|
+
const series = history[pluginKey];
|
|
41
|
+
if (!Array.isArray(series) || !series.length) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const startIdx = nextData.length - series.length;
|
|
46
|
+
for (let i = 0; i < series.length; i += 1) {
|
|
47
|
+
const candleIndex = startIdx + i;
|
|
48
|
+
if (candleIndex < 0 || candleIndex >= nextData.length) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const value = series[i];
|
|
52
|
+
if (!Number.isFinite(value)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
(nextData[candleIndex] as Record<string, unknown>)[pluginKey] = value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return nextData;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const POST = async (
|
|
63
|
+
request: NextRequest,
|
|
64
|
+
{ params }: { params: Promise<Params> },
|
|
65
|
+
) => {
|
|
66
|
+
try {
|
|
67
|
+
const { provider, symbol, interval } = await params;
|
|
68
|
+
const body = await request.json();
|
|
69
|
+
const options = body as
|
|
70
|
+
| Omit<KlineRequest, 'symbol' | 'interval'>
|
|
71
|
+
| undefined;
|
|
72
|
+
|
|
73
|
+
if (!options || !symbol || !interval || !options.end) {
|
|
74
|
+
return NextResponse.json(
|
|
75
|
+
{ error: 'Missing required kline parameters' },
|
|
76
|
+
{ status: 400 },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const connectorCreator =
|
|
81
|
+
(await getConnectorCreatorByProvider(provider)) ||
|
|
82
|
+
(await getConnectorCreatorByProvider('bybit'));
|
|
83
|
+
if (!connectorCreator) {
|
|
84
|
+
throw new Error('No connector available for provider');
|
|
85
|
+
}
|
|
86
|
+
const connector = await (connectorCreator as ConnectorCreator)({
|
|
87
|
+
userName: 'root',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const baseData = await connector.kline({
|
|
91
|
+
symbol,
|
|
92
|
+
interval: interval as Interval,
|
|
93
|
+
...options,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await ensureIndicatorPluginsLoaded();
|
|
97
|
+
const pluginKeys = getRegisteredIndicatorEntries().map(
|
|
98
|
+
(entry) => entry.historyKey || entry.indicator.id,
|
|
99
|
+
);
|
|
100
|
+
if (!pluginKeys.length) {
|
|
101
|
+
return NextResponse.json({ data: baseData });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const btcData =
|
|
105
|
+
symbol === 'BTCUSDT'
|
|
106
|
+
? baseData
|
|
107
|
+
: await connector.kline({
|
|
108
|
+
symbol: 'BTCUSDT',
|
|
109
|
+
interval: interval as Interval,
|
|
110
|
+
...options,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const data = enrichWithPluginIndicators(baseData, btcData, pluginKeys);
|
|
114
|
+
|
|
115
|
+
return NextResponse.json({ data });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.log('error', `Kline fetch error: %o`, error);
|
|
118
|
+
return NextResponse.json(
|
|
119
|
+
{ error: 'Internal Server Error' },
|
|
120
|
+
{ status: 500 },
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { ConnectorCreator } from '@tradejs/types';
|
|
3
|
+
import { getConnectorCreatorByProvider } from '@tradejs/node/connectors';
|
|
4
|
+
import { getTopTickers } from '@tradejs/core/tickers';
|
|
5
|
+
import { logger } from '@tradejs/infra/logger';
|
|
6
|
+
|
|
7
|
+
export const dynamic = 'force-dynamic';
|
|
8
|
+
|
|
9
|
+
interface Params {
|
|
10
|
+
provider: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const GET = async (
|
|
14
|
+
_request: Request,
|
|
15
|
+
{ params }: { params: Promise<Params> },
|
|
16
|
+
) => {
|
|
17
|
+
try {
|
|
18
|
+
const { provider } = await params;
|
|
19
|
+
const connectorCreator =
|
|
20
|
+
(await getConnectorCreatorByProvider(provider)) ||
|
|
21
|
+
(await getConnectorCreatorByProvider('bybit'));
|
|
22
|
+
if (!connectorCreator) {
|
|
23
|
+
throw new Error('No connector available for provider');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const connector = await (connectorCreator as ConnectorCreator)({
|
|
27
|
+
userName: 'root',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const data = await connector.getTickers();
|
|
31
|
+
const tickers = getTopTickers(data);
|
|
32
|
+
|
|
33
|
+
return NextResponse.json({ tickers });
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logger.log('error', `Scanner error: %o`, error);
|
|
36
|
+
return NextResponse.json(
|
|
37
|
+
{ error: 'Internal Server Error' },
|
|
38
|
+
{ status: 500 },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getTopTickers } from '@tradejs/core/tickers';
|
|
3
|
+
import { getConnectorCreatorByProvider } from '@tradejs/node/connectors';
|
|
4
|
+
import { logger } from '@tradejs/infra/logger';
|
|
5
|
+
import { ConnectorCreator } from '@tradejs/types';
|
|
6
|
+
|
|
7
|
+
export const dynamic = 'force-dynamic';
|
|
8
|
+
|
|
9
|
+
export const GET = async () => {
|
|
10
|
+
try {
|
|
11
|
+
const connectorCreator = await getConnectorCreatorByProvider('bybit');
|
|
12
|
+
if (!connectorCreator) {
|
|
13
|
+
throw new Error('No connector available for provider');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const byBitConnector = await (connectorCreator as ConnectorCreator)({
|
|
17
|
+
userName: 'root',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const data = await byBitConnector.getTickers();
|
|
21
|
+
const tickers = getTopTickers(data);
|
|
22
|
+
|
|
23
|
+
return NextResponse.json({ tickers });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logger.log('error', `Scanner error: %o`, error);
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: 'Internal Server Error' },
|
|
28
|
+
{ status: 500 },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import { getData, redisKeys } from '@tradejs/infra/redis';
|
|
4
|
+
import { logger } from '@tradejs/infra/logger';
|
|
5
|
+
import { Signal } from '@tradejs/types';
|
|
6
|
+
|
|
7
|
+
export const dynamic = 'force-dynamic';
|
|
8
|
+
|
|
9
|
+
interface Params {
|
|
10
|
+
symbol: string;
|
|
11
|
+
signalId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const GET = async (
|
|
15
|
+
_req: Request,
|
|
16
|
+
{ params }: { params: Promise<Params> },
|
|
17
|
+
) => {
|
|
18
|
+
try {
|
|
19
|
+
const { symbol, signalId } = await params;
|
|
20
|
+
|
|
21
|
+
if (!symbol || !signalId) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: 'Missing required parameters: symbol, signalId' },
|
|
24
|
+
{ status: 400 },
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let signal: Signal = await getData(redisKeys.signal(symbol, signalId));
|
|
29
|
+
|
|
30
|
+
if (!signal || _.isEmpty(signal)) {
|
|
31
|
+
signal = await getData(redisKeys.storeSignal(symbol, signalId));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return NextResponse.json({ signal });
|
|
35
|
+
} catch (error) {
|
|
36
|
+
logger.log('error', `Signal load error: %o`, error);
|
|
37
|
+
return NextResponse.json(
|
|
38
|
+
{ error: 'Internal Server Error' },
|
|
39
|
+
{ status: 500 },
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import {
|
|
3
|
+
DerivativesInterval,
|
|
4
|
+
getSpreadRangeForSymbols,
|
|
5
|
+
} from '@tradejs/infra/timescale';
|
|
6
|
+
import { logger } from '@tradejs/infra/logger';
|
|
7
|
+
|
|
8
|
+
export const dynamic = 'force-dynamic';
|
|
9
|
+
|
|
10
|
+
interface Params {
|
|
11
|
+
symbol: string;
|
|
12
|
+
interval: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const asInterval = (value: string): DerivativesInterval | null => {
|
|
16
|
+
if (value === '15m' || value === '1h') return value;
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const GET = async (
|
|
21
|
+
request: NextRequest,
|
|
22
|
+
{ params }: { params: Promise<Params> },
|
|
23
|
+
) => {
|
|
24
|
+
try {
|
|
25
|
+
const { symbol, interval } = await params;
|
|
26
|
+
const tf = asInterval(interval);
|
|
27
|
+
if (!tf) {
|
|
28
|
+
return NextResponse.json({ error: 'Invalid interval' }, { status: 400 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fromRaw = Number(request.nextUrl.searchParams.get('from') ?? 0);
|
|
32
|
+
const toRaw = Number(request.nextUrl.searchParams.get('to') ?? Date.now());
|
|
33
|
+
const startMs =
|
|
34
|
+
Number.isFinite(fromRaw) && fromRaw > 0
|
|
35
|
+
? fromRaw
|
|
36
|
+
: Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
37
|
+
const endMs = Number.isFinite(toRaw) ? toRaw : Date.now();
|
|
38
|
+
|
|
39
|
+
const rows = await getSpreadRangeForSymbols(
|
|
40
|
+
[String(symbol).toUpperCase()],
|
|
41
|
+
tf,
|
|
42
|
+
startMs,
|
|
43
|
+
endMs,
|
|
44
|
+
);
|
|
45
|
+
return NextResponse.json({
|
|
46
|
+
rows,
|
|
47
|
+
symbol: String(symbol).toUpperCase(),
|
|
48
|
+
interval: tf,
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger.log('error', 'Spread range error: %o', error);
|
|
52
|
+
return NextResponse.json(
|
|
53
|
+
{ error: 'Failed to load spread data' },
|
|
54
|
+
{ status: 500 },
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { logger } from '@tradejs/infra/logger';
|
|
3
|
+
import { getSpreadSummary } from '@tradejs/infra/timescale';
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic';
|
|
6
|
+
|
|
7
|
+
export const GET = async (request: NextRequest) => {
|
|
8
|
+
try {
|
|
9
|
+
const hoursRaw = Number(request.nextUrl.searchParams.get('hours') ?? 24);
|
|
10
|
+
const limitRaw = Number(request.nextUrl.searchParams.get('limit') ?? 500);
|
|
11
|
+
const summary = await getSpreadSummary(hoursRaw, limitRaw);
|
|
12
|
+
return NextResponse.json(summary);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
logger.log('error', 'Spread summary error: %o', error);
|
|
15
|
+
return NextResponse.json(
|
|
16
|
+
{ error: 'Failed to load spread summary' },
|
|
17
|
+
{ status: 500 },
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
};
|
package/src/app/auth.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import NextAuth from 'next-auth';
|
|
2
|
+
import Credentials from 'next-auth/providers/credentials';
|
|
3
|
+
import bcrypt from 'bcryptjs';
|
|
4
|
+
import { getData, redisKeys } from '@tradejs/infra/redis';
|
|
5
|
+
|
|
6
|
+
const getPasswordHash = (user: unknown): string | null => {
|
|
7
|
+
if (!user) return null;
|
|
8
|
+
if (typeof user === 'string') return user;
|
|
9
|
+
if (typeof user !== 'object') return null;
|
|
10
|
+
|
|
11
|
+
const record = user as Record<string, unknown>;
|
|
12
|
+
const direct = record.passwordHash ?? record.password;
|
|
13
|
+
if (typeof direct === 'string') return direct;
|
|
14
|
+
|
|
15
|
+
const nested = record.password as Record<string, unknown> | undefined;
|
|
16
|
+
const nestedHash = nested?.hash;
|
|
17
|
+
if (typeof nestedHash === 'string') return nestedHash;
|
|
18
|
+
|
|
19
|
+
const alt = record.hash;
|
|
20
|
+
if (typeof alt === 'string') return alt;
|
|
21
|
+
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const { handlers, auth, signIn, signOut } = NextAuth({
|
|
26
|
+
trustHost: true,
|
|
27
|
+
providers: [
|
|
28
|
+
Credentials({
|
|
29
|
+
name: 'Credentials',
|
|
30
|
+
credentials: {
|
|
31
|
+
username: { label: 'Username', type: 'text' },
|
|
32
|
+
password: { label: 'Password', type: 'password' },
|
|
33
|
+
},
|
|
34
|
+
authorize: async (credentials) => {
|
|
35
|
+
const username = credentials?.username?.toString().trim();
|
|
36
|
+
const password = credentials?.password?.toString();
|
|
37
|
+
|
|
38
|
+
if (!username || !password) return null;
|
|
39
|
+
|
|
40
|
+
const user = await getData(redisKeys.user(username), null);
|
|
41
|
+
const passwordHash = getPasswordHash(user);
|
|
42
|
+
if (!passwordHash) return null;
|
|
43
|
+
|
|
44
|
+
const isValid = await bcrypt.compare(password, passwordHash);
|
|
45
|
+
if (!isValid) return null;
|
|
46
|
+
|
|
47
|
+
return { id: username, name: username };
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
pages: {
|
|
52
|
+
signIn: '/routes/signin',
|
|
53
|
+
},
|
|
54
|
+
session: {
|
|
55
|
+
strategy: 'jwt',
|
|
56
|
+
},
|
|
57
|
+
callbacks: {
|
|
58
|
+
authorized: ({ auth: session }) => Boolean(session?.user),
|
|
59
|
+
jwt: ({ token, user }) => {
|
|
60
|
+
if (user) {
|
|
61
|
+
token.id = user.id;
|
|
62
|
+
token.name = user.name;
|
|
63
|
+
}
|
|
64
|
+
return token;
|
|
65
|
+
},
|
|
66
|
+
session: ({ session, token }) => {
|
|
67
|
+
if (token?.id) {
|
|
68
|
+
session.user = {
|
|
69
|
+
...session.user,
|
|
70
|
+
id: String(token.id),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return session;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { For, Tag, HStack, Text } from '@chakra-ui/react';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import { useTestsCompare } from '@store';
|
|
4
|
+
|
|
5
|
+
export const CompareList = () => {
|
|
6
|
+
const { compareList, onChangeCompare } = useTestsCompare();
|
|
7
|
+
|
|
8
|
+
if (_.isEmpty(compareList)) {
|
|
9
|
+
return (
|
|
10
|
+
<HStack p={2} gap={4}>
|
|
11
|
+
<Text color="gray.400">No compare tests</Text>
|
|
12
|
+
</HStack>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<HStack p={2} gap={4}>
|
|
18
|
+
<For each={compareList}>
|
|
19
|
+
{({ testResult, color }) => (
|
|
20
|
+
<Tag.Root key={testResult.test.testId} size="lg" colorPalette={color}>
|
|
21
|
+
<Tag.Label>
|
|
22
|
+
{testResult.test.symbol}-{testResult.test.testId}
|
|
23
|
+
</Tag.Label>
|
|
24
|
+
<Tag.EndElement>
|
|
25
|
+
<Tag.CloseTrigger
|
|
26
|
+
onClick={() => onChangeCompare(testResult.test.name)}
|
|
27
|
+
/>
|
|
28
|
+
</Tag.EndElement>
|
|
29
|
+
</Tag.Root>
|
|
30
|
+
)}
|
|
31
|
+
</For>
|
|
32
|
+
</HStack>
|
|
33
|
+
);
|
|
34
|
+
};
|