@@ -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' ,
0 commit comments