@timmeck/trading-brain 1.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.
Files changed (247) hide show
  1. package/README.md +306 -0
  2. package/dist/api/server.d.ts +21 -0
  3. package/dist/api/server.js +157 -0
  4. package/dist/api/server.js.map +1 -0
  5. package/dist/cli/colors.d.ts +46 -0
  6. package/dist/cli/colors.js +70 -0
  7. package/dist/cli/colors.js.map +1 -0
  8. package/dist/cli/commands/config.d.ts +2 -0
  9. package/dist/cli/commands/config.js +70 -0
  10. package/dist/cli/commands/config.js.map +1 -0
  11. package/dist/cli/commands/doctor.d.ts +2 -0
  12. package/dist/cli/commands/doctor.js +61 -0
  13. package/dist/cli/commands/doctor.js.map +1 -0
  14. package/dist/cli/commands/export.d.ts +2 -0
  15. package/dist/cli/commands/export.js +23 -0
  16. package/dist/cli/commands/export.js.map +1 -0
  17. package/dist/cli/commands/import.d.ts +2 -0
  18. package/dist/cli/commands/import.js +42 -0
  19. package/dist/cli/commands/import.js.map +1 -0
  20. package/dist/cli/commands/insights.d.ts +2 -0
  21. package/dist/cli/commands/insights.js +29 -0
  22. package/dist/cli/commands/insights.js.map +1 -0
  23. package/dist/cli/commands/network.d.ts +2 -0
  24. package/dist/cli/commands/network.js +43 -0
  25. package/dist/cli/commands/network.js.map +1 -0
  26. package/dist/cli/commands/query.d.ts +2 -0
  27. package/dist/cli/commands/query.js +27 -0
  28. package/dist/cli/commands/query.js.map +1 -0
  29. package/dist/cli/commands/rules.d.ts +2 -0
  30. package/dist/cli/commands/rules.js +26 -0
  31. package/dist/cli/commands/rules.js.map +1 -0
  32. package/dist/cli/commands/start.d.ts +2 -0
  33. package/dist/cli/commands/start.js +86 -0
  34. package/dist/cli/commands/start.js.map +1 -0
  35. package/dist/cli/commands/status.d.ts +2 -0
  36. package/dist/cli/commands/status.js +58 -0
  37. package/dist/cli/commands/status.js.map +1 -0
  38. package/dist/cli/commands/stop.d.ts +2 -0
  39. package/dist/cli/commands/stop.js +34 -0
  40. package/dist/cli/commands/stop.js.map +1 -0
  41. package/dist/cli/ipc-helper.d.ts +2 -0
  42. package/dist/cli/ipc-helper.js +26 -0
  43. package/dist/cli/ipc-helper.js.map +1 -0
  44. package/dist/config.d.ts +2 -0
  45. package/dist/config.js +107 -0
  46. package/dist/config.js.map +1 -0
  47. package/dist/db/connection.d.ts +2 -0
  48. package/dist/db/connection.js +19 -0
  49. package/dist/db/connection.js.map +1 -0
  50. package/dist/db/migrations/001_core.d.ts +2 -0
  51. package/dist/db/migrations/001_core.js +42 -0
  52. package/dist/db/migrations/001_core.js.map +1 -0
  53. package/dist/db/migrations/002_synapses.d.ts +2 -0
  54. package/dist/db/migrations/002_synapses.js +43 -0
  55. package/dist/db/migrations/002_synapses.js.map +1 -0
  56. package/dist/db/migrations/003_learning.d.ts +2 -0
  57. package/dist/db/migrations/003_learning.js +48 -0
  58. package/dist/db/migrations/003_learning.js.map +1 -0
  59. package/dist/db/migrations/004_research.d.ts +2 -0
  60. package/dist/db/migrations/004_research.js +29 -0
  61. package/dist/db/migrations/004_research.js.map +1 -0
  62. package/dist/db/migrations/index.d.ts +2 -0
  63. package/dist/db/migrations/index.js +45 -0
  64. package/dist/db/migrations/index.js.map +1 -0
  65. package/dist/db/repositories/calibration.repository.d.ts +25 -0
  66. package/dist/db/repositories/calibration.repository.js +66 -0
  67. package/dist/db/repositories/calibration.repository.js.map +1 -0
  68. package/dist/db/repositories/chain.repository.d.ts +28 -0
  69. package/dist/db/repositories/chain.repository.js +52 -0
  70. package/dist/db/repositories/chain.repository.js.map +1 -0
  71. package/dist/db/repositories/graph.repository.d.ts +33 -0
  72. package/dist/db/repositories/graph.repository.js +73 -0
  73. package/dist/db/repositories/graph.repository.js.map +1 -0
  74. package/dist/db/repositories/insight.repository.d.ts +30 -0
  75. package/dist/db/repositories/insight.repository.js +60 -0
  76. package/dist/db/repositories/insight.repository.js.map +1 -0
  77. package/dist/db/repositories/rule.repository.d.ts +35 -0
  78. package/dist/db/repositories/rule.repository.js +48 -0
  79. package/dist/db/repositories/rule.repository.js.map +1 -0
  80. package/dist/db/repositories/signal.repository.d.ts +17 -0
  81. package/dist/db/repositories/signal.repository.js +35 -0
  82. package/dist/db/repositories/signal.repository.js.map +1 -0
  83. package/dist/db/repositories/synapse.repository.d.ts +25 -0
  84. package/dist/db/repositories/synapse.repository.js +50 -0
  85. package/dist/db/repositories/synapse.repository.js.map +1 -0
  86. package/dist/db/repositories/trade.repository.d.ts +36 -0
  87. package/dist/db/repositories/trade.repository.js +64 -0
  88. package/dist/db/repositories/trade.repository.js.map +1 -0
  89. package/dist/graph/weighted-graph.d.ts +58 -0
  90. package/dist/graph/weighted-graph.js +149 -0
  91. package/dist/graph/weighted-graph.js.map +1 -0
  92. package/dist/index.d.ts +2 -0
  93. package/dist/index.js +49 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/ipc/client.d.ts +16 -0
  96. package/dist/ipc/client.js +95 -0
  97. package/dist/ipc/client.js.map +1 -0
  98. package/dist/ipc/protocol.d.ts +8 -0
  99. package/dist/ipc/protocol.js +29 -0
  100. package/dist/ipc/protocol.js.map +1 -0
  101. package/dist/ipc/router.d.ts +32 -0
  102. package/dist/ipc/router.js +70 -0
  103. package/dist/ipc/router.js.map +1 -0
  104. package/dist/ipc/server.d.ts +18 -0
  105. package/dist/ipc/server.js +142 -0
  106. package/dist/ipc/server.js.map +1 -0
  107. package/dist/learning/calibrator.d.ts +6 -0
  108. package/dist/learning/calibrator.js +57 -0
  109. package/dist/learning/calibrator.js.map +1 -0
  110. package/dist/learning/chain-detector.d.ts +17 -0
  111. package/dist/learning/chain-detector.js +29 -0
  112. package/dist/learning/chain-detector.js.map +1 -0
  113. package/dist/learning/learning-engine.d.ts +31 -0
  114. package/dist/learning/learning-engine.js +85 -0
  115. package/dist/learning/learning-engine.js.map +1 -0
  116. package/dist/learning/pattern-extractor.d.ts +14 -0
  117. package/dist/learning/pattern-extractor.js +40 -0
  118. package/dist/learning/pattern-extractor.js.map +1 -0
  119. package/dist/mcp/http-server.d.ts +14 -0
  120. package/dist/mcp/http-server.js +117 -0
  121. package/dist/mcp/http-server.js.map +1 -0
  122. package/dist/mcp/server.d.ts +1 -0
  123. package/dist/mcp/server.js +67 -0
  124. package/dist/mcp/server.js.map +1 -0
  125. package/dist/mcp/tools.d.ts +7 -0
  126. package/dist/mcp/tools.js +158 -0
  127. package/dist/mcp/tools.js.map +1 -0
  128. package/dist/research/research-engine.d.ts +21 -0
  129. package/dist/research/research-engine.js +204 -0
  130. package/dist/research/research-engine.js.map +1 -0
  131. package/dist/services/analytics.service.d.ts +16 -0
  132. package/dist/services/analytics.service.js +64 -0
  133. package/dist/services/analytics.service.js.map +1 -0
  134. package/dist/services/insight.service.d.ts +11 -0
  135. package/dist/services/insight.service.js +25 -0
  136. package/dist/services/insight.service.js.map +1 -0
  137. package/dist/services/signal.service.d.ts +22 -0
  138. package/dist/services/signal.service.js +96 -0
  139. package/dist/services/signal.service.js.map +1 -0
  140. package/dist/services/strategy.service.d.ts +29 -0
  141. package/dist/services/strategy.service.js +115 -0
  142. package/dist/services/strategy.service.js.map +1 -0
  143. package/dist/services/synapse.service.d.ts +20 -0
  144. package/dist/services/synapse.service.js +48 -0
  145. package/dist/services/synapse.service.js.map +1 -0
  146. package/dist/services/trade.service.d.ts +37 -0
  147. package/dist/services/trade.service.js +114 -0
  148. package/dist/services/trade.service.js.map +1 -0
  149. package/dist/signals/fingerprint.d.ts +29 -0
  150. package/dist/signals/fingerprint.js +98 -0
  151. package/dist/signals/fingerprint.js.map +1 -0
  152. package/dist/signals/wilson-score.d.ts +10 -0
  153. package/dist/signals/wilson-score.js +19 -0
  154. package/dist/signals/wilson-score.js.map +1 -0
  155. package/dist/synapses/decay.d.ts +6 -0
  156. package/dist/synapses/decay.js +17 -0
  157. package/dist/synapses/decay.js.map +1 -0
  158. package/dist/synapses/hebbian.d.ts +11 -0
  159. package/dist/synapses/hebbian.js +21 -0
  160. package/dist/synapses/hebbian.js.map +1 -0
  161. package/dist/synapses/synapse-manager.d.ts +22 -0
  162. package/dist/synapses/synapse-manager.js +99 -0
  163. package/dist/synapses/synapse-manager.js.map +1 -0
  164. package/dist/trading-core.d.ts +17 -0
  165. package/dist/trading-core.js +235 -0
  166. package/dist/trading-core.js.map +1 -0
  167. package/dist/types/config.types.d.ts +52 -0
  168. package/dist/types/config.types.js +2 -0
  169. package/dist/types/config.types.js.map +1 -0
  170. package/dist/types/ipc.types.d.ts +11 -0
  171. package/dist/types/ipc.types.js +2 -0
  172. package/dist/types/ipc.types.js.map +1 -0
  173. package/dist/utils/events.d.ts +48 -0
  174. package/dist/utils/events.js +23 -0
  175. package/dist/utils/events.js.map +1 -0
  176. package/dist/utils/hash.d.ts +1 -0
  177. package/dist/utils/hash.js +5 -0
  178. package/dist/utils/hash.js.map +1 -0
  179. package/dist/utils/logger.d.ts +8 -0
  180. package/dist/utils/logger.js +39 -0
  181. package/dist/utils/logger.js.map +1 -0
  182. package/dist/utils/paths.d.ts +3 -0
  183. package/dist/utils/paths.js +18 -0
  184. package/dist/utils/paths.js.map +1 -0
  185. package/package.json +47 -0
  186. package/src/api/server.ts +160 -0
  187. package/src/cli/colors.ts +80 -0
  188. package/src/cli/commands/config.ts +76 -0
  189. package/src/cli/commands/doctor.ts +62 -0
  190. package/src/cli/commands/export.ts +24 -0
  191. package/src/cli/commands/import.ts +44 -0
  192. package/src/cli/commands/insights.ts +30 -0
  193. package/src/cli/commands/network.ts +43 -0
  194. package/src/cli/commands/query.ts +28 -0
  195. package/src/cli/commands/rules.ts +27 -0
  196. package/src/cli/commands/start.ts +93 -0
  197. package/src/cli/commands/status.ts +64 -0
  198. package/src/cli/commands/stop.ts +33 -0
  199. package/src/cli/ipc-helper.ts +22 -0
  200. package/src/config.ts +103 -0
  201. package/src/db/connection.ts +22 -0
  202. package/src/db/migrations/001_core.ts +43 -0
  203. package/src/db/migrations/002_synapses.ts +44 -0
  204. package/src/db/migrations/003_learning.ts +49 -0
  205. package/src/db/migrations/004_research.ts +30 -0
  206. package/src/db/migrations/index.ts +60 -0
  207. package/src/db/repositories/calibration.repository.ts +86 -0
  208. package/src/db/repositories/chain.repository.ts +70 -0
  209. package/src/db/repositories/graph.repository.ts +103 -0
  210. package/src/db/repositories/insight.repository.ts +80 -0
  211. package/src/db/repositories/rule.repository.ts +67 -0
  212. package/src/db/repositories/signal.repository.ts +48 -0
  213. package/src/db/repositories/synapse.repository.ts +71 -0
  214. package/src/db/repositories/trade.repository.ts +97 -0
  215. package/src/graph/weighted-graph.ts +194 -0
  216. package/src/index.ts +55 -0
  217. package/src/ipc/client.ts +112 -0
  218. package/src/ipc/protocol.ts +35 -0
  219. package/src/ipc/router.ts +113 -0
  220. package/src/ipc/server.ts +150 -0
  221. package/src/learning/calibrator.ts +57 -0
  222. package/src/learning/chain-detector.ts +43 -0
  223. package/src/learning/learning-engine.ts +94 -0
  224. package/src/learning/pattern-extractor.ts +53 -0
  225. package/src/mcp/http-server.ts +118 -0
  226. package/src/mcp/server.ts +72 -0
  227. package/src/mcp/tools.ts +256 -0
  228. package/src/research/research-engine.ts +223 -0
  229. package/src/services/analytics.service.ts +68 -0
  230. package/src/services/insight.service.ts +29 -0
  231. package/src/services/signal.service.ts +109 -0
  232. package/src/services/strategy.service.ts +130 -0
  233. package/src/services/synapse.service.ts +58 -0
  234. package/src/services/trade.service.ts +139 -0
  235. package/src/signals/fingerprint.ts +93 -0
  236. package/src/signals/wilson-score.ts +17 -0
  237. package/src/synapses/decay.ts +19 -0
  238. package/src/synapses/hebbian.ts +23 -0
  239. package/src/synapses/synapse-manager.ts +112 -0
  240. package/src/trading-core.ts +285 -0
  241. package/src/types/config.types.ts +60 -0
  242. package/src/types/ipc.types.ts +8 -0
  243. package/src/utils/events.ts +42 -0
  244. package/src/utils/hash.ts +5 -0
  245. package/src/utils/logger.ts +48 -0
  246. package/src/utils/paths.ts +19 -0
  247. package/tsconfig.json +18 -0
@@ -0,0 +1,139 @@
1
+ import type { TradeRepository, TradeRecord } from '../db/repositories/trade.repository.js';
2
+ import type { SignalRepository } from '../db/repositories/signal.repository.js';
3
+ import type { ChainRepository } from '../db/repositories/chain.repository.js';
4
+ import type { SynapseManager } from '../synapses/synapse-manager.js';
5
+ import type { WeightedGraph } from '../graph/weighted-graph.js';
6
+ import type { CalibrationConfig, LearningConfig } from '../types/config.types.js';
7
+ import { fingerprint, decomposeFingerprint, type SignalInput } from '../signals/fingerprint.js';
8
+ import { NODE_TYPES } from '../graph/weighted-graph.js';
9
+ import { detectChain } from '../learning/chain-detector.js';
10
+ import { getLogger } from '../utils/logger.js';
11
+ import { getEventBus } from '../utils/events.js';
12
+
13
+ export interface RecordOutcomeInput {
14
+ signals: SignalInput;
15
+ regime?: string;
16
+ profitPct: number;
17
+ win: boolean;
18
+ botType: string;
19
+ pair: string;
20
+ }
21
+
22
+ export class TradeService {
23
+ private recentTrades: TradeRecord[] = [];
24
+ private logger = getLogger();
25
+
26
+ constructor(
27
+ private tradeRepo: TradeRepository,
28
+ private signalRepo: SignalRepository,
29
+ private chainRepo: ChainRepository,
30
+ private synapseManager: SynapseManager,
31
+ private graph: WeightedGraph,
32
+ private cal: CalibrationConfig,
33
+ private learningConfig: LearningConfig,
34
+ ) {
35
+ // Seed recent trades for chain detection
36
+ this.recentTrades = this.tradeRepo.getRecent(10);
37
+ }
38
+
39
+ updateCalibration(cal: CalibrationConfig): void {
40
+ this.cal = cal;
41
+ }
42
+
43
+ recordOutcome(input: RecordOutcomeInput): { tradeId: number; fingerprint: string; synapseWeight: number } {
44
+ const bus = getEventBus();
45
+ const fp = fingerprint({ ...input.signals, regime: input.regime });
46
+
47
+ // 1. Store signal combo
48
+ this.signalRepo.create(fp, JSON.stringify(input.signals), input.regime);
49
+
50
+ // 2. Store trade
51
+ const tradeId = this.tradeRepo.create({
52
+ fingerprint: fp,
53
+ pair: input.pair,
54
+ bot_type: input.botType,
55
+ regime: input.regime,
56
+ profit_pct: input.profitPct,
57
+ win: input.win,
58
+ signals_json: JSON.stringify(input.signals),
59
+ });
60
+
61
+ // 3. Hebbian synapse update
62
+ const synapse = input.win
63
+ ? this.synapseManager.recordWin(fp, input.profitPct)
64
+ : this.synapseManager.recordLoss(fp, input.profitPct);
65
+
66
+ // 4. Update weighted graph
67
+ const graphNodes = decomposeFingerprint(fp, input.regime, input.pair, input.botType);
68
+ const outcomeNodeId = input.win ? 'outcome_win' : 'outcome_loss';
69
+
70
+ for (const gn of graphNodes) {
71
+ this.graph.addNode(gn.id, gn.type, gn.label);
72
+ }
73
+ this.graph.addNode(outcomeNodeId, NODE_TYPES.OUTCOME, input.win ? 'win' : 'loss');
74
+
75
+ const comboNodeId = `combo_${fp}`;
76
+ for (const gn of graphNodes) {
77
+ if (gn.id !== comboNodeId) {
78
+ this.graph.addEdge(gn.id, comboNodeId, 0.5);
79
+ if (input.win) {
80
+ this.graph.strengthenEdge(gn.id, comboNodeId, this.cal.learningRate);
81
+ } else {
82
+ this.graph.weakenEdge(gn.id, comboNodeId, this.cal.weakenPenalty);
83
+ }
84
+ }
85
+ }
86
+
87
+ this.graph.addEdge(comboNodeId, outcomeNodeId, 0.5);
88
+ if (input.win) {
89
+ this.graph.strengthenEdge(comboNodeId, outcomeNodeId, this.cal.learningRate);
90
+ } else {
91
+ this.graph.weakenEdge(comboNodeId, outcomeNodeId, this.cal.weakenPenalty);
92
+ }
93
+
94
+ // Cross-connect co-occurring signals (Hebbian)
95
+ for (let i = 0; i < graphNodes.length; i++) {
96
+ for (let j = i + 1; j < graphNodes.length; j++) {
97
+ if (graphNodes[i]!.id !== comboNodeId && graphNodes[j]!.id !== comboNodeId) {
98
+ this.graph.addEdge(graphNodes[i]!.id, graphNodes[j]!.id, 0.3);
99
+ if (input.win) {
100
+ this.graph.strengthenEdge(graphNodes[i]!.id, graphNodes[j]!.id, this.cal.learningRate * 0.5);
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ // 5. Chain detection
107
+ const trade = this.tradeRepo.getById(tradeId)!;
108
+ this.recentTrades.push(trade);
109
+ if (this.recentTrades.length > 10) this.recentTrades.shift();
110
+
111
+ const chain = detectChain(this.recentTrades, trade, this.learningConfig.chainMinLength);
112
+ if (chain) {
113
+ this.chainRepo.create(chain);
114
+ bus.emit('chain:detected', { pair: chain.pair, type: chain.type, length: chain.length });
115
+ this.logger.info(`Chain detected: ${chain.type} (${chain.length}x) on ${chain.pair}`);
116
+ }
117
+
118
+ bus.emit('trade:recorded', { tradeId, fingerprint: fp, win: input.win });
119
+ this.logger.info(`Recorded: ${fp} → ${input.win ? 'WIN' : 'LOSS'} (${input.profitPct.toFixed(2)}%) | weight: ${synapse.weight.toFixed(3)} | graph: ${this.graph.getNodeCount()}N/${this.graph.getEdgeCount()}E`);
120
+
121
+ return { tradeId, fingerprint: fp, synapseWeight: synapse.weight };
122
+ }
123
+
124
+ query(search: string, limit: number = 50): TradeRecord[] {
125
+ return this.tradeRepo.search(search, limit);
126
+ }
127
+
128
+ getRecent(limit: number = 10): TradeRecord[] {
129
+ return this.tradeRepo.getRecent(limit);
130
+ }
131
+
132
+ getByPair(pair: string): TradeRecord[] {
133
+ return this.tradeRepo.getByPair(pair);
134
+ }
135
+
136
+ count(): number {
137
+ return this.tradeRepo.count();
138
+ }
139
+ }
@@ -0,0 +1,93 @@
1
+ import { NODE_TYPES } from '../graph/weighted-graph.js';
2
+
3
+ export interface SignalInput {
4
+ rsi14?: number;
5
+ macd?: number;
6
+ trendScore?: number;
7
+ volatility?: number;
8
+ regime?: string;
9
+ }
10
+
11
+ export interface DecomposedNode {
12
+ id: string;
13
+ type: string;
14
+ label: string;
15
+ }
16
+
17
+ export function classifyRSI(rsi: number): string {
18
+ if (rsi < 25) return 'extreme_oversold';
19
+ if (rsi < 30) return 'oversold';
20
+ if (rsi < 40) return 'low';
21
+ if (rsi > 75) return 'extreme_overbought';
22
+ if (rsi > 70) return 'overbought';
23
+ if (rsi > 60) return 'high';
24
+ return 'neutral';
25
+ }
26
+
27
+ export function classifyMACD(macd: number, trendScore: number): string {
28
+ if (macd > 0 && trendScore > 0) return 'bullish';
29
+ if (macd < 0 && trendScore < 0) return 'bearish';
30
+ return 'neutral';
31
+ }
32
+
33
+ export function classifyTrend(trendScore: number): string {
34
+ if (trendScore > 3) return 'strong_up';
35
+ if (trendScore > 1) return 'up';
36
+ if (trendScore < -3) return 'strong_down';
37
+ if (trendScore < -1) return 'down';
38
+ return 'flat';
39
+ }
40
+
41
+ export function classifyVolatility(volatility: number): string {
42
+ if (volatility > 80) return 'extreme';
43
+ if (volatility > 50) return 'high';
44
+ if (volatility > 30) return 'medium';
45
+ return 'low';
46
+ }
47
+
48
+ /**
49
+ * Create a signal fingerprint string from input signals.
50
+ * Format: rsi_class|macd_class|trend_class|vol_class[|regime]
51
+ */
52
+ export function fingerprint(signals: SignalInput): string {
53
+ const parts = [
54
+ classifyRSI(signals.rsi14 ?? 50),
55
+ classifyMACD(signals.macd ?? 0, signals.trendScore ?? 0),
56
+ classifyTrend(signals.trendScore ?? 0),
57
+ classifyVolatility(signals.volatility ?? 30),
58
+ ];
59
+ if (signals.regime) parts.push(signals.regime);
60
+ return parts.join('|');
61
+ }
62
+
63
+ /**
64
+ * Compare two fingerprints for similarity (0-1).
65
+ */
66
+ export function fingerprintSimilarity(fp1: string, fp2: string): number {
67
+ const parts1 = fp1.split('|');
68
+ const parts2 = fp2.split('|');
69
+ const maxLen = Math.max(parts1.length, parts2.length);
70
+ if (maxLen === 0) return 1;
71
+ let matches = 0;
72
+ for (let i = 0; i < maxLen; i++) {
73
+ if (parts1[i] === parts2[i]) matches++;
74
+ }
75
+ return matches / maxLen;
76
+ }
77
+
78
+ /**
79
+ * Decompose a fingerprint into individual graph node IDs for the weighted graph.
80
+ */
81
+ export function decomposeFingerprint(fp: string, regime?: string, pair?: string, botType?: string): DecomposedNode[] {
82
+ const parts = fp.split('|');
83
+ const nodes: DecomposedNode[] = [];
84
+ if (parts[0]) nodes.push({ id: `sig_rsi_${parts[0]}`, type: NODE_TYPES.SIGNAL, label: parts[0] });
85
+ if (parts[1]) nodes.push({ id: `sig_macd_${parts[1]}`, type: NODE_TYPES.SIGNAL, label: parts[1] });
86
+ if (parts[2]) nodes.push({ id: `sig_trend_${parts[2]}`, type: NODE_TYPES.SIGNAL, label: parts[2] });
87
+ if (parts[3]) nodes.push({ id: `sig_vol_${parts[3]}`, type: NODE_TYPES.SIGNAL, label: parts[3] });
88
+ if (regime) nodes.push({ id: `regime_${regime}`, type: NODE_TYPES.REGIME, label: regime });
89
+ if (pair) nodes.push({ id: `pair_${pair}`, type: NODE_TYPES.PAIR, label: pair });
90
+ if (botType) nodes.push({ id: `bot_${botType}`, type: NODE_TYPES.BOT_TYPE, label: botType });
91
+ nodes.push({ id: `combo_${fp}`, type: NODE_TYPES.COMBO, label: fp });
92
+ return nodes;
93
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Wilson Score lower bound — statistically sound confidence interval for win rates.
3
+ * Penalizes small sample sizes (wide confidence intervals).
4
+ *
5
+ * @param wins - Number of wins
6
+ * @param total - Total number of trades
7
+ * @param z - Z-score (1.64=90%, 1.80, 1.96=95%, 2.33=99%)
8
+ * @returns Lower bound of confidence interval (0-1)
9
+ */
10
+ export function wilsonScore(wins: number, total: number, z: number = 1.96): number {
11
+ if (total === 0) return 0;
12
+ const p = wins / total;
13
+ const denominator = 1 + (z * z) / total;
14
+ const centre = p + (z * z) / (2 * total);
15
+ const spread = z * Math.sqrt((p * (1 - p) + (z * z) / (4 * total)) / total);
16
+ return Math.max(0, (centre - spread) / denominator);
17
+ }
@@ -0,0 +1,19 @@
1
+ import type { SynapseRecord } from '../db/repositories/synapse.repository.js';
2
+
3
+ /**
4
+ * Apply temporal decay to a synapse.
5
+ * Formula: new_weight = max(0.01, weight * 0.5^(age/halfLifeMs))
6
+ */
7
+ export function decaySynapse(synapse: Omit<SynapseRecord, 'created_at'>, halfLifeMs: number): boolean {
8
+ const now = Date.now();
9
+ const lastActivated = new Date(synapse.last_activated).getTime();
10
+ const age = now - lastActivated;
11
+
12
+ if (age > halfLifeMs) {
13
+ const periods = age / halfLifeMs;
14
+ const oldWeight = synapse.weight;
15
+ synapse.weight = Math.max(0.01, synapse.weight * Math.pow(0.5, periods));
16
+ return synapse.weight !== oldWeight;
17
+ }
18
+ return false;
19
+ }
@@ -0,0 +1,23 @@
1
+ import type { SynapseRecord } from '../db/repositories/synapse.repository.js';
2
+
3
+ /**
4
+ * Hebbian strengthen — "neurons that fire together wire together"
5
+ * Asymptotic approach to 1.0: weight += (1 - weight) * learningRate
6
+ */
7
+ export function strengthen(synapse: Omit<SynapseRecord, 'created_at'>, learningRate: number): void {
8
+ synapse.wins++;
9
+ synapse.activations++;
10
+ synapse.weight += (1.0 - synapse.weight) * learningRate;
11
+ synapse.last_activated = new Date().toISOString();
12
+ }
13
+
14
+ /**
15
+ * Hebbian weaken — multiplicative decay on loss
16
+ * weight *= weakenPenalty (e.g. 0.7)
17
+ */
18
+ export function weaken(synapse: Omit<SynapseRecord, 'created_at'>, weakenPenalty: number): void {
19
+ synapse.losses++;
20
+ synapse.activations++;
21
+ synapse.weight *= weakenPenalty;
22
+ synapse.last_activated = new Date().toISOString();
23
+ }
@@ -0,0 +1,112 @@
1
+ import type { SynapseRepository, SynapseRecord } from '../db/repositories/synapse.repository.js';
2
+ import type { CalibrationConfig } from '../types/config.types.js';
3
+ import { strengthen, weaken } from './hebbian.js';
4
+ import { decaySynapse } from './decay.js';
5
+ import { getLogger } from '../utils/logger.js';
6
+
7
+ export class SynapseManager {
8
+ private cache: Map<string, Omit<SynapseRecord, 'created_at'>> = new Map();
9
+ private logger = getLogger();
10
+
11
+ constructor(
12
+ private repo: SynapseRepository,
13
+ private cal: CalibrationConfig,
14
+ ) {
15
+ this.loadCache();
16
+ }
17
+
18
+ private loadCache(): void {
19
+ const all = this.repo.getAll();
20
+ for (const syn of all) {
21
+ this.cache.set(syn.id, syn);
22
+ }
23
+ this.logger.info(`Synapse cache loaded: ${this.cache.size} synapses`);
24
+ }
25
+
26
+ updateCalibration(cal: CalibrationConfig): void {
27
+ this.cal = cal;
28
+ }
29
+
30
+ getOrCreate(fingerprint: string): Omit<SynapseRecord, 'created_at'> {
31
+ const id = `syn_${fingerprint}`;
32
+ let synapse = this.cache.get(id);
33
+ if (!synapse) {
34
+ synapse = {
35
+ id,
36
+ fingerprint,
37
+ weight: 0.5,
38
+ wins: 0,
39
+ losses: 0,
40
+ activations: 0,
41
+ total_profit: 0,
42
+ last_activated: new Date().toISOString(),
43
+ };
44
+ this.cache.set(id, synapse);
45
+ }
46
+ return synapse;
47
+ }
48
+
49
+ get(id: string): Omit<SynapseRecord, 'created_at'> | undefined {
50
+ return this.cache.get(id);
51
+ }
52
+
53
+ getByFingerprint(fingerprint: string): Omit<SynapseRecord, 'created_at'> | undefined {
54
+ return this.cache.get(`syn_${fingerprint}`);
55
+ }
56
+
57
+ recordWin(fingerprint: string, profitPct: number): Omit<SynapseRecord, 'created_at'> {
58
+ const synapse = this.getOrCreate(fingerprint);
59
+ synapse.total_profit += profitPct;
60
+ strengthen(synapse, this.cal.learningRate);
61
+ this.repo.upsert(synapse);
62
+ return synapse;
63
+ }
64
+
65
+ recordLoss(fingerprint: string, profitPct: number): Omit<SynapseRecord, 'created_at'> {
66
+ const synapse = this.getOrCreate(fingerprint);
67
+ synapse.total_profit += profitPct;
68
+ weaken(synapse, this.cal.weakenPenalty);
69
+ this.repo.upsert(synapse);
70
+ return synapse;
71
+ }
72
+
73
+ runDecay(): number {
74
+ const halfLifeMs = this.cal.decayHalfLifeDays * 86400000;
75
+ let decayed = 0;
76
+ for (const synapse of this.cache.values()) {
77
+ if (decaySynapse(synapse, halfLifeMs)) {
78
+ this.repo.upsert(synapse);
79
+ decayed++;
80
+ }
81
+ }
82
+ if (decayed > 0) {
83
+ this.logger.info(`Decayed ${decayed} synapses`);
84
+ }
85
+ return decayed;
86
+ }
87
+
88
+ getAll(): Omit<SynapseRecord, 'created_at'>[] {
89
+ return Array.from(this.cache.values());
90
+ }
91
+
92
+ count(): number {
93
+ return this.cache.size;
94
+ }
95
+
96
+ getStrongest(limit: number = 20): Omit<SynapseRecord, 'created_at'>[] {
97
+ return Array.from(this.cache.values())
98
+ .sort((a, b) => b.weight - a.weight)
99
+ .slice(0, limit);
100
+ }
101
+
102
+ getAvgWeight(): number {
103
+ if (this.cache.size === 0) return 0;
104
+ let sum = 0;
105
+ for (const syn of this.cache.values()) sum += syn.weight;
106
+ return sum / this.cache.size;
107
+ }
108
+
109
+ clear(): void {
110
+ this.cache.clear();
111
+ }
112
+ }
@@ -0,0 +1,285 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import type Database from 'better-sqlite3';
4
+ import { loadConfig } from './config.js';
5
+ import type { TradingBrainConfig } from './types/config.types.js';
6
+ import { createLogger, getLogger } from './utils/logger.js';
7
+ import { getEventBus } from './utils/events.js';
8
+ import { createConnection } from './db/connection.js';
9
+ import { runMigrations } from './db/migrations/index.js';
10
+
11
+ // Repositories
12
+ import { TradeRepository } from './db/repositories/trade.repository.js';
13
+ import { SignalRepository } from './db/repositories/signal.repository.js';
14
+ import { SynapseRepository } from './db/repositories/synapse.repository.js';
15
+ import { GraphRepository } from './db/repositories/graph.repository.js';
16
+ import { RuleRepository } from './db/repositories/rule.repository.js';
17
+ import { ChainRepository } from './db/repositories/chain.repository.js';
18
+ import { InsightRepository } from './db/repositories/insight.repository.js';
19
+ import { CalibrationRepository } from './db/repositories/calibration.repository.js';
20
+
21
+ // Graph
22
+ import { WeightedGraph } from './graph/weighted-graph.js';
23
+
24
+ // Synapses
25
+ import { SynapseManager } from './synapses/synapse-manager.js';
26
+
27
+ // Services
28
+ import { TradeService } from './services/trade.service.js';
29
+ import { SignalService } from './services/signal.service.js';
30
+ import { StrategyService } from './services/strategy.service.js';
31
+ import { SynapseService } from './services/synapse.service.js';
32
+ import { AnalyticsService } from './services/analytics.service.js';
33
+ import { InsightService } from './services/insight.service.js';
34
+
35
+ // Engines
36
+ import { LearningEngine } from './learning/learning-engine.js';
37
+ import { ResearchEngine } from './research/research-engine.js';
38
+
39
+ // IPC
40
+ import { IpcRouter, type Services } from './ipc/router.js';
41
+ import { IpcServer } from './ipc/server.js';
42
+
43
+ // API & MCP HTTP
44
+ import { ApiServer } from './api/server.js';
45
+ import { McpHttpServer } from './mcp/http-server.js';
46
+
47
+ export class TradingCore {
48
+ private db: Database.Database | null = null;
49
+ private ipcServer: IpcServer | null = null;
50
+ private apiServer: ApiServer | null = null;
51
+ private mcpHttpServer: McpHttpServer | null = null;
52
+ private learningEngine: LearningEngine | null = null;
53
+ private researchEngine: ResearchEngine | null = null;
54
+ private config: TradingBrainConfig | null = null;
55
+ private configPath?: string;
56
+ private restarting = false;
57
+
58
+ start(configPath?: string): void {
59
+ this.configPath = configPath;
60
+
61
+ // 1. Config
62
+ this.config = loadConfig(configPath);
63
+ const config = this.config;
64
+
65
+ // 2. Ensure data dir
66
+ fs.mkdirSync(path.dirname(config.dbPath), { recursive: true });
67
+
68
+ // 3. Logger
69
+ createLogger({
70
+ level: config.log.level,
71
+ file: config.log.file,
72
+ maxSize: config.log.maxSize,
73
+ maxFiles: config.log.maxFiles,
74
+ });
75
+ const logger = getLogger();
76
+
77
+ // 4. Database
78
+ this.db = createConnection(config.dbPath);
79
+ runMigrations(this.db);
80
+ logger.info(`Database initialized: ${config.dbPath}`);
81
+
82
+ // 5. Repositories
83
+ const tradeRepo = new TradeRepository(this.db);
84
+ const signalRepo = new SignalRepository(this.db);
85
+ const synapseRepo = new SynapseRepository(this.db);
86
+ const graphRepo = new GraphRepository(this.db);
87
+ const ruleRepo = new RuleRepository(this.db);
88
+ const chainRepo = new ChainRepository(this.db);
89
+ const insightRepo = new InsightRepository(this.db);
90
+ const calibrationRepo = new CalibrationRepository(this.db);
91
+
92
+ // 6. Synapse Manager
93
+ const synapseManager = new SynapseManager(synapseRepo, config.calibration);
94
+
95
+ // 7. Weighted Graph (load from DB)
96
+ const graph = new WeightedGraph();
97
+ const graphNodes = graphRepo.getAllNodes();
98
+ for (const node of graphNodes) {
99
+ graph.addNode(node.id, node.type, node.label);
100
+ }
101
+ const graphEdges = graphRepo.getAllEdges();
102
+ for (const edge of graphEdges) {
103
+ graph.addEdge(edge.source, edge.target, edge.weight);
104
+ }
105
+ logger.info(`Graph loaded: ${graphNodes.length} nodes, ${graphEdges.length} edges`);
106
+
107
+ // 8. Calibration (load current or use defaults)
108
+ const cal = calibrationRepo.get() ?? config.calibration;
109
+ const tradeCount = () => tradeRepo.count();
110
+
111
+ // 9. Services
112
+ const services: Services = {
113
+ trade: new TradeService(tradeRepo, signalRepo, chainRepo, synapseManager, graph, cal, config.learning),
114
+ signal: new SignalService(synapseManager, graph, cal, tradeCount),
115
+ strategy: new StrategyService(synapseManager, graph, cal, tradeCount),
116
+ synapse: new SynapseService(synapseManager, graph),
117
+ analytics: new AnalyticsService(tradeRepo, ruleRepo, chainRepo, insightRepo, synapseManager, graph),
118
+ insight: new InsightService(insightRepo),
119
+ ruleRepo,
120
+ chainRepo,
121
+ calRepo: calibrationRepo,
122
+ };
123
+
124
+ // 10. Learning Engine
125
+ this.learningEngine = new LearningEngine(
126
+ config.learning,
127
+ cal,
128
+ tradeRepo,
129
+ ruleRepo,
130
+ chainRepo,
131
+ calibrationRepo,
132
+ synapseManager,
133
+ graph,
134
+ );
135
+ this.learningEngine.start();
136
+ logger.info(`Learning engine started (interval: ${config.learning.intervalMs}ms)`);
137
+
138
+ // 11. Research Engine
139
+ this.researchEngine = new ResearchEngine(
140
+ config.research,
141
+ tradeRepo,
142
+ insightRepo,
143
+ );
144
+ this.researchEngine.start();
145
+ logger.info(`Research engine started (interval: ${config.research.intervalMs}ms)`);
146
+
147
+ // Expose engines to IPC
148
+ services.learning = this.learningEngine;
149
+ services.research = this.researchEngine;
150
+
151
+ // 12. IPC Server
152
+ const router = new IpcRouter(services);
153
+ this.ipcServer = new IpcServer(router, config.ipc.pipeName);
154
+ this.ipcServer.start();
155
+
156
+ // 13. REST API Server
157
+ if (config.api.enabled) {
158
+ this.apiServer = new ApiServer({
159
+ port: config.api.port,
160
+ router,
161
+ apiKey: config.api.apiKey,
162
+ });
163
+ this.apiServer.start();
164
+ logger.info(`REST API enabled on port ${config.api.port}`);
165
+ }
166
+
167
+ // 14. MCP HTTP Server (SSE transport for Cursor, Windsurf, Cline, Continue)
168
+ if (config.mcpHttp.enabled) {
169
+ this.mcpHttpServer = new McpHttpServer(config.mcpHttp.port, router);
170
+ this.mcpHttpServer.start();
171
+ logger.info(`MCP HTTP (SSE) enabled on port ${config.mcpHttp.port}`);
172
+ }
173
+
174
+ // 15. Event listeners (synapse wiring)
175
+ this.setupEventListeners(synapseManager);
176
+
177
+ // 16. PID file
178
+ const pidPath = path.join(path.dirname(config.dbPath), 'trading-brain.pid');
179
+ fs.writeFileSync(pidPath, String(process.pid));
180
+
181
+ // 17. Graceful shutdown
182
+ process.on('SIGINT', () => this.stop());
183
+ process.on('SIGTERM', () => this.stop());
184
+
185
+ // 18. Crash recovery
186
+ process.on('uncaughtException', (err) => {
187
+ logger.error('Uncaught exception — restarting', { error: err.message, stack: err.stack });
188
+ this.logCrash('uncaughtException', err);
189
+ this.restart();
190
+ });
191
+ process.on('unhandledRejection', (reason) => {
192
+ logger.error('Unhandled rejection — restarting', { reason: String(reason) });
193
+ this.logCrash('unhandledRejection', reason instanceof Error ? reason : new Error(String(reason)));
194
+ this.restart();
195
+ });
196
+
197
+ logger.info(`Trading Brain daemon started (PID: ${process.pid})`);
198
+ }
199
+
200
+ private logCrash(type: string, err: Error): void {
201
+ if (!this.config) return;
202
+ const crashLog = path.join(path.dirname(this.config.dbPath), 'crashes.log');
203
+ const entry = `[${new Date().toISOString()}] ${type}: ${err.message}\n${err.stack ?? ''}\n\n`;
204
+ try { fs.appendFileSync(crashLog, entry); } catch { /* best effort */ }
205
+ }
206
+
207
+ private cleanup(): void {
208
+ this.researchEngine?.stop();
209
+ this.learningEngine?.stop();
210
+ this.mcpHttpServer?.stop();
211
+ this.apiServer?.stop();
212
+ this.ipcServer?.stop();
213
+ this.db?.close();
214
+
215
+ this.db = null;
216
+ this.ipcServer = null;
217
+ this.apiServer = null;
218
+ this.mcpHttpServer = null;
219
+ this.learningEngine = null;
220
+ this.researchEngine = null;
221
+ }
222
+
223
+ restart(): void {
224
+ if (this.restarting) return;
225
+ this.restarting = true;
226
+
227
+ const logger = getLogger();
228
+ logger.info('Restarting Trading Brain daemon...');
229
+
230
+ try { this.cleanup(); } catch { /* best effort cleanup */ }
231
+
232
+ this.restarting = false;
233
+ this.start(this.configPath);
234
+ }
235
+
236
+ stop(): void {
237
+ const logger = getLogger();
238
+ logger.info('Shutting down...');
239
+
240
+ this.cleanup();
241
+
242
+ // Remove PID file
243
+ if (this.config) {
244
+ const pidPath = path.join(path.dirname(this.config.dbPath), 'trading-brain.pid');
245
+ try { fs.unlinkSync(pidPath); } catch { /* ignore */ }
246
+ }
247
+
248
+ logger.info('Trading Brain daemon stopped');
249
+ process.exit(0);
250
+ }
251
+
252
+ private setupEventListeners(_synapseManager: SynapseManager): void {
253
+ const bus = getEventBus();
254
+
255
+ // Trade recorded → log
256
+ bus.on('trade:recorded', ({ tradeId, fingerprint, win }) => {
257
+ getLogger().info(`Trade #${tradeId} recorded: ${fingerprint} (${win ? 'WIN' : 'LOSS'})`);
258
+ });
259
+
260
+ // Synapse updated → log at debug level
261
+ bus.on('synapse:updated', ({ synapseId }) => {
262
+ getLogger().debug(`Synapse updated: ${synapseId}`);
263
+ });
264
+
265
+ // Rule learned → log
266
+ bus.on('rule:learned', ({ ruleId, pattern }) => {
267
+ getLogger().info(`New rule #${ruleId} learned: ${pattern}`);
268
+ });
269
+
270
+ // Chain detected → log
271
+ bus.on('chain:detected', ({ pair, type, length }) => {
272
+ getLogger().info(`Chain: ${pair} ${type} streak (${length})`);
273
+ });
274
+
275
+ // Insight created → log
276
+ bus.on('insight:created', ({ insightId, type }) => {
277
+ getLogger().info(`New insight #${insightId} (${type})`);
278
+ });
279
+
280
+ // Calibration updated → log
281
+ bus.on('calibration:updated', ({ outcomeCount }) => {
282
+ getLogger().info(`Calibration updated (${outcomeCount} outcomes)`);
283
+ });
284
+ }
285
+ }