@tradejs/app 3.1.3 → 3.1.4
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 +6 -6
- package/src/app/api/user/runtime-deployments/[deploymentId]/strategies/[strategyName]/control/route.ts +2 -2
- package/src/app/api/user/runtime-deployments/route.ts +52 -57
- package/src/app/api/user/settings/route.ts +14 -30
- package/src/app/api/user/trading-accounts/route.ts +1 -42
- package/src/app/components/Shared/Sidebar/AccountSettingsDrawer.tsx +23 -63
- package/src/app/components/Strategies/RuntimeStrategyCard.tsx +24 -38
- package/src/app/components/Strategies/RuntimeStrategyConfigDrawer.tsx +43 -483
- package/src/app/api/user/runtime-strategy-configs/route.ts +0 -51
- package/src/app/api/user/runtime-strategy-releases/route.ts +0 -60
- package/src/app/lib/runtimeStrategyConfigForm.ts +0 -86
- package/src/app/lib/runtimeStrategyConfigService.ts +0 -279
- package/src/app/lib/runtimeStrategyReleaseService.ts +0 -123
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/app",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.4",
|
|
4
4
|
"description": "Installable Next.js UI for the TradeJS TypeScript framework: dashboards, backtests, charts, and runtime data.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
"@chakra-ui/charts": "3.36.1",
|
|
53
53
|
"@chakra-ui/react": "3.36.1",
|
|
54
54
|
"@emotion/react": "^11.14.0",
|
|
55
|
-
"@tradejs/core": "^3.1.
|
|
56
|
-
"@tradejs/indicators": "^3.1.
|
|
57
|
-
"@tradejs/infra": "^3.1.
|
|
58
|
-
"@tradejs/node": "^3.1.
|
|
59
|
-
"@tradejs/types": "^3.1.
|
|
55
|
+
"@tradejs/core": "^3.1.4",
|
|
56
|
+
"@tradejs/indicators": "^3.1.4",
|
|
57
|
+
"@tradejs/infra": "^3.1.4",
|
|
58
|
+
"@tradejs/node": "^3.1.4",
|
|
59
|
+
"@tradejs/types": "^3.1.4",
|
|
60
60
|
"@types/bcryptjs": "2.4.6",
|
|
61
61
|
"@types/lodash": "4.17.24",
|
|
62
62
|
"@types/node": "24.13.3",
|
|
@@ -42,13 +42,13 @@ export const PATCH = async (
|
|
|
42
42
|
const reference = deployment.strategies.find(
|
|
43
43
|
(strategy) => strategy.strategyName === strategyName,
|
|
44
44
|
);
|
|
45
|
-
if (!reference
|
|
45
|
+
if (!reference) {
|
|
46
46
|
return NextResponse.json(
|
|
47
47
|
{ error: 'Pause/resume requires a versioned strategy release' },
|
|
48
48
|
{ status: 400 },
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
|
-
const previousState = reference.controlState
|
|
51
|
+
const previousState = reference.controlState;
|
|
52
52
|
if (previousState === controlState) {
|
|
53
53
|
return NextResponse.json({ deployment, controlState });
|
|
54
54
|
}
|
|
@@ -6,11 +6,33 @@ import {
|
|
|
6
6
|
} from '@tradejs/infra/runtimeDeployments';
|
|
7
7
|
import { getTradingAccount } from '@tradejs/infra/tradingAccounts';
|
|
8
8
|
import { getRuntimeStrategyRelease } from '@tradejs/infra/runtimeStrategyReleases';
|
|
9
|
-
import
|
|
9
|
+
import type {
|
|
10
|
+
MarketUniverse,
|
|
11
|
+
RuntimeDeployment,
|
|
12
|
+
RuntimeDeploymentStrategy,
|
|
13
|
+
} from '@tradejs/types';
|
|
10
14
|
import { getCurrentUserName } from '#app/lib/currentUser';
|
|
11
15
|
|
|
12
16
|
export const dynamic = 'force-dynamic';
|
|
13
17
|
|
|
18
|
+
const isRuntimeStrategyReference = (
|
|
19
|
+
value: unknown,
|
|
20
|
+
): value is RuntimeDeploymentStrategy => {
|
|
21
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
22
|
+
const record = value as Record<string, unknown>;
|
|
23
|
+
return (
|
|
24
|
+
typeof record.strategyName === 'string' &&
|
|
25
|
+
record.strategyName.trim().length > 0 &&
|
|
26
|
+
Number.isSafeInteger(record.releaseVersion) &&
|
|
27
|
+
Number(record.releaseVersion) > 0 &&
|
|
28
|
+
(record.controlState === 'active' ||
|
|
29
|
+
record.controlState === 'entries_paused') &&
|
|
30
|
+
Object.keys(record).every((key) =>
|
|
31
|
+
['strategyName', 'releaseVersion', 'controlState'].includes(key),
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
14
36
|
export const GET = async () => {
|
|
15
37
|
const userName = await getCurrentUserName();
|
|
16
38
|
if (!userName) {
|
|
@@ -33,60 +55,38 @@ export const POST = async (request: NextRequest) => {
|
|
|
33
55
|
}
|
|
34
56
|
try {
|
|
35
57
|
const body = (await request.json()) as Partial<RuntimeDeployment>;
|
|
36
|
-
const hasVersionedStrategies = Boolean(
|
|
37
|
-
body.strategies?.some((strategy) => strategy.releaseVersion != null),
|
|
38
|
-
);
|
|
39
58
|
if (
|
|
40
|
-
|
|
41
|
-
body.strategies
|
|
59
|
+
!Array.isArray(body.strategies) ||
|
|
60
|
+
!body.strategies.length ||
|
|
61
|
+
!body.strategies.every(isRuntimeStrategyReference)
|
|
42
62
|
) {
|
|
43
63
|
return NextResponse.json(
|
|
44
|
-
{ error: '
|
|
64
|
+
{ error: 'Invalid runtime strategy reference' },
|
|
45
65
|
{ status: 400 },
|
|
46
66
|
);
|
|
47
67
|
}
|
|
48
|
-
const releases =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!release) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`Release not found: ${strategy.strategyName} v${strategy.releaseVersion}`,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
return release;
|
|
69
|
-
}),
|
|
70
|
-
)
|
|
71
|
-
: [];
|
|
72
|
-
const universe = releases[0]?.config.UNIVERSE ?? body.universe;
|
|
73
|
-
const interval = releases[0]?.config.INTERVAL ?? body.interval;
|
|
68
|
+
const releases = await Promise.all(
|
|
69
|
+
body.strategies.map(async (strategy) => {
|
|
70
|
+
const release = await getRuntimeStrategyRelease(
|
|
71
|
+
userName,
|
|
72
|
+
strategy.strategyName,
|
|
73
|
+
strategy.releaseVersion,
|
|
74
|
+
);
|
|
75
|
+
if (!release) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Release not found: ${strategy.strategyName} v${strategy.releaseVersion}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return release;
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
74
83
|
if (
|
|
75
84
|
!body.id ||
|
|
76
85
|
!body.label ||
|
|
77
86
|
!body.connectorName ||
|
|
78
87
|
!body.provider ||
|
|
79
88
|
!body.accountId ||
|
|
80
|
-
|
|
81
|
-
!isMarketUniverse(universe) ||
|
|
82
|
-
!Array.isArray(body.strategies) ||
|
|
83
|
-
!body.strategies.length ||
|
|
84
|
-
body.strategies.some((strategy) =>
|
|
85
|
-
hasVersionedStrategies
|
|
86
|
-
? !String(strategy.strategyName ?? '').trim()
|
|
87
|
-
: !String(strategy.strategyName ?? '').trim() ||
|
|
88
|
-
!String(strategy.policyProfileId ?? '').trim(),
|
|
89
|
-
)
|
|
89
|
+
typeof body.enabled !== 'boolean'
|
|
90
90
|
) {
|
|
91
91
|
return NextResponse.json(
|
|
92
92
|
{ error: 'Invalid runtime deployment' },
|
|
@@ -104,9 +104,14 @@ export const POST = async (request: NextRequest) => {
|
|
|
104
104
|
{ status: 400 },
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
|
-
|
|
107
|
+
const unsupportedUniverse = releases
|
|
108
|
+
.map((release) => release.config.UNIVERSE as MarketUniverse)
|
|
109
|
+
.find((universe) => !account.universes.includes(universe));
|
|
110
|
+
if (unsupportedUniverse) {
|
|
108
111
|
return NextResponse.json(
|
|
109
|
-
{
|
|
112
|
+
{
|
|
113
|
+
error: `Account ${account.id} does not support ${unsupportedUniverse}`,
|
|
114
|
+
},
|
|
110
115
|
{ status: 400 },
|
|
111
116
|
);
|
|
112
117
|
}
|
|
@@ -116,18 +121,8 @@ export const POST = async (request: NextRequest) => {
|
|
|
116
121
|
connectorName: body.connectorName,
|
|
117
122
|
provider: body.provider,
|
|
118
123
|
accountId: body.accountId,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
enabled: body.enabled !== false,
|
|
122
|
-
strategies: body.strategies.map((strategy) =>
|
|
123
|
-
hasVersionedStrategies
|
|
124
|
-
? {
|
|
125
|
-
strategyName: strategy.strategyName,
|
|
126
|
-
releaseVersion: strategy.releaseVersion,
|
|
127
|
-
controlState: strategy.controlState ?? 'active',
|
|
128
|
-
}
|
|
129
|
-
: strategy,
|
|
130
|
-
),
|
|
124
|
+
enabled: body.enabled,
|
|
125
|
+
strategies: body.strategies,
|
|
131
126
|
assetClasses: body.assetClasses,
|
|
132
127
|
tickers: body.tickers,
|
|
133
128
|
});
|
|
@@ -18,13 +18,6 @@ import { getCurrentUserName } from '#app/lib/currentUser';
|
|
|
18
18
|
export const dynamic = 'force-dynamic';
|
|
19
19
|
|
|
20
20
|
type UpdateBody =
|
|
21
|
-
| {
|
|
22
|
-
section: 'bybit';
|
|
23
|
-
data?: {
|
|
24
|
-
apiKey?: string;
|
|
25
|
-
apiSecret?: string;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
21
|
| {
|
|
29
22
|
section: 'coinalyze';
|
|
30
23
|
data?: {
|
|
@@ -61,6 +54,14 @@ type UpdateBody =
|
|
|
61
54
|
};
|
|
62
55
|
};
|
|
63
56
|
|
|
57
|
+
const UPDATE_SECTIONS = new Set([
|
|
58
|
+
'coinalyze',
|
|
59
|
+
'coinmarketcap',
|
|
60
|
+
'ai',
|
|
61
|
+
'telegram',
|
|
62
|
+
'password',
|
|
63
|
+
]);
|
|
64
|
+
|
|
64
65
|
const cleanText = (value: unknown): string =>
|
|
65
66
|
typeof value === 'string' ? value.trim() : '';
|
|
66
67
|
|
|
@@ -99,10 +100,6 @@ const toResponse = (rawSettings: UserSettings) => {
|
|
|
99
100
|
return {
|
|
100
101
|
userName: settings.userName,
|
|
101
102
|
settings: {
|
|
102
|
-
bybit: {
|
|
103
|
-
apiKey: maskSecret(settings.BYBIT_API_KEY),
|
|
104
|
-
apiSecret: maskSecret(settings.BYBIT_API_SECRET),
|
|
105
|
-
},
|
|
106
103
|
coinalyze: {
|
|
107
104
|
apiKey: maskSecret(settings.COINALYZE_API_KEY),
|
|
108
105
|
},
|
|
@@ -153,7 +150,12 @@ export const PATCH = async (request: Request) => {
|
|
|
153
150
|
|
|
154
151
|
await removeLegacyPasswordlessToken(userName);
|
|
155
152
|
const body = (await request.json()) as UpdateBody | null;
|
|
156
|
-
if (
|
|
153
|
+
if (
|
|
154
|
+
!body ||
|
|
155
|
+
typeof body !== 'object' ||
|
|
156
|
+
!('section' in body) ||
|
|
157
|
+
!UPDATE_SECTIONS.has(String(body.section))
|
|
158
|
+
) {
|
|
157
159
|
return NextResponse.json({ error: 'Invalid payload' }, { status: 400 });
|
|
158
160
|
}
|
|
159
161
|
|
|
@@ -181,24 +183,6 @@ export const PATCH = async (request: Request) => {
|
|
|
181
183
|
return NextResponse.json(toResponse(settings));
|
|
182
184
|
}
|
|
183
185
|
|
|
184
|
-
if (body.section === 'bybit') {
|
|
185
|
-
const patch: Partial<UserRecord> = {};
|
|
186
|
-
const apiKey = cleanOptionalText(body.data?.apiKey);
|
|
187
|
-
const apiSecret = cleanOptionalText(body.data?.apiSecret);
|
|
188
|
-
|
|
189
|
-
if (apiKey) {
|
|
190
|
-
patch.BYBIT_API_KEY = apiKey;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (apiSecret) {
|
|
194
|
-
patch.BYBIT_API_SECRET = apiSecret;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (hasKeys(patch)) {
|
|
198
|
-
await updateUserRecord(userName, patch);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
186
|
if (body.section === 'coinalyze') {
|
|
203
187
|
const apiKey = cleanOptionalText(body.data?.apiKey);
|
|
204
188
|
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
listTradingAccounts,
|
|
5
5
|
saveTradingAccount,
|
|
6
6
|
} from '@tradejs/infra/tradingAccounts';
|
|
7
|
-
import { getUserSettings } from '@tradejs/infra/userSettings';
|
|
8
7
|
import { isMarketUniverse, type TradingAccountRef } from '@tradejs/types';
|
|
9
8
|
import { getCurrentUserName } from '#app/lib/currentUser';
|
|
10
9
|
|
|
@@ -26,52 +25,12 @@ const toPublicAccount = (account: TradingAccountRef) => {
|
|
|
26
25
|
};
|
|
27
26
|
};
|
|
28
27
|
|
|
29
|
-
const migrateLegacyBybitAccount = async (
|
|
30
|
-
userName: string,
|
|
31
|
-
accounts: TradingAccountRef[],
|
|
32
|
-
) => {
|
|
33
|
-
const upgradedAccounts = await Promise.all(
|
|
34
|
-
accounts.map((account) =>
|
|
35
|
-
account.provider === 'bybit' && !account.universes?.includes('tradfi')
|
|
36
|
-
? saveTradingAccount(userName, {
|
|
37
|
-
...account,
|
|
38
|
-
universes: ['crypto', 'tradfi'],
|
|
39
|
-
})
|
|
40
|
-
: account,
|
|
41
|
-
),
|
|
42
|
-
);
|
|
43
|
-
if (upgradedAccounts.some(({ provider }) => provider === 'bybit')) {
|
|
44
|
-
return upgradedAccounts;
|
|
45
|
-
}
|
|
46
|
-
const settings = await getUserSettings(userName);
|
|
47
|
-
if (!settings.BYBIT_API_KEY || !settings.BYBIT_API_SECRET) {
|
|
48
|
-
return upgradedAccounts;
|
|
49
|
-
}
|
|
50
|
-
const migrated = await saveTradingAccount(userName, {
|
|
51
|
-
id: 'bybit-default',
|
|
52
|
-
label: 'Bybit',
|
|
53
|
-
provider: 'bybit',
|
|
54
|
-
enabled: true,
|
|
55
|
-
isDefault: true,
|
|
56
|
-
universes: ['crypto', 'tradfi'],
|
|
57
|
-
environment: 'mainnet',
|
|
58
|
-
apiKey: settings.BYBIT_API_KEY,
|
|
59
|
-
apiSecret: settings.BYBIT_API_SECRET,
|
|
60
|
-
});
|
|
61
|
-
return [...upgradedAccounts, migrated].sort((left, right) =>
|
|
62
|
-
left.label.localeCompare(right.label),
|
|
63
|
-
);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
28
|
export const GET = async () => {
|
|
67
29
|
const userName = await getCurrentUserName();
|
|
68
30
|
if (!userName) {
|
|
69
31
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
70
32
|
}
|
|
71
|
-
const accounts = await
|
|
72
|
-
userName,
|
|
73
|
-
await listTradingAccounts(userName),
|
|
74
|
-
);
|
|
33
|
+
const accounts = await listTradingAccounts(userName);
|
|
75
34
|
return NextResponse.json({ accounts: accounts.map(toPublicAccount) });
|
|
76
35
|
};
|
|
77
36
|
|
|
@@ -38,10 +38,6 @@ import { TradingAccountsPanel } from './TradingAccountsPanel';
|
|
|
38
38
|
type SettingsResponse = {
|
|
39
39
|
userName: string;
|
|
40
40
|
settings: {
|
|
41
|
-
bybit: {
|
|
42
|
-
apiKey: string;
|
|
43
|
-
apiSecret: string;
|
|
44
|
-
};
|
|
45
41
|
coinalyze: {
|
|
46
42
|
apiKey: string;
|
|
47
43
|
};
|
|
@@ -67,8 +63,6 @@ type SettingsErrorResponse = {
|
|
|
67
63
|
|
|
68
64
|
type SettingsViewState = {
|
|
69
65
|
userName: string;
|
|
70
|
-
bybitApiKey: string;
|
|
71
|
-
bybitApiSecret: string;
|
|
72
66
|
coinalyzeApiKey: string;
|
|
73
67
|
coinmarketcapApiKey: string;
|
|
74
68
|
aiApiKey: string;
|
|
@@ -87,15 +81,12 @@ type PasswordState = {
|
|
|
87
81
|
};
|
|
88
82
|
|
|
89
83
|
type SectionName =
|
|
90
|
-
| 'bybit'
|
|
91
84
|
| 'password'
|
|
92
85
|
| 'coinalyze'
|
|
93
86
|
| 'coinmarketcap'
|
|
94
87
|
| 'ai'
|
|
95
88
|
| 'telegram';
|
|
96
89
|
type EditableField =
|
|
97
|
-
| 'bybitApiKey'
|
|
98
|
-
| 'bybitApiSecret'
|
|
99
90
|
| 'coinalyzeApiKey'
|
|
100
91
|
| 'coinmarketcapApiKey'
|
|
101
92
|
| 'aiApiKey'
|
|
@@ -107,8 +98,6 @@ type EditableField =
|
|
|
107
98
|
|
|
108
99
|
const EMPTY_SETTINGS: SettingsViewState = {
|
|
109
100
|
userName: '',
|
|
110
|
-
bybitApiKey: '',
|
|
111
|
-
bybitApiSecret: '',
|
|
112
101
|
coinalyzeApiKey: '',
|
|
113
102
|
coinmarketcapApiKey: '',
|
|
114
103
|
aiApiKey: '',
|
|
@@ -120,8 +109,6 @@ const EMPTY_SETTINGS: SettingsViewState = {
|
|
|
120
109
|
};
|
|
121
110
|
|
|
122
111
|
const EMPTY_DRAFTS: SettingsDraftState = {
|
|
123
|
-
bybitApiKey: '',
|
|
124
|
-
bybitApiSecret: '',
|
|
125
112
|
coinalyzeApiKey: '',
|
|
126
113
|
coinmarketcapApiKey: '',
|
|
127
114
|
aiApiKey: '',
|
|
@@ -138,8 +125,6 @@ const EMPTY_PASSWORDS: PasswordState = {
|
|
|
138
125
|
};
|
|
139
126
|
|
|
140
127
|
const EMPTY_EDITING: Record<EditableField, boolean> = {
|
|
141
|
-
bybitApiKey: false,
|
|
142
|
-
bybitApiSecret: false,
|
|
143
128
|
coinalyzeApiKey: false,
|
|
144
129
|
coinmarketcapApiKey: false,
|
|
145
130
|
aiApiKey: false,
|
|
@@ -150,20 +135,7 @@ const EMPTY_EDITING: Record<EditableField, boolean> = {
|
|
|
150
135
|
tgChatId: false,
|
|
151
136
|
};
|
|
152
137
|
|
|
153
|
-
const SECTION_FIELDS: Record<
|
|
154
|
-
Exclude<SectionName, 'password'>,
|
|
155
|
-
EditableField[]
|
|
156
|
-
> = {
|
|
157
|
-
bybit: ['bybitApiKey', 'bybitApiSecret'],
|
|
158
|
-
coinalyze: ['coinalyzeApiKey'],
|
|
159
|
-
coinmarketcap: ['coinmarketcapApiKey'],
|
|
160
|
-
ai: ['aiApiKey', 'aiApiEndpoint', 'aiModel', 'aiResponseLanguage'],
|
|
161
|
-
telegram: ['tgBotToken', 'tgChatId'],
|
|
162
|
-
};
|
|
163
|
-
|
|
164
138
|
const MASKED_FIELDS = new Set<EditableField>([
|
|
165
|
-
'bybitApiKey',
|
|
166
|
-
'bybitApiSecret',
|
|
167
139
|
'coinalyzeApiKey',
|
|
168
140
|
'coinmarketcapApiKey',
|
|
169
141
|
'aiApiKey',
|
|
@@ -172,8 +144,6 @@ const MASKED_FIELDS = new Set<EditableField>([
|
|
|
172
144
|
|
|
173
145
|
const toViewState = (payload: SettingsResponse): SettingsViewState => ({
|
|
174
146
|
userName: payload.userName,
|
|
175
|
-
bybitApiKey: payload.settings.bybit.apiKey || '',
|
|
176
|
-
bybitApiSecret: payload.settings.bybit.apiSecret || '',
|
|
177
147
|
coinalyzeApiKey: payload.settings.coinalyze.apiKey || '',
|
|
178
148
|
coinmarketcapApiKey: payload.settings.coinmarketcap.apiKey || '',
|
|
179
149
|
aiApiKey: payload.settings.ai.apiKey || '',
|
|
@@ -349,9 +319,7 @@ export const AccountSettingsDrawer = () => {
|
|
|
349
319
|
);
|
|
350
320
|
}
|
|
351
321
|
|
|
352
|
-
return
|
|
353
|
-
Boolean(drafts[field].trim()),
|
|
354
|
-
);
|
|
322
|
+
return false;
|
|
355
323
|
},
|
|
356
324
|
[drafts, passwords.confirmPassword, passwords.password, settings],
|
|
357
325
|
);
|
|
@@ -437,56 +405,48 @@ export const AccountSettingsDrawer = () => {
|
|
|
437
405
|
|
|
438
406
|
try {
|
|
439
407
|
const body =
|
|
440
|
-
section === '
|
|
408
|
+
section === 'coinalyze'
|
|
441
409
|
? {
|
|
442
410
|
section,
|
|
443
411
|
data: {
|
|
444
|
-
apiKey: getSecretUpdateValue('
|
|
445
|
-
apiSecret: getSecretUpdateValue('bybitApiSecret'),
|
|
412
|
+
apiKey: getSecretUpdateValue('coinalyzeApiKey'),
|
|
446
413
|
},
|
|
447
414
|
}
|
|
448
|
-
: section === '
|
|
415
|
+
: section === 'ai'
|
|
449
416
|
? {
|
|
450
417
|
section,
|
|
451
418
|
data: {
|
|
452
|
-
apiKey: getSecretUpdateValue('
|
|
419
|
+
apiKey: getSecretUpdateValue('aiApiKey'),
|
|
420
|
+
apiEndpoint: drafts.aiApiEndpoint,
|
|
421
|
+
model: drafts.aiModel.trim(),
|
|
422
|
+
responseLanguage: drafts.aiResponseLanguage,
|
|
453
423
|
},
|
|
454
424
|
}
|
|
455
|
-
: section === '
|
|
425
|
+
: section === 'coinmarketcap'
|
|
456
426
|
? {
|
|
457
427
|
section,
|
|
458
428
|
data: {
|
|
459
|
-
apiKey: getSecretUpdateValue('
|
|
460
|
-
apiEndpoint: drafts.aiApiEndpoint,
|
|
461
|
-
model: drafts.aiModel.trim(),
|
|
462
|
-
responseLanguage: drafts.aiResponseLanguage,
|
|
429
|
+
apiKey: getSecretUpdateValue('coinmarketcapApiKey'),
|
|
463
430
|
},
|
|
464
431
|
}
|
|
465
|
-
: section === '
|
|
432
|
+
: section === 'telegram'
|
|
466
433
|
? {
|
|
467
434
|
section,
|
|
468
435
|
data: {
|
|
469
|
-
|
|
436
|
+
botToken: getSecretUpdateValue('tgBotToken'),
|
|
437
|
+
chatId:
|
|
438
|
+
drafts.tgChatId !== settings.tgChatId
|
|
439
|
+
? drafts.tgChatId.trim()
|
|
440
|
+
: undefined,
|
|
470
441
|
},
|
|
471
442
|
}
|
|
472
|
-
:
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
? drafts.tgChatId.trim()
|
|
480
|
-
: undefined,
|
|
481
|
-
},
|
|
482
|
-
}
|
|
483
|
-
: {
|
|
484
|
-
section,
|
|
485
|
-
data: {
|
|
486
|
-
password: passwords.password,
|
|
487
|
-
confirmPassword: passwords.confirmPassword,
|
|
488
|
-
},
|
|
489
|
-
};
|
|
443
|
+
: {
|
|
444
|
+
section,
|
|
445
|
+
data: {
|
|
446
|
+
password: passwords.password,
|
|
447
|
+
confirmPassword: passwords.confirmPassword,
|
|
448
|
+
},
|
|
449
|
+
};
|
|
490
450
|
|
|
491
451
|
const response = await fetch('/api/user/settings', {
|
|
492
452
|
method: 'PATCH',
|
|
@@ -72,7 +72,6 @@ export const RuntimeStrategyCard = ({
|
|
|
72
72
|
);
|
|
73
73
|
const { lastTrade, runtimeOrders } = viewModel;
|
|
74
74
|
const setControlState = async (controlState: 'active' | 'entries_paused') => {
|
|
75
|
-
if (!strategy.deploymentId || !strategy.releaseVersion) return;
|
|
76
75
|
setControlSaving(true);
|
|
77
76
|
try {
|
|
78
77
|
const response = await fetch(
|
|
@@ -135,25 +134,17 @@ export const RuntimeStrategyCard = ({
|
|
|
135
134
|
<Badge colorPalette="cyan" variant="outline">
|
|
136
135
|
TF: {strategy.interval}m
|
|
137
136
|
</Badge>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
</Badge>
|
|
142
|
-
) : (
|
|
143
|
-
<Badge colorPalette="gray" variant="outline">
|
|
144
|
-
legacy config: {strategy.configId}
|
|
145
|
-
</Badge>
|
|
146
|
-
)}
|
|
137
|
+
<Badge colorPalette="gray" variant="outline">
|
|
138
|
+
release: v{strategy.releaseVersion}
|
|
139
|
+
</Badge>
|
|
147
140
|
{strategy.accountId ? (
|
|
148
141
|
<Badge colorPalette="purple" variant="outline">
|
|
149
142
|
account: {strategy.accountLabel ?? strategy.accountId}
|
|
150
143
|
</Badge>
|
|
151
144
|
) : null}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
</Badge>
|
|
156
|
-
) : null}
|
|
145
|
+
<Badge colorPalette="orange" variant="outline">
|
|
146
|
+
deployment: {strategy.deploymentId}
|
|
147
|
+
</Badge>
|
|
157
148
|
{strategy.policyProfileId ? (
|
|
158
149
|
<Badge colorPalette="cyan" variant="outline">
|
|
159
150
|
policy: {strategy.policyProfileId}
|
|
@@ -211,28 +202,24 @@ export const RuntimeStrategyCard = ({
|
|
|
211
202
|
<Portal>
|
|
212
203
|
<Menu.Positioner>
|
|
213
204
|
<Menu.Content minW="160px">
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
? 'Resume entries'
|
|
233
|
-
: 'Pause new entries'}
|
|
234
|
-
</Menu.Item>
|
|
235
|
-
) : null}
|
|
205
|
+
<Menu.Item value="config" onClick={() => setConfigOpen(true)}>
|
|
206
|
+
View config
|
|
207
|
+
</Menu.Item>
|
|
208
|
+
<Menu.Item
|
|
209
|
+
value="control"
|
|
210
|
+
disabled={controlSaving}
|
|
211
|
+
onClick={() =>
|
|
212
|
+
void setControlState(
|
|
213
|
+
strategy.controlState === 'entries_paused'
|
|
214
|
+
? 'active'
|
|
215
|
+
: 'entries_paused',
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
>
|
|
219
|
+
{strategy.controlState === 'entries_paused'
|
|
220
|
+
? 'Resume entries'
|
|
221
|
+
: 'Pause new entries'}
|
|
222
|
+
</Menu.Item>
|
|
236
223
|
<Menu.Item value="orders" onClick={() => setOrdersOpen(true)}>
|
|
237
224
|
Orders
|
|
238
225
|
</Menu.Item>
|
|
@@ -258,7 +245,6 @@ export const RuntimeStrategyCard = ({
|
|
|
258
245
|
open={configOpen}
|
|
259
246
|
strategy={strategy}
|
|
260
247
|
onOpenChange={setConfigOpen}
|
|
261
|
-
onSaved={onUpdated}
|
|
262
248
|
/>
|
|
263
249
|
|
|
264
250
|
<RuntimeStrategyStatsDrawer
|