Files
trading_bot_v4/n8n-daily-report.json
mindesbunister d3c04ea9c9 feat: Position Manager persistence + order cleanup + improved stop loss
- Add Position Manager state persistence to survive restarts
  - Auto-restore open trades from database on startup
  - Save state after TP1, SL adjustments, profit locks
  - Persist to configSnapshot JSON field

- Add automatic order cancellation
  - Cancel all TP/SL orders when position fully closed
  - New cancelAllOrders() function in drift/orders.ts
  - Prevents orphaned orders after manual closes

- Improve stop loss management
  - Move SL to +0.35% after TP1 (was +0.15%)
  - Gives more breathing room for retracements
  - Still locks in half of TP1 profit

- Add database sync when Position Manager closes trades
  - Auto-update Trade record with exit data
  - Save P&L, exit reason, hold time
  - Fix analytics showing stale data

- Add trade state management functions
  - updateTradeState() for Position Manager persistence
  - getOpenTrades() for startup restoration
  - getInitializedPositionManager() for async init

- Create n8n database analytics workflows
  - Daily report workflow (automated at midnight)
  - Pattern analysis (hourly/daily performance)
  - Stop loss effectiveness analysis
  - Database analytics query workflow
  - Complete setup guide (N8N_DATABASE_SETUP.md)
2025-10-27 10:39:05 +01:00

172 lines
5.8 KiB
JSON

{
"name": "Daily Trading Report",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 0,
"triggerAtMinute": 5
}
]
}
},
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"name": "Every Day at Midnight",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [240, 300]
},
{
"parameters": {
"operation": "executeQuery",
"query": "-- Get yesterday's trading activity\nSELECT \n COUNT(*) as total_trades,\n COUNT(CASE WHEN \"realizedPnL\" > 0 THEN 1 END) as winning_trades,\n COUNT(CASE WHEN \"realizedPnL\" < 0 THEN 1 END) as losing_trades,\n SUM(\"realizedPnL\") as total_pnl,\n AVG(\"realizedPnL\") as avg_pnl,\n MAX(\"realizedPnL\") as best_trade,\n MIN(\"realizedPnL\") as worst_trade,\n AVG(\"holdTimeSeconds\") / 60 as avg_hold_time_minutes\nFROM \"Trade\"\nWHERE status = 'closed'\n AND \"isTestTrade\" = false\n AND \"exitTime\" >= CURRENT_DATE - INTERVAL '1 day'\n AND \"exitTime\" < CURRENT_DATE;"
},
"id": "2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
"name": "Query Yesterday Stats",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.4,
"position": [460, 300],
"credentials": {
"postgres": {
"id": "1",
"name": "Trading Bot Database"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "-- Get breakdown by symbol\nSELECT \n symbol,\n COUNT(*) as trades,\n SUM(\"realizedPnL\") as pnl\nFROM \"Trade\"\nWHERE status = 'closed'\n AND \"isTestTrade\" = false\n AND \"exitTime\" >= CURRENT_DATE - INTERVAL '1 day'\n AND \"exitTime\" < CURRENT_DATE\nGROUP BY symbol\nORDER BY pnl DESC;"
},
"id": "3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
"name": "Query Symbol Breakdown",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.4,
"position": [460, 500],
"credentials": {
"postgres": {
"id": "1",
"name": "Trading Bot Database"
}
}
},
{
"parameters": {
"operation": "insert",
"schema": {
"__rl": true,
"value": "public",
"mode": "list",
"cachedResultName": "public"
},
"table": {
"__rl": true,
"value": "DailyStats",
"mode": "list",
"cachedResultName": "DailyStats"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"date": "={{ $json.date }}",
"tradesCount": "={{ $json.total_trades }}",
"winningTrades": "={{ $json.winning_trades }}",
"losingTrades": "={{ $json.losing_trades }}",
"totalPnL": "={{ $json.total_pnl }}",
"totalPnLPercent": "0",
"winRate": "={{ $json.win_rate }}",
"avgWin": "={{ $json.avg_win }}",
"avgLoss": "={{ $json.avg_loss }}",
"profitFactor": "={{ $json.profit_factor }}",
"maxDrawdown": "0",
"sharpeRatio": "0"
}
}
},
"id": "4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
"name": "Save Daily Stats",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.4,
"position": [900, 300],
"credentials": {
"postgres": {
"id": "1",
"name": "Trading Bot Database"
}
}
},
{
"parameters": {
"jsCode": "const stats = $('Query Yesterday Stats').first().json;\nconst symbols = $('Query Symbol Breakdown').all();\n\nconst winRate = stats.total_trades > 0 ? (stats.winning_trades / stats.total_trades) * 100 : 0;\nconst avgWin = stats.winning_trades > 0 ? stats.total_pnl / stats.winning_trades : 0;\nconst avgLoss = stats.losing_trades > 0 ? Math.abs(stats.total_pnl / stats.losing_trades) : 0;\nconst profitFactor = avgLoss !== 0 ? avgWin / avgLoss : 0;\n\nconst yesterday = new Date();\nyesterday.setDate(yesterday.getDate() - 1);\nyesterday.setHours(0, 0, 0, 0);\n\nreturn [{\n json: {\n date: yesterday.toISOString(),\n total_trades: parseInt(stats.total_trades) || 0,\n winning_trades: parseInt(stats.winning_trades) || 0,\n losing_trades: parseInt(stats.losing_trades) || 0,\n total_pnl: parseFloat(stats.total_pnl) || 0,\n win_rate: winRate,\n avg_win: avgWin,\n avg_loss: avgLoss,\n profit_factor: profitFactor,\n symbols: symbols.map(s => s.json)\n }\n}];"
},
"id": "5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b",
"name": "Process Data",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [680, 300]
}
],
"connections": {
"Every Day at Midnight": {
"main": [
[
{
"node": "Query Yesterday Stats",
"type": "main",
"index": 0
},
{
"node": "Query Symbol Breakdown",
"type": "main",
"index": 0
}
]
]
},
"Query Yesterday Stats": {
"main": [
[
{
"node": "Process Data",
"type": "main",
"index": 0
}
]
]
},
"Query Symbol Breakdown": {
"main": [
[
{
"node": "Process Data",
"type": "main",
"index": 0
}
]
]
},
"Process Data": {
"main": [
[
{
"node": "Save Daily Stats",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 0,
"updatedAt": "2025-10-27T00:00:00.000Z",
"versionId": "1"
}