@trading-boy/cli 0.2.5 → 0.2.6

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.
@@ -0,0 +1,217 @@
1
+ import { Option } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { createLogger } from '@trading-boy/core';
4
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
5
+ // ─── Logger ───
6
+ const logger = createLogger('cli-replay');
7
+ // ─── Helpers ───
8
+ function formatPercent(val) {
9
+ const sign = val >= 0 ? '+' : '';
10
+ const str = `${sign}${val.toFixed(2)}%`;
11
+ return val >= 0 ? chalk.green(str) : chalk.red(str);
12
+ }
13
+ function formatShortDate(isoString) {
14
+ try {
15
+ return new Date(isoString).toISOString().slice(0, 10);
16
+ }
17
+ catch {
18
+ return isoString;
19
+ }
20
+ }
21
+ // ─── Formatters ───
22
+ export function formatReplayResult(result) {
23
+ const lines = [];
24
+ lines.push('');
25
+ lines.push(chalk.bold.cyan(` Replay Results — ${result.tokenSymbol} (strategy: ${result.strategyId})`));
26
+ lines.push(chalk.gray(' ' + '─'.repeat(60)));
27
+ lines.push('');
28
+ if (result.status === 'FAILED') {
29
+ lines.push(chalk.red(` Replay failed: ${result.error ?? 'Unknown error'}`));
30
+ lines.push('');
31
+ return lines.join('\n');
32
+ }
33
+ lines.push(` ${chalk.gray('Period:')} ${formatShortDate(result.from)} → ${formatShortDate(result.to)}`);
34
+ if (result.completedAt) {
35
+ lines.push(` ${chalk.gray('Completed:')} ${chalk.dim(new Date(result.completedAt).toISOString().slice(0, 19).replace('T', ' '))}`);
36
+ }
37
+ if (result.summary) {
38
+ const s = result.summary;
39
+ lines.push('');
40
+ lines.push(chalk.bold(' Summary'));
41
+ lines.push(` ${chalk.gray('Total Trades:')} ${chalk.white(String(s.totalTrades))}`);
42
+ lines.push(` ${chalk.gray('Win Rate:')} ${formatWinRate(s.winRate)}`);
43
+ lines.push(` ${chalk.gray('Avg PnL %:')} ${formatPercent(s.avgPnlPercent)}`);
44
+ lines.push(` ${chalk.gray('Total PnL %:')} ${formatPercent(s.totalPnlPercent)}`);
45
+ lines.push(` ${chalk.gray('Max Drawdown:')} ${chalk.red('-' + Math.abs(s.maxDrawdownPercent).toFixed(2) + '%')}`);
46
+ if (s.sharpeRatio !== undefined) {
47
+ const sharpeColor = s.sharpeRatio >= 1.0 ? chalk.green : s.sharpeRatio >= 0.5 ? chalk.yellow : chalk.red;
48
+ lines.push(` ${chalk.gray('Sharpe Ratio:')} ${sharpeColor(s.sharpeRatio.toFixed(2))}`);
49
+ }
50
+ if (s.profitFactor !== undefined) {
51
+ const pfColor = s.profitFactor >= 1.5 ? chalk.green : s.profitFactor >= 1.0 ? chalk.yellow : chalk.red;
52
+ lines.push(` ${chalk.gray('Profit Factor:')} ${pfColor(s.profitFactor.toFixed(2))}`);
53
+ }
54
+ }
55
+ if (result.regimeBreakdown && result.regimeBreakdown.length > 0) {
56
+ lines.push('');
57
+ lines.push(chalk.bold(' Regime Breakdown'));
58
+ lines.push(' ' +
59
+ padRight('Regime', 20) +
60
+ padRight('Trades', 8) +
61
+ padRight('Win Rate', 10) +
62
+ 'Avg PnL %');
63
+ lines.push(chalk.gray(' ' + '─'.repeat(48)));
64
+ for (const r of result.regimeBreakdown) {
65
+ lines.push(' ' +
66
+ padRight(r.regime, 20) +
67
+ padRight(String(r.trades), 8) +
68
+ padRight(formatWinRate(r.winRate), 10) +
69
+ formatPercent(r.avgPnlPercent));
70
+ }
71
+ }
72
+ lines.push('');
73
+ return lines.join('\n');
74
+ }
75
+ function formatWinRate(rate) {
76
+ const str = `${(rate * 100).toFixed(1)}%`;
77
+ if (rate >= 0.6)
78
+ return chalk.green(str);
79
+ if (rate >= 0.4)
80
+ return chalk.yellow(str);
81
+ return chalk.red(str);
82
+ }
83
+ function padRight(str, len) {
84
+ return str.length >= len ? str.slice(0, len) : str + ' '.repeat(len - str.length);
85
+ }
86
+ // ─── Polling ───
87
+ const POLL_INTERVAL_MS = 2000;
88
+ const POLL_TIMEOUT_MS = 60000;
89
+ async function pollReplayJob(jobId) {
90
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
91
+ while (Date.now() < deadline) {
92
+ const status = await apiRequest(`/api/v1/replay/${encodeURIComponent(jobId)}`);
93
+ if (status.status === 'COMPLETE' || status.status === 'FAILED') {
94
+ return status;
95
+ }
96
+ // Wait before next poll
97
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
98
+ }
99
+ throw new Error(`Replay job ${jobId} did not complete within ${POLL_TIMEOUT_MS / 1000}s timeout.`);
100
+ }
101
+ // ─── Error Handler ───
102
+ function handleApiError(error, context) {
103
+ if (error instanceof ApiError) {
104
+ switch (error.status) {
105
+ case 401:
106
+ console.error(chalk.red('Error: API key invalid or expired. Run `trading-boy login` to re-authenticate.'));
107
+ break;
108
+ case 403:
109
+ console.error(chalk.red('Error: Subscription inactive. Run `trading-boy billing manage` to update your billing.'));
110
+ break;
111
+ case 404:
112
+ console.error(chalk.red(`Error: Not found — ${error.message}`));
113
+ break;
114
+ case 422:
115
+ console.error(chalk.red(`Error: Validation failed — ${error.message}`));
116
+ break;
117
+ default:
118
+ console.error(chalk.red(`Error: ${error.message}`));
119
+ }
120
+ }
121
+ else {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ logger.error({ error: message }, context);
124
+ console.error(chalk.red(`Error: ${message}`));
125
+ }
126
+ process.exitCode = 1;
127
+ }
128
+ // ─── Command Registration ───
129
+ export function registerReplayCommand(program) {
130
+ program
131
+ .command('replay')
132
+ .description('Run strategy replay (backtest) over a historical period')
133
+ .requiredOption('--strategy <id>', 'Strategy ID')
134
+ .requiredOption('--token <symbol>', 'Token symbol')
135
+ .requiredOption('--from <date>', 'Start date (ISO-8601, e.g. 2025-01-01)')
136
+ .requiredOption('--to <date>', 'End date (ISO-8601, e.g. 2025-03-01)')
137
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
138
+ .action(async (options) => {
139
+ if (!(await isRemoteMode())) {
140
+ console.error(chalk.yellow('Replay requires a remote API connection.'));
141
+ console.error(chalk.dim(' Run: trading-boy login'));
142
+ process.exitCode = 1;
143
+ return;
144
+ }
145
+ // Validate dates
146
+ const fromDate = new Date(options.from);
147
+ const toDate = new Date(options.to);
148
+ if (isNaN(fromDate.getTime())) {
149
+ console.error(chalk.red(`Error: Invalid --from date: ${options.from}`));
150
+ process.exitCode = 1;
151
+ return;
152
+ }
153
+ if (isNaN(toDate.getTime())) {
154
+ console.error(chalk.red(`Error: Invalid --to date: ${options.to}`));
155
+ process.exitCode = 1;
156
+ return;
157
+ }
158
+ if (fromDate >= toDate) {
159
+ console.error(chalk.red('Error: --from must be before --to'));
160
+ process.exitCode = 1;
161
+ return;
162
+ }
163
+ // Use dynamic import for ora (ESM spinner)
164
+ const { default: ora } = await import('ora');
165
+ // ─── Submit replay job ───
166
+ let jobId;
167
+ const submitSpinner = ora('Submitting replay job…').start();
168
+ try {
169
+ const submitted = await apiRequest('/api/v1/replay', {
170
+ method: 'POST',
171
+ body: {
172
+ strategyId: options.strategy,
173
+ tokenSymbol: options.token.toUpperCase(),
174
+ from: fromDate.toISOString(),
175
+ to: toDate.toISOString(),
176
+ },
177
+ });
178
+ jobId = submitted.jobId;
179
+ submitSpinner.succeed(chalk.dim(`Job submitted: ${jobId}`));
180
+ }
181
+ catch (error) {
182
+ submitSpinner.fail('Failed to submit replay job');
183
+ handleApiError(error, 'Replay submit failed');
184
+ return;
185
+ }
186
+ // ─── Poll until complete ───
187
+ const pollSpinner = ora('Running replay…').start();
188
+ let finalStatus;
189
+ try {
190
+ finalStatus = await pollReplayJob(jobId);
191
+ pollSpinner.stop();
192
+ }
193
+ catch (error) {
194
+ pollSpinner.fail('Replay timed out or encountered an error');
195
+ handleApiError(error, 'Replay polling failed');
196
+ return;
197
+ }
198
+ // ─── Display results ───
199
+ if (!finalStatus.result) {
200
+ if (finalStatus.status === 'FAILED') {
201
+ console.error(chalk.red(`Error: Replay job failed — ${finalStatus.error ?? 'unknown error'}`));
202
+ }
203
+ else {
204
+ console.error(chalk.red('Error: Replay completed but returned no result data.'));
205
+ }
206
+ process.exitCode = 1;
207
+ return;
208
+ }
209
+ if (options.format === 'json') {
210
+ console.log(JSON.stringify(finalStatus.result, null, 2));
211
+ }
212
+ else {
213
+ console.log(formatReplayResult(finalStatus.result));
214
+ }
215
+ });
216
+ }
217
+ //# sourceMappingURL=replay-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-cmd.js","sourceRoot":"","sources":["../../src/commands/replay-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,MAAM,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEtE,iBAAiB;AAEjB,MAAM,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;AAyE1C,kBAAkB;AAElB,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACxC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,eAAe,CAAC,SAAiB;IACxC,IAAI,CAAC;QACH,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,qBAAqB;AAErB,MAAM,UAAU,kBAAkB,CAAC,MAAoB;IACrD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,WAAW,eAAe,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IACzG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,oBAAoB,MAAM,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC,CAAC;QAC7E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACnH,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7I,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC;QAEzB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;QACpC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;QACzF,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC/E,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QACrF,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QACvF,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;QACvH,IAAI,CAAC,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,WAAW,GAAG,CAAC,CAAC,WAAW,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;YACzG,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9F,CAAC;QACD,IAAI,CAAC,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;YACvG,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3F,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,eAAe,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;QAC7C,KAAK,CAAC,IAAI,CACR,IAAI;YACJ,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC;YACtB,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;YACrB,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC;YACxB,WAAW,CACZ,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAE9C,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;YACvC,KAAK,CAAC,IAAI,CACR,IAAI;gBACJ,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;gBACtB,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC7B,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;gBACtC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAC/B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1C,IAAI,IAAI,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,IAAI,IAAI,GAAG;QAAE,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,GAAW;IACxC,OAAO,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;AACpF,CAAC;AAED,kBAAkB;AAElB,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,eAAe,GAAG,KAAK,CAAC;AAE9B,KAAK,UAAU,aAAa,CAAC,KAAa;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,CAAC;IAE9C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,UAAU,CAC7B,kBAAkB,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAC9C,CAAC;QAEF,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/D,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,wBAAwB;QACxB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,cAAc,KAAK,4BAA4B,eAAe,GAAG,IAAI,YAAY,CAAC,CAAC;AACrG,CAAC;AAED,wBAAwB;AAExB,SAAS,cAAc,CAAC,KAAc,EAAE,OAAe;IACrD,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;YACrB,KAAK,GAAG;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,gFAAgF,CAAC,CAAC,CAAC;gBAC3G,MAAM;YACR,KAAK,GAAG;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,wFAAwF,CAAC,CAAC,CAAC;gBACnH,MAAM;YACR,KAAK,GAAG;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,sBAAsB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBAChE,MAAM;YACR,KAAK,GAAG;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,8BAA8B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACxE,MAAM;YACR;gBACE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,+BAA+B;AAE/B,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,yDAAyD,CAAC;SACtE,cAAc,CAAC,iBAAiB,EAAE,aAAa,CAAC;SAChD,cAAc,CAAC,kBAAkB,EAAE,cAAc,CAAC;SAClD,cAAc,CAAC,eAAe,EAAE,wCAAwC,CAAC;SACzE,cAAc,CAAC,aAAa,EAAE,sCAAsC,CAAC;SACrE,SAAS,CAAC,IAAI,MAAM,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;SACrG,MAAM,CAAC,KAAK,EAAE,OAAsB,EAAE,EAAE;QACvC,IAAI,CAAC,CAAC,MAAM,YAAY,EAAE,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,0CAA0C,CAAC,CAAC,CAAC;YACxE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC;YACrD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,iBAAiB;QACjB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAEpC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,+BAA+B,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YACxE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,6BAA6B,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;YACpE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,QAAQ,IAAI,MAAM,EAAE,CAAC;YACvB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC,CAAC;YAC9D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,2CAA2C;QAC3C,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAE7C,4BAA4B;QAE5B,IAAI,KAAa,CAAC;QAClB,MAAM,aAAa,GAAG,GAAG,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC;QAE5D,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,UAAU,CAA0B,gBAAgB,EAAE;gBAC5E,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE;oBACJ,UAAU,EAAE,OAAO,CAAC,QAAQ;oBAC5B,WAAW,EAAE,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE;oBACxC,IAAI,EAAE,QAAQ,CAAC,WAAW,EAAE;oBAC5B,EAAE,EAAE,MAAM,CAAC,WAAW,EAAE;iBACzB;aACF,CAAC,CAAC;YAEH,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;YACxB,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,KAAK,EAAE,CAAC,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAClD,cAAc,CAAC,KAAK,EAAE,sBAAsB,CAAC,CAAC;YAC9C,OAAO;QACT,CAAC;QAED,8BAA8B;QAE9B,MAAM,WAAW,GAAG,GAAG,CAAC,iBAAiB,CAAC,CAAC,KAAK,EAAE,CAAC;QAEnD,IAAI,WAA4B,CAAC;QACjC,IAAI,CAAC;YACH,WAAW,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;YACzC,WAAW,CAAC,IAAI,EAAE,CAAC;QACrB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,WAAW,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;YAC7D,cAAc,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC;YAC/C,OAAO;QACT,CAAC;QAED,0BAA0B;QAE1B,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;YACxB,IAAI,WAAW,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACpC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,8BAA8B,WAAW,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC,CAAC;YACjG,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC,CAAC;YACnF,CAAC;YACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QACtD,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { Command } from 'commander';
2
+ interface AgentStrategy {
3
+ id: string;
4
+ agentId: string;
5
+ traderId: string;
6
+ tenantId: string;
7
+ version: number;
8
+ name: string;
9
+ tokens: string[];
10
+ setupTypes: string[];
11
+ regimeBehavior: Record<string, unknown>;
12
+ riskLimits: Record<string, unknown>;
13
+ signalWeights: Record<string, number>;
14
+ narrativePreferences: string[];
15
+ active: boolean;
16
+ createdAt: string;
17
+ updatedAt: string;
18
+ }
19
+ interface StrategyListResponse {
20
+ strategies: AgentStrategy[];
21
+ total: number;
22
+ limit: number;
23
+ offset: number;
24
+ }
25
+ interface StrategyHistoryEntry {
26
+ id: string;
27
+ strategyId: string;
28
+ version: number;
29
+ name: string;
30
+ tokens: string[];
31
+ setupTypes: string[];
32
+ changedAt: string;
33
+ changedBy?: string;
34
+ }
35
+ interface StrategyHistoryResponse {
36
+ strategyId: string;
37
+ history: StrategyHistoryEntry[];
38
+ }
39
+ export declare function formatStrategyDetail(s: AgentStrategy): string;
40
+ export declare function formatStrategyList(response: StrategyListResponse): string;
41
+ export declare function formatHistoryList(response: StrategyHistoryResponse): string;
42
+ export declare function registerStrategyCommand(program: Command): void;
43
+ export {};
44
+ //# sourceMappingURL=strategy-cmd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy-cmd.d.ts","sourceRoot":"","sources":["../../src/commands/strategy-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAW5C,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,oBAAoB;IAC5B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,uBAAuB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,oBAAoB,EAAE,CAAC;CACjC;AA0DD,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAmD7D;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,CA+CzE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,MAAM,CAwC3E;AA4CD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8M9D"}
@@ -0,0 +1,353 @@
1
+ import { Option } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { createLogger } from '@trading-boy/core';
4
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
5
+ // ─── Logger ───
6
+ const logger = createLogger('cli-strategy');
7
+ // ─── Helpers ───
8
+ function padRight(str, len) {
9
+ return str.length >= len ? str.slice(0, len) : str + ' '.repeat(len - str.length);
10
+ }
11
+ function formatShortDate(isoString) {
12
+ try {
13
+ return new Date(isoString).toISOString().slice(0, 16).replace('T', ' ');
14
+ }
15
+ catch {
16
+ return isoString;
17
+ }
18
+ }
19
+ // ─── Formatters ───
20
+ export function formatStrategyDetail(s) {
21
+ const lines = [];
22
+ lines.push('');
23
+ lines.push(chalk.bold.cyan(` Strategy — ${s.name}`));
24
+ lines.push(chalk.gray(' ' + '─'.repeat(56)));
25
+ lines.push('');
26
+ lines.push(` ${chalk.gray('ID:')} ${chalk.white(s.id)}`);
27
+ lines.push(` ${chalk.gray('Version:')} ${chalk.white(String(s.version))}`);
28
+ lines.push(` ${chalk.gray('Agent ID:')} ${chalk.white(s.agentId)}`);
29
+ lines.push(` ${chalk.gray('Trader ID:')} ${chalk.white(s.traderId)}`);
30
+ lines.push(` ${chalk.gray('Tenant ID:')} ${chalk.dim(s.tenantId)}`);
31
+ lines.push(` ${chalk.gray('Active:')} ${s.active ? chalk.green('YES') : chalk.red('NO')}`);
32
+ lines.push('');
33
+ lines.push(` ${chalk.gray('Tokens:')} ${chalk.white(s.tokens.join(', ') || chalk.dim('none'))}`);
34
+ lines.push(` ${chalk.gray('Setup Types:')} ${chalk.white(s.setupTypes.join(', ') || chalk.dim('none'))}`);
35
+ if (s.narrativePreferences.length > 0) {
36
+ lines.push(` ${chalk.gray('Narratives:')} ${chalk.white(s.narrativePreferences.join(', '))}`);
37
+ }
38
+ if (Object.keys(s.signalWeights).length > 0) {
39
+ lines.push('');
40
+ lines.push(chalk.bold(' Signal Weights'));
41
+ for (const [signal, weight] of Object.entries(s.signalWeights)) {
42
+ lines.push(` ${chalk.gray(signal + ':')} ${chalk.white(weight.toFixed(3))}`);
43
+ }
44
+ }
45
+ if (Object.keys(s.riskLimits).length > 0) {
46
+ lines.push('');
47
+ lines.push(chalk.bold(' Risk Limits'));
48
+ for (const [key, val] of Object.entries(s.riskLimits)) {
49
+ lines.push(` ${chalk.gray(key + ':')} ${chalk.white(JSON.stringify(val))}`);
50
+ }
51
+ }
52
+ if (Object.keys(s.regimeBehavior).length > 0) {
53
+ lines.push('');
54
+ lines.push(chalk.bold(' Regime Behavior'));
55
+ for (const [regime, behavior] of Object.entries(s.regimeBehavior)) {
56
+ lines.push(` ${chalk.gray(regime + ':')} ${chalk.white(JSON.stringify(behavior))}`);
57
+ }
58
+ }
59
+ lines.push('');
60
+ lines.push(` ${chalk.gray('Created:')} ${chalk.dim(formatShortDate(s.createdAt))}`);
61
+ lines.push(` ${chalk.gray('Updated:')} ${chalk.dim(formatShortDate(s.updatedAt))}`);
62
+ lines.push('');
63
+ return lines.join('\n');
64
+ }
65
+ export function formatStrategyList(response) {
66
+ const lines = [];
67
+ lines.push('');
68
+ lines.push(chalk.bold.cyan(' Agent Strategies'));
69
+ lines.push(chalk.gray(' ' + '─'.repeat(96)));
70
+ lines.push('');
71
+ if (response.strategies.length === 0) {
72
+ lines.push(` ${chalk.dim('No strategies found.')}`);
73
+ lines.push('');
74
+ return lines.join('\n');
75
+ }
76
+ // Header
77
+ lines.push(' ' +
78
+ padRight('Name', 24) +
79
+ padRight('ID', 30) +
80
+ padRight('Tokens', 20) +
81
+ padRight('v', 4) +
82
+ padRight('Active', 8) +
83
+ 'Updated');
84
+ lines.push(' ' + '─'.repeat(96));
85
+ for (const s of response.strategies) {
86
+ const activeStr = s.active ? chalk.green('YES') : chalk.red('NO');
87
+ const tokensStr = s.tokens.slice(0, 3).join(',') + (s.tokens.length > 3 ? '…' : '');
88
+ lines.push(' ' +
89
+ padRight(s.name, 24) +
90
+ padRight(chalk.dim(s.id), 30) +
91
+ padRight(tokensStr || chalk.dim('-'), 20) +
92
+ padRight(String(s.version), 4) +
93
+ padRight(activeStr, 8) +
94
+ chalk.dim(formatShortDate(s.updatedAt)));
95
+ }
96
+ lines.push('');
97
+ lines.push(` ${chalk.gray(`Showing ${response.strategies.length} of ${response.total} (offset: ${response.offset})`)}`);
98
+ lines.push('');
99
+ return lines.join('\n');
100
+ }
101
+ export function formatHistoryList(response) {
102
+ const lines = [];
103
+ lines.push('');
104
+ lines.push(chalk.bold.cyan(` Strategy History — ${response.strategyId}`));
105
+ lines.push(chalk.gray(' ' + '─'.repeat(80)));
106
+ lines.push('');
107
+ if (response.history.length === 0) {
108
+ lines.push(` ${chalk.dim('No history entries found.')}`);
109
+ lines.push('');
110
+ return lines.join('\n');
111
+ }
112
+ lines.push(' ' +
113
+ padRight('v', 4) +
114
+ padRight('Name', 28) +
115
+ padRight('Tokens', 24) +
116
+ padRight('Setups', 20) +
117
+ 'Changed At');
118
+ lines.push(' ' + '─'.repeat(80));
119
+ for (const entry of response.history) {
120
+ const tokensStr = entry.tokens.slice(0, 3).join(',') + (entry.tokens.length > 3 ? '…' : '');
121
+ const setupsStr = entry.setupTypes.slice(0, 2).join(',') + (entry.setupTypes.length > 2 ? '…' : '');
122
+ lines.push(' ' +
123
+ padRight(String(entry.version), 4) +
124
+ padRight(entry.name, 28) +
125
+ padRight(tokensStr || chalk.dim('-'), 24) +
126
+ padRight(setupsStr || chalk.dim('-'), 20) +
127
+ chalk.dim(formatShortDate(entry.changedAt)));
128
+ }
129
+ lines.push('');
130
+ return lines.join('\n');
131
+ }
132
+ // ─── Remote Mode Guard ───
133
+ async function requireRemote() {
134
+ if (!(await isRemoteMode())) {
135
+ console.error(chalk.yellow('Strategy commands require a remote API connection.'));
136
+ console.error(chalk.dim(' Run: trading-boy login'));
137
+ process.exitCode = 1;
138
+ return false;
139
+ }
140
+ return true;
141
+ }
142
+ // ─── Error Handler ───
143
+ function handleApiError(error, context) {
144
+ if (error instanceof ApiError) {
145
+ switch (error.status) {
146
+ case 401:
147
+ console.error(chalk.red('Error: API key invalid or expired. Run `trading-boy login` to re-authenticate.'));
148
+ break;
149
+ case 403:
150
+ console.error(chalk.red('Error: Subscription inactive. Run `trading-boy billing manage` to update your billing.'));
151
+ break;
152
+ case 404:
153
+ console.error(chalk.red(`Error: Not found — ${error.message}`));
154
+ break;
155
+ case 422:
156
+ console.error(chalk.red(`Error: Validation failed — ${error.message}`));
157
+ break;
158
+ default:
159
+ console.error(chalk.red(`Error: ${error.message}`));
160
+ }
161
+ }
162
+ else {
163
+ const message = error instanceof Error ? error.message : String(error);
164
+ logger.error({ error: message }, context);
165
+ console.error(chalk.red(`Error: ${message}`));
166
+ }
167
+ process.exitCode = 1;
168
+ }
169
+ // ─── Command Registration ───
170
+ export function registerStrategyCommand(program) {
171
+ const strategy = program
172
+ .command('strategy')
173
+ .description('Manage agent strategies (token watch-lists, setup types, signal weights)');
174
+ // ─── strategy create ───
175
+ strategy
176
+ .command('create')
177
+ .description('Create a new agent strategy')
178
+ .requiredOption('--name <name>', 'Strategy name')
179
+ .requiredOption('--trader-id <id>', 'Trader ID')
180
+ .requiredOption('--agent-id <id>', 'Agent ID')
181
+ .requiredOption('--tokens <tokens>', 'Comma-separated token symbols')
182
+ .option('--setups <types>', 'Comma-separated setup types')
183
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
184
+ .action(async (options) => {
185
+ if (!(await requireRemote()))
186
+ return;
187
+ const body = {
188
+ name: options.name,
189
+ traderId: options.traderId,
190
+ agentId: options.agentId,
191
+ tokens: options.tokens.split(',').map((t) => t.trim().toUpperCase()).filter(Boolean),
192
+ };
193
+ if (options.setups) {
194
+ body.setupTypes = options.setups.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean);
195
+ }
196
+ try {
197
+ const result = await apiRequest('/api/v1/strategies', {
198
+ method: 'POST',
199
+ body,
200
+ });
201
+ if (options.format === 'json') {
202
+ console.log(JSON.stringify(result, null, 2));
203
+ }
204
+ else {
205
+ console.log(formatStrategyDetail(result));
206
+ }
207
+ }
208
+ catch (error) {
209
+ handleApiError(error, 'Strategy create failed');
210
+ }
211
+ });
212
+ // ─── strategy list ───
213
+ strategy
214
+ .command('list')
215
+ .description('List strategies for a trader')
216
+ .requiredOption('--trader-id <id>', 'Trader ID')
217
+ .option('--agent-id <id>', 'Filter by agent ID')
218
+ .option('--limit <n>', 'Maximum results', '20')
219
+ .option('--offset <n>', 'Pagination offset', '0')
220
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
221
+ .action(async (options) => {
222
+ if (!(await requireRemote()))
223
+ return;
224
+ const params = new URLSearchParams();
225
+ params.set('traderId', options.traderId);
226
+ if (options.agentId)
227
+ params.set('agentId', options.agentId);
228
+ params.set('limit', options.limit);
229
+ params.set('offset', options.offset);
230
+ try {
231
+ const result = await apiRequest(`/api/v1/strategies?${params.toString()}`);
232
+ if (options.format === 'json') {
233
+ console.log(JSON.stringify(result, null, 2));
234
+ }
235
+ else {
236
+ console.log(formatStrategyList(result));
237
+ }
238
+ }
239
+ catch (error) {
240
+ handleApiError(error, 'Strategy list failed');
241
+ }
242
+ });
243
+ // ─── strategy show <id> ───
244
+ strategy
245
+ .command('show <id>')
246
+ .description('Show full details for a strategy')
247
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
248
+ .action(async (id, options) => {
249
+ if (!(await requireRemote()))
250
+ return;
251
+ try {
252
+ const result = await apiRequest(`/api/v1/strategies/${encodeURIComponent(id)}`);
253
+ if (options.format === 'json') {
254
+ console.log(JSON.stringify(result, null, 2));
255
+ }
256
+ else {
257
+ console.log(formatStrategyDetail(result));
258
+ }
259
+ }
260
+ catch (error) {
261
+ handleApiError(error, 'Strategy show failed');
262
+ }
263
+ });
264
+ // ─── strategy update <id> ───
265
+ strategy
266
+ .command('update <id>')
267
+ .description('Update a strategy')
268
+ .option('--name <name>', 'New strategy name')
269
+ .option('--tokens <tokens>', 'New comma-separated token symbols (replaces existing)')
270
+ .option('--setups <types>', 'New comma-separated setup types (replaces existing)')
271
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
272
+ .action(async (id, options) => {
273
+ if (!(await requireRemote()))
274
+ return;
275
+ const body = {};
276
+ if (options.name) {
277
+ body.name = options.name;
278
+ }
279
+ if (options.tokens) {
280
+ body.tokens = options.tokens.split(',').map((t) => t.trim().toUpperCase()).filter(Boolean);
281
+ }
282
+ if (options.setups) {
283
+ body.setupTypes = options.setups.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean);
284
+ }
285
+ if (Object.keys(body).length === 0) {
286
+ console.error(chalk.red('Error: Provide at least one of --name, --tokens, or --setups to update.'));
287
+ process.exitCode = 1;
288
+ return;
289
+ }
290
+ try {
291
+ const result = await apiRequest(`/api/v1/strategies/${encodeURIComponent(id)}`, { method: 'PUT', body });
292
+ if (options.format === 'json') {
293
+ console.log(JSON.stringify(result, null, 2));
294
+ }
295
+ else {
296
+ console.log(formatStrategyDetail(result));
297
+ }
298
+ }
299
+ catch (error) {
300
+ handleApiError(error, 'Strategy update failed');
301
+ }
302
+ });
303
+ // ─── strategy history <id> ───
304
+ strategy
305
+ .command('history <id>')
306
+ .description('Show version history for a strategy')
307
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
308
+ .action(async (id, options) => {
309
+ if (!(await requireRemote()))
310
+ return;
311
+ try {
312
+ const result = await apiRequest(`/api/v1/strategies/${encodeURIComponent(id)}/history`);
313
+ if (options.format === 'json') {
314
+ console.log(JSON.stringify(result, null, 2));
315
+ }
316
+ else {
317
+ console.log(formatHistoryList(result));
318
+ }
319
+ }
320
+ catch (error) {
321
+ handleApiError(error, 'Strategy history failed');
322
+ }
323
+ });
324
+ // ─── strategy export ───
325
+ strategy
326
+ .command('export')
327
+ .description('Export a strategy in json, elizaos, or freqtrade format')
328
+ .requiredOption('--id <id>', 'Strategy ID to export')
329
+ .addOption(new Option('--format <format>', 'Export format')
330
+ .choices(['json', 'elizaos', 'freqtrade'])
331
+ .default('json'))
332
+ .option('--output <file>', 'Write output to a file instead of stdout')
333
+ .action(async (options) => {
334
+ if (!(await requireRemote()))
335
+ return;
336
+ try {
337
+ const result = await apiRequest(`/api/v1/strategies/${encodeURIComponent(options.id)}/export?format=${encodeURIComponent(options.format)}`);
338
+ const output = JSON.stringify(result, null, 2);
339
+ if (options.output) {
340
+ const { writeFile } = await import('node:fs/promises');
341
+ await writeFile(options.output, output + '\n', 'utf-8');
342
+ console.log(chalk.green(`Strategy exported to ${options.output}`));
343
+ }
344
+ else {
345
+ console.log(output);
346
+ }
347
+ }
348
+ catch (error) {
349
+ handleApiError(error, 'Strategy export failed');
350
+ }
351
+ });
352
+ }
353
+ //# sourceMappingURL=strategy-cmd.js.map