Skip to content

Commit 4777190

Browse files
alsk1992claude
andcommitted
Add flexible custom tracking system for bots and trades
## Custom Tracking (tracking.ts) - Define custom columns without schema changes - Track any metric per trade/bot/signal/position - Built-in columns: slippage, latency, confidence, volatility, etc. - Time-series tracking for custom values - Aggregations: sum, avg, min, max, count, last ## Easy Bot Integration ```typescript // Track anything in your strategy tracking.trackTrade(tradeId, 'my_score', 85); tracking.trackBot(botId, 'signals_skipped', 3); tracking.trackTimeSeries('portfolio_value', 12500); // Define custom columns tracking.defineColumn({ name: 'edge_estimate', type: 'number', category: 'trade', aggregation: 'avg' }); ``` ## Commands - /track columns [category] - List all columns - /track add <name> <type> - Add custom column - /track set <entity> <id> <col> <val> - Track value - /track get <entity> <id> - Get tracked data - /track summary <column> - Get stats - /track export - Export to CSV ## Built-in Columns Trade: slippage_pct, latency_ms, confidence_score, volatility Bot: signals_generated, signals_executed, evaluation_time_ms Market: volume_24h, liquidity_score, price_impact Custom: notes, tags, external_id Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d23f024 commit 4777190

3 files changed

Lines changed: 951 additions & 0 deletions

File tree

src/commands/registry.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,205 @@ export function createDefaultCommands(): CommandDefinition[] {
13391339
}
13401340
},
13411341
},
1342+
{
1343+
name: 'track',
1344+
description: 'Manage custom tracking columns and data',
1345+
usage: '/track [columns|add|remove|get|summary] [args]',
1346+
aliases: ['tracking'],
1347+
handler: async (args, ctx) => {
1348+
const trading = (ctx as any).trading;
1349+
if (!trading?.tracking) {
1350+
return 'Tracking manager not initialized.';
1351+
}
1352+
1353+
const parts = args.trim().split(/\s+/);
1354+
const subcommand = parts[0]?.toLowerCase() || 'columns';
1355+
const rest = parts.slice(1);
1356+
1357+
switch (subcommand) {
1358+
case 'columns': {
1359+
const category = rest[0];
1360+
const columns = trading.tracking.getColumns(category);
1361+
1362+
if (columns.length === 0) {
1363+
return category
1364+
? `No columns in category "${category}".`
1365+
: 'No tracking columns defined.';
1366+
}
1367+
1368+
const grouped = new Map<string, typeof columns>();
1369+
for (const col of columns) {
1370+
const cat = col.category || 'other';
1371+
const list = grouped.get(cat) || [];
1372+
list.push(col);
1373+
grouped.set(cat, list);
1374+
}
1375+
1376+
const lines = ['Tracking Columns', ''];
1377+
for (const [cat, cols] of grouped) {
1378+
lines.push(`**${cat}**`);
1379+
for (const col of cols) {
1380+
const summary = col.showInSummary ? ' [summary]' : '';
1381+
lines.push(` ${col.name} (${col.type})${summary}`);
1382+
if (col.description) {
1383+
lines.push(` ${col.description}`);
1384+
}
1385+
}
1386+
lines.push('');
1387+
}
1388+
1389+
return lines.join('\n');
1390+
}
1391+
1392+
case 'add': {
1393+
// /track add column_name type [category] [description]
1394+
const name = rest[0];
1395+
const type = (rest[1]?.toLowerCase() || 'string') as 'number' | 'string' | 'boolean' | 'json';
1396+
const category = rest[2] || 'custom';
1397+
const description = rest.slice(3).join(' ') || undefined;
1398+
1399+
if (!name) {
1400+
return [
1401+
'Usage: /track add <name> [type] [category] [description]',
1402+
'',
1403+
'Types: number, string, boolean, json',
1404+
'',
1405+
'Examples:',
1406+
' /track add my_score number trade My custom score',
1407+
' /track add trade_notes string trade Notes for each trade',
1408+
].join('\n');
1409+
}
1410+
1411+
if (!['number', 'string', 'boolean', 'json'].includes(type)) {
1412+
return `Invalid type: ${type}. Use: number, string, boolean, json`;
1413+
}
1414+
1415+
trading.tracking.defineColumn({
1416+
name,
1417+
label: name.replace(/_/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase()),
1418+
type,
1419+
category,
1420+
description,
1421+
showInSummary: type === 'number',
1422+
aggregation: type === 'number' ? 'avg' : undefined,
1423+
});
1424+
1425+
return `Column "${name}" added (${type}, ${category}).`;
1426+
}
1427+
1428+
case 'remove': {
1429+
const name = rest[0];
1430+
if (!name) {
1431+
return 'Usage: /track remove <column_name>';
1432+
}
1433+
1434+
trading.tracking.removeColumn(name);
1435+
return `Column "${name}" removed.`;
1436+
}
1437+
1438+
case 'set': {
1439+
// /track set entity_type entity_id column value
1440+
const [entityType, entityId, column, ...valueParts] = rest;
1441+
1442+
if (!entityType || !entityId || !column || valueParts.length === 0) {
1443+
return [
1444+
'Usage: /track set <entity_type> <entity_id> <column> <value>',
1445+
'',
1446+
'Examples:',
1447+
' /track set trade trade_abc123 my_score 85',
1448+
' /track set bot mean-reversion notes "Tweaked params"',
1449+
].join('\n');
1450+
}
1451+
1452+
const value = valueParts.join(' ');
1453+
const numValue = parseFloat(value);
1454+
const finalValue = isNaN(numValue) ? value : numValue;
1455+
1456+
trading.tracking.track({
1457+
entityType,
1458+
entityId,
1459+
column,
1460+
value: finalValue,
1461+
});
1462+
1463+
return `Tracked: ${entityType}/${entityId}.${column} = ${finalValue}`;
1464+
}
1465+
1466+
case 'get': {
1467+
// /track get entity_type entity_id [column]
1468+
const [entityType, entityId, column] = rest;
1469+
1470+
if (!entityType || !entityId) {
1471+
return 'Usage: /track get <entity_type> <entity_id> [column]';
1472+
}
1473+
1474+
if (column) {
1475+
const value = trading.tracking.getLatest(entityType, entityId, column);
1476+
return value !== undefined
1477+
? `${column}: ${JSON.stringify(value)}`
1478+
: `No value for ${column}`;
1479+
}
1480+
1481+
const entries = trading.tracking.get({ entityType, entityId, limit: 20 });
1482+
if (entries.length === 0) {
1483+
return `No tracking data for ${entityType}/${entityId}`;
1484+
}
1485+
1486+
const lines = [`Tracking: ${entityType}/${entityId}`, ''];
1487+
const byColumn = new Map<string, unknown>();
1488+
for (const entry of entries) {
1489+
if (!byColumn.has(entry.column)) {
1490+
byColumn.set(entry.column, entry.value);
1491+
}
1492+
}
1493+
1494+
for (const [col, val] of byColumn) {
1495+
lines.push(` ${col}: ${JSON.stringify(val)}`);
1496+
}
1497+
1498+
return lines.join('\n');
1499+
}
1500+
1501+
case 'summary': {
1502+
const column = rest[0];
1503+
if (!column) {
1504+
return 'Usage: /track summary <column>';
1505+
}
1506+
1507+
const summary = trading.tracking.getSummary(column);
1508+
1509+
const lines = [`Summary: ${column}`, ''];
1510+
lines.push(` Count: ${summary.count}`);
1511+
if (summary.sum !== null) lines.push(` Sum: ${summary.sum?.toFixed(2)}`);
1512+
if (summary.avg !== null) lines.push(` Avg: ${summary.avg?.toFixed(2)}`);
1513+
if (summary.min !== null) lines.push(` Min: ${summary.min?.toFixed(2)}`);
1514+
if (summary.max !== null) lines.push(` Max: ${summary.max?.toFixed(2)}`);
1515+
if (summary.latest !== undefined) lines.push(` Latest: ${JSON.stringify(summary.latest)}`);
1516+
1517+
return lines.join('\n');
1518+
}
1519+
1520+
case 'export': {
1521+
const csv = trading.tracking.exportCsv({ limit: 100 });
1522+
return `Exported ${csv.split('\n').length - 1} rows:\n\n${csv.slice(0, 1000)}${csv.length > 1000 ? '\n...' : ''}`;
1523+
}
1524+
1525+
default:
1526+
return [
1527+
'Usage: /track [command]',
1528+
'',
1529+
'Commands:',
1530+
' columns [category] - List tracking columns',
1531+
' add <name> [type] [cat] - Add custom column',
1532+
' remove <name> - Remove column',
1533+
' set <type> <id> <col> <v> - Track a value',
1534+
' get <type> <id> [col] - Get tracked values',
1535+
' summary <column> - Get column stats',
1536+
' export - Export to CSV',
1537+
].join('\n');
1538+
}
1539+
},
1540+
},
13421541
{
13431542
name: 'safety',
13441543
description: 'Trading safety controls and circuit breakers',

src/trading/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export * from './safety';
4444
export * from './resilience';
4545
export * from './secrets';
4646
export * from './backtest';
47+
export * from './tracking';
4748

4849
import {
4950
createTradeLogger,

0 commit comments

Comments
 (0)