@trading-boy/cli 1.11.0 → 2.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 +50 -22
- package/dist/api-client.d.ts +4 -7
- package/dist/api-client.js +8 -13
- package/dist/cli.bundle.js +1977 -33976
- package/dist/credentials.js +1 -1
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -24
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +12 -0
- package/dist/utils.js +3 -3
- package/package.json +20 -5
- package/dist/cli.d.ts +0 -5
- package/dist/cli.js +0 -157
- package/dist/commands/agent-cmd.d.ts +0 -9
- package/dist/commands/agent-cmd.js +0 -572
- package/dist/commands/audit.d.ts +0 -18
- package/dist/commands/audit.js +0 -73
- package/dist/commands/behavioral.d.ts +0 -73
- package/dist/commands/behavioral.js +0 -349
- package/dist/commands/benchmark-cmd.d.ts +0 -3
- package/dist/commands/benchmark-cmd.js +0 -191
- package/dist/commands/billing.d.ts +0 -12
- package/dist/commands/billing.js +0 -142
- package/dist/commands/catalysts.d.ts +0 -17
- package/dist/commands/catalysts.js +0 -151
- package/dist/commands/coaching-cmd.d.ts +0 -16
- package/dist/commands/coaching-cmd.js +0 -222
- package/dist/commands/config-cmd.d.ts +0 -30
- package/dist/commands/config-cmd.js +0 -515
- package/dist/commands/connect-chatgpt.d.ts +0 -5
- package/dist/commands/connect-chatgpt.js +0 -293
- package/dist/commands/connect-claude.d.ts +0 -5
- package/dist/commands/connect-claude.js +0 -280
- package/dist/commands/context.d.ts +0 -41
- package/dist/commands/context.js +0 -405
- package/dist/commands/cron-cmd.d.ts +0 -3
- package/dist/commands/cron-cmd.js +0 -305
- package/dist/commands/decisions.d.ts +0 -57
- package/dist/commands/decisions.js +0 -364
- package/dist/commands/edge-cmd.d.ts +0 -78
- package/dist/commands/edge-cmd.js +0 -183
- package/dist/commands/edge-guard-cmd.d.ts +0 -36
- package/dist/commands/edge-guard-cmd.js +0 -169
- package/dist/commands/events.d.ts +0 -3
- package/dist/commands/events.js +0 -117
- package/dist/commands/infra.d.ts +0 -24
- package/dist/commands/infra.js +0 -137
- package/dist/commands/journal.d.ts +0 -3
- package/dist/commands/journal.js +0 -302
- package/dist/commands/login.d.ts +0 -18
- package/dist/commands/login.js +0 -127
- package/dist/commands/logout.d.ts +0 -8
- package/dist/commands/logout.js +0 -108
- package/dist/commands/narratives.d.ts +0 -3
- package/dist/commands/narratives.js +0 -259
- package/dist/commands/onboarding.d.ts +0 -7
- package/dist/commands/onboarding.js +0 -298
- package/dist/commands/query.d.ts +0 -32
- package/dist/commands/query.js +0 -135
- package/dist/commands/replay-cmd.d.ts +0 -43
- package/dist/commands/replay-cmd.js +0 -184
- package/dist/commands/review.d.ts +0 -3
- package/dist/commands/review.js +0 -443
- package/dist/commands/risk.d.ts +0 -47
- package/dist/commands/risk.js +0 -158
- package/dist/commands/social.d.ts +0 -43
- package/dist/commands/social.js +0 -318
- package/dist/commands/soul-wizard.d.ts +0 -29
- package/dist/commands/soul-wizard.js +0 -155
- package/dist/commands/strategy-cmd.d.ts +0 -44
- package/dist/commands/strategy-cmd.js +0 -340
- package/dist/commands/subscribe.d.ts +0 -78
- package/dist/commands/subscribe.js +0 -552
- package/dist/commands/suggestions-cmd.d.ts +0 -24
- package/dist/commands/suggestions-cmd.js +0 -148
- package/dist/commands/thesis-cmd.d.ts +0 -3
- package/dist/commands/thesis-cmd.js +0 -129
- package/dist/commands/trader.d.ts +0 -30
- package/dist/commands/trader.js +0 -971
- package/dist/commands/watch.d.ts +0 -16
- package/dist/commands/watch.js +0 -104
- package/dist/commands/whoami.d.ts +0 -14
- package/dist/commands/whoami.js +0 -105
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
import { Option } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { createLogger } from '@trading-boy/core';
|
|
4
|
-
import { padRight, parseIntOption, formatDate, formatConnectionError } from '../utils.js';
|
|
5
|
-
import { apiRequest, ApiError } from '../api-client.js';
|
|
6
|
-
// ─── Logger ───
|
|
7
|
-
const logger = createLogger('cli-decisions');
|
|
8
|
-
// ─── Default Trader ───
|
|
9
|
-
const DEFAULT_TRADER_ID = 'default';
|
|
10
|
-
// ─── Formatters ───
|
|
11
|
-
/**
|
|
12
|
-
* Format a PnL value with color coding.
|
|
13
|
-
*/
|
|
14
|
-
function formatPnl(pnl) {
|
|
15
|
-
if (pnl === null) {
|
|
16
|
-
return chalk.dim('-');
|
|
17
|
-
}
|
|
18
|
-
const sign = pnl >= 0 ? '+' : '';
|
|
19
|
-
const str = `${sign}$${pnl.toFixed(2)}`;
|
|
20
|
-
return pnl > 0 ? chalk.green(str) : pnl < 0 ? chalk.red(str) : chalk.dim(str);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Format a percentage with color coding.
|
|
24
|
-
*/
|
|
25
|
-
function formatPercent(pct) {
|
|
26
|
-
if (pct === null) {
|
|
27
|
-
return chalk.dim('-');
|
|
28
|
-
}
|
|
29
|
-
const sign = pct >= 0 ? '+' : '';
|
|
30
|
-
const str = `${sign}${pct.toFixed(2)}%`;
|
|
31
|
-
return pct > 0 ? chalk.green(str) : pct < 0 ? chalk.red(str) : chalk.dim(str);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Format a win rate as a colored percentage.
|
|
35
|
-
*/
|
|
36
|
-
function formatWinRate(rate) {
|
|
37
|
-
const str = `${(rate * 100).toFixed(1)}%`;
|
|
38
|
-
if (rate >= 0.6) {
|
|
39
|
-
return chalk.green(str);
|
|
40
|
-
}
|
|
41
|
-
if (rate >= 0.4) {
|
|
42
|
-
return chalk.yellow(str);
|
|
43
|
-
}
|
|
44
|
-
return chalk.red(str);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Format milliseconds into a human-readable duration.
|
|
48
|
-
*/
|
|
49
|
-
function formatDuration(ms) {
|
|
50
|
-
if (ms === 0) {
|
|
51
|
-
return chalk.dim('-');
|
|
52
|
-
}
|
|
53
|
-
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
54
|
-
const days = Math.floor(hours / 24);
|
|
55
|
-
const remainingHours = hours % 24;
|
|
56
|
-
if (days > 0) {
|
|
57
|
-
return `${days}d ${remainingHours}h`;
|
|
58
|
-
}
|
|
59
|
-
if (hours > 0) {
|
|
60
|
-
return `${hours}h`;
|
|
61
|
-
}
|
|
62
|
-
const minutes = Math.floor(ms / (1000 * 60));
|
|
63
|
-
return `${minutes}m`;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Format a decision record row for the decisions table.
|
|
67
|
-
*/
|
|
68
|
-
function formatDecisionRow(d) {
|
|
69
|
-
const date = d.eventTime ? formatDate(d.eventTime) : '-';
|
|
70
|
-
const token = d.tokenSymbol ?? '-';
|
|
71
|
-
const setup = d.setupType ?? '-';
|
|
72
|
-
const dir = d.direction ?? '-';
|
|
73
|
-
const formatPrice = (p) => p < 0.01 ? p.toPrecision(3) : p.toFixed(2);
|
|
74
|
-
const price = d.entryPrice !== null
|
|
75
|
-
? `$${formatPrice(d.entryPrice)}`
|
|
76
|
-
: d.exitPrice !== null
|
|
77
|
-
? `$${formatPrice(d.exitPrice)}`
|
|
78
|
-
: '-';
|
|
79
|
-
const pnl = formatPnl(d.pnlAbsolute);
|
|
80
|
-
const pnlPct = formatPercent(d.pnlPercent);
|
|
81
|
-
return (' ' +
|
|
82
|
-
padRight(date, 14) +
|
|
83
|
-
padRight(token, 8) +
|
|
84
|
-
padRight(d.decisionType, 14) +
|
|
85
|
-
padRight(dir, 8) +
|
|
86
|
-
padRight(setup, 18) +
|
|
87
|
-
padRight(price, 12) +
|
|
88
|
-
padRight(pnl, 14) +
|
|
89
|
-
pnlPct);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Format the decisions list output.
|
|
93
|
-
*/
|
|
94
|
-
export function formatDecisionsOutput(decisions) {
|
|
95
|
-
const lines = [];
|
|
96
|
-
lines.push('');
|
|
97
|
-
lines.push(chalk.bold.cyan(' Trade Decisions'));
|
|
98
|
-
lines.push(chalk.gray(' ' + '\u2500'.repeat(100)));
|
|
99
|
-
lines.push('');
|
|
100
|
-
if (decisions.length === 0) {
|
|
101
|
-
lines.push(` ${chalk.dim('No trade decisions found.')}`);
|
|
102
|
-
lines.push('');
|
|
103
|
-
return lines.join('\n');
|
|
104
|
-
}
|
|
105
|
-
// Header
|
|
106
|
-
lines.push(' ' +
|
|
107
|
-
padRight('Date', 14) +
|
|
108
|
-
padRight('Token', 8) +
|
|
109
|
-
padRight('Type', 14) +
|
|
110
|
-
padRight('Dir', 8) +
|
|
111
|
-
padRight('Setup', 18) +
|
|
112
|
-
padRight('Price', 12) +
|
|
113
|
-
padRight('PnL', 14) +
|
|
114
|
-
'PnL %');
|
|
115
|
-
lines.push(' ' + '\u2500'.repeat(100));
|
|
116
|
-
for (const d of decisions) {
|
|
117
|
-
lines.push(formatDecisionRow(d));
|
|
118
|
-
}
|
|
119
|
-
lines.push('');
|
|
120
|
-
lines.push(` ${chalk.gray(`Total: ${decisions.length} decision(s)`)}`);
|
|
121
|
-
lines.push('');
|
|
122
|
-
return lines.join('\n');
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Format the stats output.
|
|
126
|
-
*/
|
|
127
|
-
export function formatStatsOutput(stats, label = 'Overall') {
|
|
128
|
-
const lines = [];
|
|
129
|
-
lines.push('');
|
|
130
|
-
lines.push(chalk.bold.cyan(` Trading Statistics: ${label}`));
|
|
131
|
-
lines.push(chalk.gray(' ' + '\u2500'.repeat(50)));
|
|
132
|
-
lines.push('');
|
|
133
|
-
if (stats.totalTrades === 0) {
|
|
134
|
-
lines.push(` ${chalk.dim('No completed trades with outcome data.')}`);
|
|
135
|
-
lines.push('');
|
|
136
|
-
return lines.join('\n');
|
|
137
|
-
}
|
|
138
|
-
lines.push(` ${chalk.gray('Total Trades:')} ${chalk.white(String(stats.totalTrades))}`);
|
|
139
|
-
lines.push(` ${chalk.gray('Win Rate:')} ${formatWinRate(stats.winRate)}`);
|
|
140
|
-
lines.push(` ${chalk.gray('Wins / Losses:')} ${chalk.green(String(stats.wins))} / ${chalk.red(String(stats.losses))}${stats.breakeven > 0 ? ` / ${chalk.dim(String(stats.breakeven) + ' BE')}` : ''}`);
|
|
141
|
-
lines.push('');
|
|
142
|
-
lines.push(` ${chalk.gray('Total PnL:')} ${formatPnl(stats.totalPnl)}`);
|
|
143
|
-
lines.push(` ${chalk.gray('Avg PnL:')} ${formatPnl(stats.avgPnl)}`);
|
|
144
|
-
lines.push(` ${chalk.gray('Avg PnL %:')} ${formatPercent(stats.avgPnlPercent)}`);
|
|
145
|
-
lines.push(` ${chalk.gray('Avg Hold Time:')} ${formatDuration(stats.avgHoldDurationMs)}`);
|
|
146
|
-
if (stats.bestTrade) {
|
|
147
|
-
const best = stats.bestTrade;
|
|
148
|
-
lines.push('');
|
|
149
|
-
lines.push(` ${chalk.gray('Best Trade:')} ${chalk.green(`+$${best.pnlAbsolute.toFixed(2)}`)} ${chalk.dim(`(${best.tokenSymbol} ${best.setupType ?? ''})`).trim()}`);
|
|
150
|
-
}
|
|
151
|
-
if (stats.worstTrade) {
|
|
152
|
-
const worst = stats.worstTrade;
|
|
153
|
-
lines.push(` ${chalk.gray('Worst Trade:')} ${chalk.red(`$${worst.pnlAbsolute.toFixed(2)}`)} ${chalk.dim(`(${worst.tokenSymbol} ${worst.setupType ?? ''})`).trim()}`);
|
|
154
|
-
}
|
|
155
|
-
lines.push('');
|
|
156
|
-
return lines.join('\n');
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Format stats grouped by setup type as a summary table.
|
|
160
|
-
*/
|
|
161
|
-
export function formatSetupTypeStats(statsBySetup) {
|
|
162
|
-
const lines = [];
|
|
163
|
-
lines.push('');
|
|
164
|
-
lines.push(chalk.bold(' Performance by Setup Type:'));
|
|
165
|
-
lines.push('');
|
|
166
|
-
if (statsBySetup.size === 0) {
|
|
167
|
-
lines.push(` ${chalk.dim('No setup type data available.')}`);
|
|
168
|
-
lines.push('');
|
|
169
|
-
return lines.join('\n');
|
|
170
|
-
}
|
|
171
|
-
lines.push(' ' +
|
|
172
|
-
padRight('Setup', 20) +
|
|
173
|
-
padRight('Trades', 8) +
|
|
174
|
-
padRight('Win Rate', 10) +
|
|
175
|
-
padRight('Avg PnL', 12) +
|
|
176
|
-
padRight('Total PnL', 14));
|
|
177
|
-
lines.push(' ' + '\u2500'.repeat(64));
|
|
178
|
-
// Sort by total PnL descending
|
|
179
|
-
const sorted = [...statsBySetup.entries()].sort((a, b) => b[1].totalPnl - a[1].totalPnl);
|
|
180
|
-
for (const [setup, s] of sorted) {
|
|
181
|
-
lines.push(' ' +
|
|
182
|
-
padRight(setup, 20) +
|
|
183
|
-
padRight(String(s.totalTrades), 8) +
|
|
184
|
-
padRight(formatWinRate(s.winRate), 10) +
|
|
185
|
-
padRight(formatPnl(s.avgPnl), 12) +
|
|
186
|
-
formatPnl(s.totalPnl));
|
|
187
|
-
}
|
|
188
|
-
lines.push('');
|
|
189
|
-
return lines.join('\n');
|
|
190
|
-
}
|
|
191
|
-
// ─── Command Registration ───
|
|
192
|
-
export function registerDecisionsCommand(program) {
|
|
193
|
-
// ─── decisions command ───
|
|
194
|
-
program
|
|
195
|
-
.command('decisions')
|
|
196
|
-
.description('Show trade decision history with outcomes')
|
|
197
|
-
.option('--token <symbol>', 'Filter by token symbol')
|
|
198
|
-
.option('--setup <type>', 'Filter by setup type (BREAKOUT, MOMENTUM, etc.)')
|
|
199
|
-
.option('--limit <n>', 'Maximum number of results', parseIntOption, 20)
|
|
200
|
-
.option('--trader <traderId>', 'Trader ID')
|
|
201
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
202
|
-
.action(async (options) => {
|
|
203
|
-
try {
|
|
204
|
-
const params = new URLSearchParams();
|
|
205
|
-
if (options.trader)
|
|
206
|
-
params.set('traderId', options.trader);
|
|
207
|
-
if (options.token)
|
|
208
|
-
params.set('tokenSymbol', options.token.toUpperCase());
|
|
209
|
-
if (options.setup)
|
|
210
|
-
params.set('setupType', options.setup);
|
|
211
|
-
if (options.limit)
|
|
212
|
-
params.set('limit', String(options.limit));
|
|
213
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
214
|
-
const result = await apiRequest(`/api/v1/decisions${qs}`);
|
|
215
|
-
const decisions = result.decisions ?? [];
|
|
216
|
-
if (options.format === 'json') {
|
|
217
|
-
console.log(JSON.stringify(decisions, null, 2));
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
console.log(formatDecisionsOutput(decisions));
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
catch (error) {
|
|
224
|
-
const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
|
|
225
|
-
const connErr = formatConnectionError(message);
|
|
226
|
-
if (connErr) {
|
|
227
|
-
console.error(connErr);
|
|
228
|
-
process.exitCode = 1;
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
logger.error({ error: message }, 'Failed to fetch decisions');
|
|
232
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
233
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
// ─── stats command ───
|
|
237
|
-
program
|
|
238
|
-
.command('stats')
|
|
239
|
-
.description('Show aggregate trading statistics (win rate, avg PnL, best/worst setups)')
|
|
240
|
-
.option('--period <period>', 'Time period (e.g., 7d, 30d, 90d)', '30d')
|
|
241
|
-
.option('--trader <traderId>', 'Trader ID', DEFAULT_TRADER_ID)
|
|
242
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
243
|
-
.action(async (options) => {
|
|
244
|
-
try {
|
|
245
|
-
const params = new URLSearchParams();
|
|
246
|
-
if (options.trader && options.trader !== DEFAULT_TRADER_ID)
|
|
247
|
-
params.set('traderId', options.trader);
|
|
248
|
-
if (options.limit)
|
|
249
|
-
params.set('limit', String(options.limit));
|
|
250
|
-
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
251
|
-
const result = await apiRequest(`/api/v1/decisions${qs}`);
|
|
252
|
-
const decisions = result.decisions ?? [];
|
|
253
|
-
// Compute stats client-side from decisions with outcomes
|
|
254
|
-
const stats = computeStatsFromDecisions(decisions, options.period);
|
|
255
|
-
if (options.format === 'json') {
|
|
256
|
-
const bySetupType = {};
|
|
257
|
-
for (const [key, value] of stats.bySetupType) {
|
|
258
|
-
bySetupType[key] = value;
|
|
259
|
-
}
|
|
260
|
-
console.log(JSON.stringify({ overall: stats.overall, bySetupType }, null, 2));
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
console.log(formatStatsOutput(stats.overall, `Last ${options.period}`));
|
|
264
|
-
if (stats.bySetupType.size > 0) {
|
|
265
|
-
console.log(formatSetupTypeStats(stats.bySetupType));
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
|
|
271
|
-
const connErr = formatConnectionError(message);
|
|
272
|
-
if (connErr) {
|
|
273
|
-
console.error(connErr);
|
|
274
|
-
process.exitCode = 1;
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
logger.error({ error: message }, 'Failed to fetch stats');
|
|
278
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
279
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
// ─── Client-Side Stats Computation ───
|
|
284
|
-
function computeStatsFromDecisions(decisions, _periodLabel) {
|
|
285
|
-
// Filter to decisions with outcome data
|
|
286
|
-
const withOutcomes = decisions.filter((d) => d.pnlAbsolute !== null && d.pnlAbsolute !== undefined);
|
|
287
|
-
const overall = computeStats(withOutcomes);
|
|
288
|
-
const bySetupType = new Map();
|
|
289
|
-
// Group by setup type
|
|
290
|
-
const grouped = new Map();
|
|
291
|
-
for (const d of withOutcomes) {
|
|
292
|
-
const setup = d.setupType ?? 'UNKNOWN';
|
|
293
|
-
if (!grouped.has(setup))
|
|
294
|
-
grouped.set(setup, []);
|
|
295
|
-
grouped.get(setup).push(d);
|
|
296
|
-
}
|
|
297
|
-
for (const [setup, group] of grouped) {
|
|
298
|
-
bySetupType.set(setup, computeStats(group));
|
|
299
|
-
}
|
|
300
|
-
return { overall, bySetupType };
|
|
301
|
-
}
|
|
302
|
-
function computeStats(decisions) {
|
|
303
|
-
if (decisions.length === 0) {
|
|
304
|
-
return {
|
|
305
|
-
totalTrades: 0, wins: 0, losses: 0, breakeven: 0, winRate: 0,
|
|
306
|
-
totalPnl: 0, avgPnl: 0, avgPnlPercent: 0, avgHoldDurationMs: 0,
|
|
307
|
-
bestTrade: null, worstTrade: null,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
let wins = 0, losses = 0, breakeven = 0;
|
|
311
|
-
let totalPnl = 0, totalPnlPct = 0, totalHold = 0;
|
|
312
|
-
let best = null;
|
|
313
|
-
let worst = null;
|
|
314
|
-
for (const d of decisions) {
|
|
315
|
-
const pnl = d.pnlAbsolute ?? 0;
|
|
316
|
-
totalPnl += pnl;
|
|
317
|
-
totalPnlPct += d.pnlPercent ?? 0;
|
|
318
|
-
totalHold += d.holdDurationMs ?? 0;
|
|
319
|
-
if (pnl > 0)
|
|
320
|
-
wins++;
|
|
321
|
-
else if (pnl < 0)
|
|
322
|
-
losses++;
|
|
323
|
-
else
|
|
324
|
-
breakeven++;
|
|
325
|
-
if (!best || pnl > (best.pnlAbsolute ?? 0))
|
|
326
|
-
best = d;
|
|
327
|
-
if (!worst || pnl < (worst.pnlAbsolute ?? 0))
|
|
328
|
-
worst = d;
|
|
329
|
-
}
|
|
330
|
-
return {
|
|
331
|
-
totalTrades: decisions.length,
|
|
332
|
-
wins, losses, breakeven,
|
|
333
|
-
winRate: decisions.length > 0 ? wins / decisions.length : 0,
|
|
334
|
-
totalPnl,
|
|
335
|
-
avgPnl: totalPnl / decisions.length,
|
|
336
|
-
avgPnlPercent: totalPnlPct / decisions.length,
|
|
337
|
-
avgHoldDurationMs: totalHold / decisions.length,
|
|
338
|
-
bestTrade: best,
|
|
339
|
-
worstTrade: worst,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
// ─── Helpers ───
|
|
343
|
-
/**
|
|
344
|
-
* Parse a period string like "7d", "30d", "90d" into milliseconds.
|
|
345
|
-
*/
|
|
346
|
-
export function parsePeriod(period) {
|
|
347
|
-
const match = period.match(/^(\d+)([dhm])$/);
|
|
348
|
-
if (!match) {
|
|
349
|
-
return 30 * 24 * 60 * 60 * 1000; // Default 30 days
|
|
350
|
-
}
|
|
351
|
-
const value = parseInt(match[1], 10);
|
|
352
|
-
const unit = match[2];
|
|
353
|
-
switch (unit) {
|
|
354
|
-
case 'd':
|
|
355
|
-
return value * 24 * 60 * 60 * 1000;
|
|
356
|
-
case 'h':
|
|
357
|
-
return value * 60 * 60 * 1000;
|
|
358
|
-
case 'm':
|
|
359
|
-
return value * 60 * 1000;
|
|
360
|
-
default:
|
|
361
|
-
return 30 * 24 * 60 * 60 * 1000;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
//# sourceMappingURL=decisions.js.map
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
interface EdgeRatioResult {
|
|
3
|
-
edgeRatio: number;
|
|
4
|
-
avgMfeAtr: number;
|
|
5
|
-
avgMaeAtr: number;
|
|
6
|
-
sampleSize: number;
|
|
7
|
-
}
|
|
8
|
-
interface EfficiencyResult {
|
|
9
|
-
entryEfficiency: number;
|
|
10
|
-
exitEfficiency: number;
|
|
11
|
-
combinedEfficiency: number;
|
|
12
|
-
}
|
|
13
|
-
interface EdgeAnalysis {
|
|
14
|
-
traderId: string;
|
|
15
|
-
analysis: {
|
|
16
|
-
edgeRatio: {
|
|
17
|
-
overall: EdgeRatioResult;
|
|
18
|
-
bySetupType: Record<string, EdgeRatioResult>;
|
|
19
|
-
byRegime: Record<string, EdgeRatioResult>;
|
|
20
|
-
};
|
|
21
|
-
efficiency: {
|
|
22
|
-
overall: EfficiencyResult;
|
|
23
|
-
bySetupType: Record<string, EfficiencyResult>;
|
|
24
|
-
confidenceCorrelation: number | null;
|
|
25
|
-
};
|
|
26
|
-
trades: Array<{
|
|
27
|
-
decisionId: string;
|
|
28
|
-
tokenSymbol: string;
|
|
29
|
-
setupType: string | null;
|
|
30
|
-
direction: string | null;
|
|
31
|
-
entryPrice: number;
|
|
32
|
-
exitPrice: number;
|
|
33
|
-
mfeAtr: number;
|
|
34
|
-
maeAtr: number;
|
|
35
|
-
entryEfficiency: number;
|
|
36
|
-
exitEfficiency: number;
|
|
37
|
-
combinedEfficiency: number;
|
|
38
|
-
pnlPercent: number;
|
|
39
|
-
}>;
|
|
40
|
-
excludedCount: number;
|
|
41
|
-
} | null;
|
|
42
|
-
message?: string;
|
|
43
|
-
}
|
|
44
|
-
interface TradeAttribution {
|
|
45
|
-
decisionId: string;
|
|
46
|
-
tokenSymbol: string;
|
|
47
|
-
direction: string;
|
|
48
|
-
realizedPnl: number;
|
|
49
|
-
signalAlpha: number;
|
|
50
|
-
executionCost: number;
|
|
51
|
-
timingVariance: number;
|
|
52
|
-
}
|
|
53
|
-
interface AttributionAnalysis {
|
|
54
|
-
traderId: string;
|
|
55
|
-
analysis: {
|
|
56
|
-
attributions: TradeAttribution[];
|
|
57
|
-
overall: {
|
|
58
|
-
trades: number;
|
|
59
|
-
avgSignalAlphaPct: number;
|
|
60
|
-
avgExecutionCostPct: number;
|
|
61
|
-
avgTimingVariancePct: number;
|
|
62
|
-
totalPnlUsd: number;
|
|
63
|
-
};
|
|
64
|
-
bySetupType: Record<string, {
|
|
65
|
-
trades: number;
|
|
66
|
-
avgSignalAlphaPct: number;
|
|
67
|
-
avgExecutionCostPct: number;
|
|
68
|
-
avgTimingVariancePct: number;
|
|
69
|
-
}>;
|
|
70
|
-
excludedCount: number;
|
|
71
|
-
} | null;
|
|
72
|
-
message?: string;
|
|
73
|
-
}
|
|
74
|
-
export declare function formatEdgeOutput(data: EdgeAnalysis): string;
|
|
75
|
-
export declare function formatAttributionOutput(data: AttributionAnalysis): string;
|
|
76
|
-
export declare function registerEdgeCommand(program: Command): void;
|
|
77
|
-
export {};
|
|
78
|
-
//# sourceMappingURL=edge-cmd.d.ts.map
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { Option } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { apiRequest, ApiError } from '../api-client.js';
|
|
4
|
-
import { ensureRemote } from '../utils.js';
|
|
5
|
-
// ─── Formatters ───
|
|
6
|
-
export function formatEdgeOutput(data) {
|
|
7
|
-
const lines = [];
|
|
8
|
-
lines.push('');
|
|
9
|
-
lines.push(chalk.bold.cyan(` Edge Ratio Analysis — ${data.traderId}`));
|
|
10
|
-
lines.push(chalk.gray(' ' + '─'.repeat(50)));
|
|
11
|
-
if (!data.analysis) {
|
|
12
|
-
lines.push(` ${chalk.dim(data.message ?? 'No data available')}`);
|
|
13
|
-
lines.push('');
|
|
14
|
-
return lines.join('\n');
|
|
15
|
-
}
|
|
16
|
-
const a = data.analysis;
|
|
17
|
-
const overall = a.edgeRatio?.overall;
|
|
18
|
-
const eff = a.efficiency?.overall;
|
|
19
|
-
// Overall edge ratio
|
|
20
|
-
const edgeVal = overall?.edgeRatio ?? 0;
|
|
21
|
-
lines.push(` ${chalk.gray('Edge Ratio:')} ${colorEdge(edgeVal)}`);
|
|
22
|
-
lines.push(` ${chalk.gray('Avg MFE/ATR:')} ${(overall?.avgMfeAtr ?? 0).toFixed(3)}`);
|
|
23
|
-
lines.push(` ${chalk.gray('Avg MAE/ATR:')} ${(overall?.avgMaeAtr ?? 0).toFixed(3)}`);
|
|
24
|
-
lines.push(` ${chalk.gray('Sample Size:')} ${overall?.sampleSize ?? 0}`);
|
|
25
|
-
lines.push('');
|
|
26
|
-
// Efficiency
|
|
27
|
-
if (eff) {
|
|
28
|
-
lines.push(chalk.bold(' Efficiency'));
|
|
29
|
-
lines.push(` ${chalk.gray('Entry:')} ${formatPct(eff.entryEfficiency)}`);
|
|
30
|
-
lines.push(` ${chalk.gray('Exit:')} ${formatPct(eff.exitEfficiency)}`);
|
|
31
|
-
lines.push(` ${chalk.gray('Combined:')} ${formatPct(eff.combinedEfficiency)}`);
|
|
32
|
-
if (a.efficiency.confidenceCorrelation !== null && a.efficiency.confidenceCorrelation !== undefined) {
|
|
33
|
-
lines.push(` ${chalk.gray('Confidence Corr:')} ${a.efficiency.confidenceCorrelation.toFixed(3)}`);
|
|
34
|
-
}
|
|
35
|
-
lines.push('');
|
|
36
|
-
}
|
|
37
|
-
// By Setup Type
|
|
38
|
-
const setupEntries = Object.entries(a.edgeRatio?.bySetupType ?? {});
|
|
39
|
-
if (setupEntries.length > 0) {
|
|
40
|
-
lines.push(chalk.bold(' By Setup Type'));
|
|
41
|
-
lines.push(` ${chalk.gray('Setup'.padEnd(20))}${'Edge'.padStart(8)}${'MFE/ATR'.padStart(10)}${'MAE/ATR'.padStart(10)}${'N'.padStart(5)}`);
|
|
42
|
-
lines.push(chalk.gray(' ' + '─'.repeat(53)));
|
|
43
|
-
for (const [setup, er] of setupEntries) {
|
|
44
|
-
lines.push(` ${setup.padEnd(20)}${colorEdge(er.edgeRatio).padStart(8)}${er.avgMfeAtr.toFixed(3).padStart(10)}${er.avgMaeAtr.toFixed(3).padStart(10)}${String(er.sampleSize).padStart(5)}`);
|
|
45
|
-
}
|
|
46
|
-
lines.push('');
|
|
47
|
-
}
|
|
48
|
-
// By Regime
|
|
49
|
-
const regimeEntries = Object.entries(a.edgeRatio?.byRegime ?? {});
|
|
50
|
-
if (regimeEntries.length > 0) {
|
|
51
|
-
lines.push(chalk.bold(' By Regime'));
|
|
52
|
-
lines.push(` ${chalk.gray('Regime'.padEnd(20))}${'Edge'.padStart(8)}${'MFE/ATR'.padStart(10)}${'MAE/ATR'.padStart(10)}${'N'.padStart(5)}`);
|
|
53
|
-
lines.push(chalk.gray(' ' + '─'.repeat(53)));
|
|
54
|
-
for (const [regime, er] of regimeEntries) {
|
|
55
|
-
lines.push(` ${regime.padEnd(20)}${colorEdge(er.edgeRatio).padStart(8)}${er.avgMfeAtr.toFixed(3).padStart(10)}${er.avgMaeAtr.toFixed(3).padStart(10)}${String(er.sampleSize).padStart(5)}`);
|
|
56
|
-
}
|
|
57
|
-
lines.push('');
|
|
58
|
-
}
|
|
59
|
-
// Trade summary
|
|
60
|
-
if (a.trades.length > 0) {
|
|
61
|
-
lines.push(` ${chalk.dim(`${a.trades.length} trades analyzed, ${a.excludedCount} excluded (missing ATR)`)}`);
|
|
62
|
-
lines.push('');
|
|
63
|
-
}
|
|
64
|
-
return lines.join('\n');
|
|
65
|
-
}
|
|
66
|
-
export function formatAttributionOutput(data) {
|
|
67
|
-
const lines = [];
|
|
68
|
-
lines.push('');
|
|
69
|
-
lines.push(chalk.bold.cyan(` PnL Attribution — ${data.traderId}`));
|
|
70
|
-
lines.push(chalk.gray(' ' + '─'.repeat(50)));
|
|
71
|
-
if (!data.analysis) {
|
|
72
|
-
lines.push(` ${chalk.dim(data.message ?? 'No data available')}`);
|
|
73
|
-
lines.push('');
|
|
74
|
-
return lines.join('\n');
|
|
75
|
-
}
|
|
76
|
-
const s = data.analysis.overall;
|
|
77
|
-
if (!s) {
|
|
78
|
-
lines.push(` ${chalk.dim('No attribution data available')}`);
|
|
79
|
-
lines.push('');
|
|
80
|
-
return lines.join('\n');
|
|
81
|
-
}
|
|
82
|
-
lines.push(chalk.bold(' Summary'));
|
|
83
|
-
lines.push(` ${chalk.gray('Total PnL:')} ${colorPnl(s.totalPnlUsd ?? 0)}`);
|
|
84
|
-
lines.push(` ${chalk.gray('Signal Alpha:')} ${formatPct(s.avgSignalAlphaPct ?? 0)}`);
|
|
85
|
-
lines.push(` ${chalk.gray('Execution Cost:')} ${formatPct(s.avgExecutionCostPct ?? 0)}`);
|
|
86
|
-
lines.push(` ${chalk.gray('Timing Variance:')} ${formatPct(s.avgTimingVariancePct ?? 0)}`);
|
|
87
|
-
lines.push(` ${chalk.gray('Trade Count:')} ${s.trades ?? 0}`);
|
|
88
|
-
// By Setup Type
|
|
89
|
-
const setupEntries = Object.entries(data.analysis.bySetupType ?? {});
|
|
90
|
-
if (setupEntries.length > 0) {
|
|
91
|
-
lines.push('');
|
|
92
|
-
lines.push(chalk.bold(' By Setup Type'));
|
|
93
|
-
lines.push(` ${chalk.gray('Setup'.padEnd(20))}${'Signal%'.padStart(10)}${'Exec%'.padStart(10)}${'Timing%'.padStart(10)}${'N'.padStart(5)}`);
|
|
94
|
-
lines.push(chalk.gray(' ' + '─'.repeat(55)));
|
|
95
|
-
for (const [setup, v] of setupEntries) {
|
|
96
|
-
lines.push(` ${setup.padEnd(20)}${formatPct(v.avgSignalAlphaPct).padStart(10)}${formatPct(v.avgExecutionCostPct).padStart(10)}${formatPct(v.avgTimingVariancePct).padStart(10)}${String(v.trades).padStart(5)}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (data.analysis.attributions.length > 0) {
|
|
100
|
-
lines.push('');
|
|
101
|
-
lines.push(chalk.bold(' Per-Trade Breakdown'));
|
|
102
|
-
lines.push(` ${chalk.gray('Token'.padEnd(10))}${'PnL'.padStart(12)}${'Signal'.padStart(12)}${'Exec'.padStart(12)}${'Timing'.padStart(12)}`);
|
|
103
|
-
lines.push(chalk.gray(' ' + '─'.repeat(58)));
|
|
104
|
-
const shown = data.analysis.attributions.slice(0, 20);
|
|
105
|
-
for (const t of shown) {
|
|
106
|
-
lines.push(` ${t.tokenSymbol.padEnd(10)}${formatUsd(t.realizedPnl ?? 0).padStart(12)}${formatUsd(t.signalAlpha ?? 0).padStart(12)}${formatUsd(t.executionCost ?? 0).padStart(12)}${formatUsd(t.timingVariance ?? 0).padStart(12)}`);
|
|
107
|
-
}
|
|
108
|
-
if (data.analysis.attributions.length > 20) {
|
|
109
|
-
lines.push(chalk.dim(` ... and ${data.analysis.attributions.length - 20} more`));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
lines.push('');
|
|
113
|
-
return lines.join('\n');
|
|
114
|
-
}
|
|
115
|
-
function colorEdge(ratio) {
|
|
116
|
-
const r = ratio ?? 0;
|
|
117
|
-
const str = r.toFixed(3);
|
|
118
|
-
if (r >= 1.0)
|
|
119
|
-
return chalk.green(str);
|
|
120
|
-
if (r >= 0.5)
|
|
121
|
-
return chalk.yellow(str);
|
|
122
|
-
return chalk.red(str);
|
|
123
|
-
}
|
|
124
|
-
function colorPnl(usd) {
|
|
125
|
-
const v = usd ?? 0;
|
|
126
|
-
const str = formatUsd(v);
|
|
127
|
-
return v >= 0 ? chalk.green(str) : chalk.red(str);
|
|
128
|
-
}
|
|
129
|
-
function formatUsd(val) {
|
|
130
|
-
const v = val ?? 0;
|
|
131
|
-
const sign = v >= 0 ? '+' : '';
|
|
132
|
-
return `${sign}$${v.toFixed(2)}`;
|
|
133
|
-
}
|
|
134
|
-
function formatPct(val) {
|
|
135
|
-
const v = val ?? 0;
|
|
136
|
-
return `${v >= 0 ? '+' : ''}${(v * 100).toFixed(1)}%`;
|
|
137
|
-
}
|
|
138
|
-
// ─── Command Registration ───
|
|
139
|
-
export function registerEdgeCommand(program) {
|
|
140
|
-
program
|
|
141
|
-
.command('edge <traderId>')
|
|
142
|
-
.description('Analyze trade quality — edge ratio and PnL attribution')
|
|
143
|
-
.option('--attribution', 'Show PnL attribution breakdown instead of edge ratio')
|
|
144
|
-
.option('--token <symbol>', 'Filter by token symbol')
|
|
145
|
-
.option('--limit <n>', 'Number of trades to analyze (1-500)', '100')
|
|
146
|
-
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
147
|
-
.action(async (traderId, options) => {
|
|
148
|
-
try {
|
|
149
|
-
if (!(await ensureRemote()))
|
|
150
|
-
return;
|
|
151
|
-
const query = new URLSearchParams();
|
|
152
|
-
if (options.token)
|
|
153
|
-
query.set('tokenSymbol', options.token.toUpperCase());
|
|
154
|
-
if (options.limit !== '100')
|
|
155
|
-
query.set('limit', options.limit);
|
|
156
|
-
const qs = query.toString() ? `?${query.toString()}` : '';
|
|
157
|
-
if (options.attribution) {
|
|
158
|
-
const data = await apiRequest(`/api/v1/traders/${encodeURIComponent(traderId)}/attribution${qs}`);
|
|
159
|
-
if (options.format === 'json') {
|
|
160
|
-
console.log(JSON.stringify(data, null, 2));
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
console.log(formatAttributionOutput(data));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
const data = await apiRequest(`/api/v1/traders/${encodeURIComponent(traderId)}/edge${qs}`);
|
|
168
|
-
if (options.format === 'json') {
|
|
169
|
-
console.log(JSON.stringify(data, null, 2));
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
console.log(formatEdgeOutput(data));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
|
|
178
|
-
console.error(chalk.red(`Error: ${message}`));
|
|
179
|
-
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
//# sourceMappingURL=edge-cmd.js.map
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
interface EdgeGuardAssessment {
|
|
3
|
-
traderId: string;
|
|
4
|
-
assessment: {
|
|
5
|
-
status: string;
|
|
6
|
-
escalation: string;
|
|
7
|
-
portfolioEV: number;
|
|
8
|
-
portfolioEVLower: number;
|
|
9
|
-
portfolioEVUpper: number;
|
|
10
|
-
negativeCells: number;
|
|
11
|
-
totalCells: number;
|
|
12
|
-
frictionActive: boolean;
|
|
13
|
-
message: string;
|
|
14
|
-
assessedAt: string;
|
|
15
|
-
cellEstimates: Array<{
|
|
16
|
-
setupType: string;
|
|
17
|
-
regime: string;
|
|
18
|
-
evPoint: number;
|
|
19
|
-
evLower: number;
|
|
20
|
-
evUpper: number;
|
|
21
|
-
winRate: number;
|
|
22
|
-
}>;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
interface FrictionResult {
|
|
26
|
-
traderId: string;
|
|
27
|
-
requiresAcknowledgement: boolean;
|
|
28
|
-
escalation: string;
|
|
29
|
-
message: string;
|
|
30
|
-
acknowledged: boolean;
|
|
31
|
-
}
|
|
32
|
-
export declare function formatAssessmentOutput(data: EdgeGuardAssessment): string;
|
|
33
|
-
export declare function formatFrictionOutput(data: FrictionResult): string;
|
|
34
|
-
export declare function registerEdgeGuardCommand(program: Command): void;
|
|
35
|
-
export {};
|
|
36
|
-
//# sourceMappingURL=edge-guard-cmd.d.ts.map
|