@tradejs/app 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tradejs-app.mjs +7 -0
- package/package.json +12 -12
- package/src/app/api/ai/route.ts +63 -20
- package/src/app/api/backtest/files/route.ts +12 -1
- package/src/app/api/backtest/test/[strategy]/[name]/route.ts +18 -1
- package/src/app/api/kline/[provider]/[symbol]/[interval]/route.ts +350 -28
- package/src/app/api/signal/[symbol]/[signalId]/route.ts +6 -0
- package/src/app/api/user/settings/route.ts +51 -23
- package/src/app/components/Dashboard/AiDrawer/index.tsx +38 -51
- package/src/app/components/Shared/Filters/Backtest/index.tsx +12 -5
- package/src/app/components/Shared/Filters/Root/index.tsx +12 -1
- package/src/app/components/Shared/Filters/Symbol/index.tsx +14 -19
- package/src/app/components/Shared/Filters/context.ts +2 -0
- package/src/app/components/Shared/Sidebar/AccountSettingsDrawer.tsx +250 -111
- package/src/app/components/UI/ColorMode/index.tsx +62 -15
- package/src/app/components/UI/Select/index.tsx +3 -0
- package/src/app/components/UI/SelectWithSearch/index.tsx +3 -0
- package/src/app/layout.tsx +10 -8
- package/src/app/lib/klineWindow.ts +17 -0
- package/src/app/routes/dashboard/[provider]/[symbol]/[interval]/page.tsx +10 -2
- package/src/app/store/ai.ts +174 -0
- package/src/app/store/data.ts +34 -8
- package/src/app/store/index.ts +1 -0
- package/src/app/store/tests.ts +96 -9
- package/src/app/store/tickers.ts +113 -17
- package/src/proxy.ts +23 -50
package/bin/tradejs-app.mjs
CHANGED
|
@@ -98,6 +98,13 @@ async function main() {
|
|
|
98
98
|
const nextBin = require.resolve('next/dist/bin/next');
|
|
99
99
|
const args = [nextBin, command, ...rawArgs];
|
|
100
100
|
const explicitPort = parsePort(readArgValue(rawArgs, ['-p', '--port']));
|
|
101
|
+
const hasBundlerFlag = rawArgs.some(
|
|
102
|
+
(arg) => arg === '--webpack' || arg === '--turbopack',
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if ((command === 'dev' || command === 'build') && !hasBundlerFlag) {
|
|
106
|
+
args.push('--webpack');
|
|
107
|
+
}
|
|
101
108
|
|
|
102
109
|
if (dev && explicitPort === null) {
|
|
103
110
|
const requestedPort = parsePort(process.env.PORT) || 3000;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/app",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "Installable Next.js UI for the TradeJS open-source framework: dashboards, backtests, charts, and runtime data.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -41,20 +41,20 @@
|
|
|
41
41
|
"@chakra-ui/charts": "^3.24.0",
|
|
42
42
|
"@chakra-ui/react": "^3.24.2",
|
|
43
43
|
"@emotion/react": "^11.14.0",
|
|
44
|
-
"@langchain/core": "^
|
|
45
|
-
"@langchain/openai": "^
|
|
46
|
-
"@tradejs/connectors": "^1.0.
|
|
47
|
-
"@tradejs/core": "^1.0.
|
|
48
|
-
"@tradejs/indicators": "^1.0.
|
|
49
|
-
"@tradejs/infra": "^1.0.
|
|
50
|
-
"@tradejs/node": "^1.0.
|
|
51
|
-
"@tradejs/types": "^1.0.
|
|
44
|
+
"@langchain/core": "^1.1.42",
|
|
45
|
+
"@langchain/openai": "^1.4.5",
|
|
46
|
+
"@tradejs/connectors": "^1.0.8",
|
|
47
|
+
"@tradejs/core": "^1.0.8",
|
|
48
|
+
"@tradejs/indicators": "^1.0.8",
|
|
49
|
+
"@tradejs/infra": "^1.0.8",
|
|
50
|
+
"@tradejs/node": "^1.0.8",
|
|
51
|
+
"@tradejs/types": "^1.0.8",
|
|
52
52
|
"bcryptjs": "^2.4.3",
|
|
53
53
|
"date-fns": "^3.3.1",
|
|
54
54
|
"idb-keyval": "^6.2.2",
|
|
55
55
|
"klinecharts": "10.0.0-alpha9",
|
|
56
|
-
"lodash": "^4.
|
|
57
|
-
"next": "^16.
|
|
56
|
+
"lodash": "^4.18.1",
|
|
57
|
+
"next": "^16.2.3",
|
|
58
58
|
"next-auth": "^5.0.0-beta.26",
|
|
59
59
|
"next-themes": "^0.4.6",
|
|
60
60
|
"react": "^19.2.3",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"dev": "node ./bin/tradejs-app.mjs dev",
|
|
73
73
|
"build": "NODE_ENV=production node ./bin/tradejs-app.mjs build",
|
|
74
74
|
"start": "node ./bin/tradejs-app.mjs start",
|
|
75
|
-
"lint": "yarn run -T eslint src
|
|
75
|
+
"lint": "ESLINT_USE_FLAT_CONFIG=true yarn run -T eslint src"
|
|
76
76
|
},
|
|
77
77
|
"license": "MIT",
|
|
78
78
|
"author": "aleksnick (https://github.com/aleksnick)"
|
package/src/app/api/ai/route.ts
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
SystemMessage,
|
|
7
7
|
} from '@langchain/core/messages';
|
|
8
8
|
import { toJson } from '@tradejs/core/data';
|
|
9
|
-
import {
|
|
9
|
+
import { getAiResponseLanguagePromptName } from '@tradejs/infra/aiLanguages';
|
|
10
|
+
import { DEFAULT_AI_MODEL, getOpenRouterModelKwargs } from '@tradejs/node/ai';
|
|
10
11
|
import { getConnectorCreatorByProvider } from '@tradejs/node/connectors';
|
|
11
12
|
import {
|
|
12
13
|
AIChatHistory,
|
|
@@ -14,29 +15,50 @@ import {
|
|
|
14
15
|
ConnectorCreator,
|
|
15
16
|
Filters,
|
|
16
17
|
} from '@tradejs/types';
|
|
17
|
-
import {
|
|
18
|
+
import { getData, redisKeys, setData } from '@tradejs/infra/redis';
|
|
18
19
|
import { logger } from '@tradejs/infra/logger';
|
|
19
20
|
import { getUserSettings } from '@tradejs/infra/userSettings';
|
|
20
21
|
import { getCurrentUserName } from '@app/lib/currentUser';
|
|
21
22
|
|
|
22
23
|
export const dynamic = 'force-dynamic';
|
|
23
24
|
|
|
24
|
-
const HISTORY_DIR = 'data/chats';
|
|
25
25
|
const projectRoot =
|
|
26
26
|
String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
27
27
|
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
28
|
+
const normalizeChatSymbolKey = (symbol: string): string => {
|
|
29
|
+
const normalized = symbol
|
|
30
|
+
.trim()
|
|
31
|
+
.toUpperCase()
|
|
32
|
+
.replace(/[^A-Z0-9._-]+/g, '_')
|
|
33
|
+
.replace(/_+/g, '_')
|
|
34
|
+
.replace(/^_+|_+$/g, '')
|
|
35
|
+
.slice(0, 120);
|
|
36
|
+
|
|
37
|
+
if (!normalized) {
|
|
38
|
+
throw new Error('Invalid AI chat symbol');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return normalized;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getHistoryKey = (userName: string, symbol: string) =>
|
|
45
|
+
redisKeys.aiChatHistory(userName, normalizeChatSymbolKey(symbol));
|
|
46
|
+
|
|
47
|
+
const getHistory = async (
|
|
48
|
+
userName: string,
|
|
49
|
+
symbol: string,
|
|
50
|
+
): Promise<AIChatHistory> => {
|
|
51
|
+
return (await getData(getHistoryKey(userName, symbol), [])) as AIChatHistory;
|
|
31
52
|
};
|
|
32
53
|
|
|
33
54
|
const appendMessagesToHistory = async (
|
|
55
|
+
userName: string,
|
|
34
56
|
symbol: string,
|
|
35
57
|
messages: AIChatHistory,
|
|
36
58
|
): Promise<void> => {
|
|
37
|
-
const history = await getHistory(symbol);
|
|
38
|
-
await
|
|
39
|
-
|
|
59
|
+
const history = await getHistory(userName, symbol);
|
|
60
|
+
await setData(getHistoryKey(userName, symbol), [...history, ...messages], {
|
|
61
|
+
expire: 0,
|
|
40
62
|
});
|
|
41
63
|
};
|
|
42
64
|
|
|
@@ -44,18 +66,21 @@ const buildMessages = (
|
|
|
44
66
|
filters: Filters,
|
|
45
67
|
historyEntry: AIChatMessage,
|
|
46
68
|
historyData: unknown,
|
|
69
|
+
responseLanguage: string,
|
|
47
70
|
) => {
|
|
48
71
|
const messages = new Array<BaseMessage>();
|
|
49
72
|
|
|
50
73
|
messages.push(
|
|
51
74
|
new SystemMessage(
|
|
52
|
-
|
|
75
|
+
`You are a crypto trader assistant. Reply in ${getAiResponseLanguagePromptName(
|
|
76
|
+
responseLanguage,
|
|
77
|
+
)}.`,
|
|
53
78
|
),
|
|
54
79
|
);
|
|
55
80
|
|
|
56
81
|
messages.push(
|
|
57
82
|
new SystemMessage(
|
|
58
|
-
|
|
83
|
+
`Here is the market data for ${filters.symbol}: ${toJson(historyData)}`,
|
|
59
84
|
),
|
|
60
85
|
);
|
|
61
86
|
|
|
@@ -72,19 +97,19 @@ const buildMessages = (
|
|
|
72
97
|
|
|
73
98
|
const invokeChatModel = async (messages: BaseMessage[], userName: string) => {
|
|
74
99
|
const settings = await getUserSettings(userName);
|
|
75
|
-
if (!settings.
|
|
100
|
+
if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
|
|
76
101
|
throw new Error(`AI settings are incomplete for user ${userName}`);
|
|
77
102
|
}
|
|
78
103
|
|
|
79
|
-
const modelKwargs = getOpenRouterModelKwargs(settings.
|
|
104
|
+
const modelKwargs = getOpenRouterModelKwargs(settings.AI_API_ENDPOINT);
|
|
80
105
|
|
|
81
106
|
const model = new ChatOpenAI({
|
|
82
107
|
temperature: 0.7,
|
|
83
|
-
modelName:
|
|
84
|
-
apiKey: settings.
|
|
108
|
+
modelName: settings.AI_MODEL || DEFAULT_AI_MODEL,
|
|
109
|
+
apiKey: settings.AI_API_KEY,
|
|
85
110
|
...(Object.keys(modelKwargs).length ? { modelKwargs } : {}),
|
|
86
111
|
configuration: {
|
|
87
|
-
baseURL: settings.
|
|
112
|
+
baseURL: settings.AI_API_ENDPOINT,
|
|
88
113
|
},
|
|
89
114
|
});
|
|
90
115
|
|
|
@@ -107,7 +132,13 @@ export const GET = async (request: NextRequest) => {
|
|
|
107
132
|
);
|
|
108
133
|
}
|
|
109
134
|
|
|
110
|
-
|
|
135
|
+
try {
|
|
136
|
+
normalizeChatSymbolKey(symbol);
|
|
137
|
+
} catch {
|
|
138
|
+
return NextResponse.json({ error: 'Invalid symbol' }, { status: 400 });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const history = await getHistory(userName, symbol);
|
|
111
142
|
return NextResponse.json({ history });
|
|
112
143
|
} catch (error) {
|
|
113
144
|
logger.log('error', `AI history error: %o`, error);
|
|
@@ -138,7 +169,13 @@ export const POST = async (request: NextRequest) => {
|
|
|
138
169
|
);
|
|
139
170
|
}
|
|
140
171
|
|
|
141
|
-
|
|
172
|
+
try {
|
|
173
|
+
normalizeChatSymbolKey(filters.symbol);
|
|
174
|
+
} catch {
|
|
175
|
+
return NextResponse.json({ error: 'Invalid symbol' }, { status: 400 });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await appendMessagesToHistory(userName, filters.symbol, [message]);
|
|
142
179
|
|
|
143
180
|
const connectorCreator = await getConnectorCreatorByProvider(
|
|
144
181
|
'bybit',
|
|
@@ -157,7 +194,13 @@ export const POST = async (request: NextRequest) => {
|
|
|
157
194
|
interval: '60',
|
|
158
195
|
});
|
|
159
196
|
|
|
160
|
-
const
|
|
197
|
+
const settings = await getUserSettings(userName);
|
|
198
|
+
const chatMessages = buildMessages(
|
|
199
|
+
filters,
|
|
200
|
+
message,
|
|
201
|
+
data.slice(-100),
|
|
202
|
+
settings.AI_RESPONSE_LANGUAGE,
|
|
203
|
+
);
|
|
161
204
|
|
|
162
205
|
const response = await invokeChatModel(chatMessages, userName);
|
|
163
206
|
|
|
@@ -166,7 +209,7 @@ export const POST = async (request: NextRequest) => {
|
|
|
166
209
|
text: response.content as string,
|
|
167
210
|
};
|
|
168
211
|
|
|
169
|
-
await appendMessagesToHistory(filters.symbol, [responseMessage]);
|
|
212
|
+
await appendMessagesToHistory(userName, filters.symbol, [responseMessage]);
|
|
170
213
|
|
|
171
214
|
return NextResponse.json({ message: responseMessage });
|
|
172
215
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
2
|
import { Item, TestStat } from '@tradejs/types';
|
|
3
3
|
import { parseTestName } from '@tradejs/core/backtest';
|
|
4
|
-
import { getData, getKeys, redisKeys } from '@tradejs/infra/redis';
|
|
4
|
+
import { getData, getKeys, redisKeys, setData } from '@tradejs/infra/redis';
|
|
5
5
|
import { logger } from '@tradejs/infra/logger';
|
|
6
6
|
import { auth } from '@app/auth';
|
|
7
7
|
|
|
@@ -17,6 +17,15 @@ export const GET = async () => {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const result = new Array<Item>();
|
|
20
|
+
const indexedItems = (await getData(
|
|
21
|
+
redisKeys.testSummaries(userName),
|
|
22
|
+
null,
|
|
23
|
+
)) as Item[] | null;
|
|
24
|
+
|
|
25
|
+
if (Array.isArray(indexedItems) && indexedItems.length) {
|
|
26
|
+
return NextResponse.json({ items: indexedItems });
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
const testsPrefix = redisKeys.tests(userName);
|
|
21
30
|
const keys = await getKeys(testsPrefix);
|
|
22
31
|
const configKeys = keys.filter((key) => key.endsWith(':config'));
|
|
@@ -49,6 +58,8 @@ export const GET = async () => {
|
|
|
49
58
|
});
|
|
50
59
|
}
|
|
51
60
|
|
|
61
|
+
await setData(redisKeys.testSummaries(userName), result, { expire: 0 });
|
|
62
|
+
|
|
52
63
|
return NextResponse.json({ items: result });
|
|
53
64
|
} catch (error) {
|
|
54
65
|
logger.log('error', `Backtest list error: %o`, error);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
3
|
import { NextResponse } from 'next/server';
|
|
4
|
-
import { delKey, redisKeys } from '@tradejs/infra/redis';
|
|
4
|
+
import { delKey, getData, redisKeys, setData } from '@tradejs/infra/redis';
|
|
5
|
+
import { Item } from '@tradejs/types';
|
|
5
6
|
import { logger } from '@tradejs/infra/logger';
|
|
6
7
|
import { auth } from '@app/auth';
|
|
7
8
|
|
|
@@ -46,6 +47,22 @@ export const DELETE = async (
|
|
|
46
47
|
);
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
const indexedItems = (await getData(
|
|
51
|
+
redisKeys.testSummaries(userName),
|
|
52
|
+
[],
|
|
53
|
+
)) as Item[];
|
|
54
|
+
const nextIndexedItems = indexedItems.filter(
|
|
55
|
+
(item) =>
|
|
56
|
+
!(
|
|
57
|
+
item?.value === name &&
|
|
58
|
+
typeof item?.data?.strategyName === 'string' &&
|
|
59
|
+
item.data.strategyName === strategy
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
await setData(redisKeys.testSummaries(userName), nextIndexedItems, {
|
|
63
|
+
expire: 0,
|
|
64
|
+
});
|
|
65
|
+
|
|
49
66
|
return NextResponse.json({ deleted: true, removedKeys });
|
|
50
67
|
} catch (error) {
|
|
51
68
|
logger.log('error', 'Backtest delete error: %o', error);
|