@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,57 @@
1
+ import type { CalibrationConfig } from '../types/config.types.js';
2
+
3
+ /**
4
+ * Adaptive Calibration — dynamically adjust learning parameters based on data volume.
5
+ * Ported from tradingBrain.js calibrate() function.
6
+ */
7
+ export function calibrate(current: CalibrationConfig, outcomeCount: number, synapseCount: number): CalibrationConfig {
8
+ const cal = { ...current };
9
+
10
+ if (outcomeCount < 20) {
11
+ // Very early: conservative, wide thresholds
12
+ cal.learningRate = 0.08;
13
+ cal.weakenPenalty = 0.8;
14
+ cal.patternMinSamples = 5;
15
+ cal.patternWilsonThreshold = 0.3;
16
+ cal.wilsonZ = 1.64; // 90% CI
17
+ cal.minActivationsForWeight = 2;
18
+ cal.minOutcomesForWeights = 3;
19
+ } else if (outcomeCount < 100) {
20
+ // Growing: moderate
21
+ cal.learningRate = 0.12;
22
+ cal.weakenPenalty = 0.75;
23
+ cal.patternMinSamples = 8;
24
+ cal.patternWilsonThreshold = 0.4;
25
+ cal.wilsonZ = 1.80;
26
+ cal.minActivationsForWeight = 3;
27
+ cal.minOutcomesForWeights = 5;
28
+ } else if (outcomeCount < 500) {
29
+ // Mature: standard
30
+ cal.learningRate = 0.15;
31
+ cal.weakenPenalty = 0.7;
32
+ cal.patternMinSamples = 10;
33
+ cal.patternWilsonThreshold = 0.5;
34
+ cal.wilsonZ = 1.96; // 95% CI
35
+ cal.minActivationsForWeight = 3;
36
+ cal.minOutcomesForWeights = 5;
37
+ } else {
38
+ // Large dataset: high confidence, fine-grained
39
+ cal.learningRate = 0.10;
40
+ cal.weakenPenalty = 0.75;
41
+ cal.patternMinSamples = 15;
42
+ cal.patternWilsonThreshold = 0.55;
43
+ cal.wilsonZ = 2.33; // 99% CI
44
+ cal.minActivationsForWeight = 5;
45
+ cal.minOutcomesForWeights = 8;
46
+ cal.patternExtractionInterval = 30;
47
+ }
48
+
49
+ // Adjust decay based on synapse density
50
+ if (synapseCount > 100) {
51
+ cal.decayHalfLifeDays = 10; // faster cleanup
52
+ } else if (synapseCount < 10) {
53
+ cal.decayHalfLifeDays = 21; // preserve early knowledge
54
+ }
55
+
56
+ return cal;
57
+ }
@@ -0,0 +1,43 @@
1
+ import type { TradeRecord } from '../db/repositories/trade.repository.js';
2
+
3
+ export interface DetectedChain {
4
+ pair: string;
5
+ type: 'winning_streak' | 'losing_streak';
6
+ length: number;
7
+ fingerprints: string[];
8
+ total_profit: number;
9
+ }
10
+
11
+ /**
12
+ * Detect winning/losing streaks from recent trade outcomes.
13
+ * Ported from tradingBrain.js _detectChain().
14
+ *
15
+ * @param recentTrades - Last N trades (should be ~10)
16
+ * @param latestTrade - The most recent trade
17
+ * @param minLength - Minimum consecutive trades for a chain (default: 3)
18
+ */
19
+ export function detectChain(
20
+ recentTrades: TradeRecord[],
21
+ latestTrade: TradeRecord,
22
+ minLength: number = 3,
23
+ ): DetectedChain | null {
24
+ if (recentTrades.length < minLength) return null;
25
+
26
+ const recent = recentTrades.slice(-5);
27
+ const samePair = recent.filter(o => o.pair === latestTrade.pair);
28
+ if (samePair.length < minLength) return null;
29
+
30
+ const lastThree = samePair.slice(-minLength);
31
+ const allLosses = lastThree.every(o => o.win === 0);
32
+ const allWins = lastThree.every(o => o.win === 1);
33
+
34
+ if (!allLosses && !allWins) return null;
35
+
36
+ return {
37
+ pair: latestTrade.pair,
38
+ type: allLosses ? 'losing_streak' : 'winning_streak',
39
+ length: lastThree.length,
40
+ fingerprints: lastThree.map(o => o.fingerprint),
41
+ total_profit: lastThree.reduce((s, o) => s + o.profit_pct, 0),
42
+ };
43
+ }
@@ -0,0 +1,94 @@
1
+ import type { TradeRepository } from '../db/repositories/trade.repository.js';
2
+ import type { RuleRepository } from '../db/repositories/rule.repository.js';
3
+ import type { ChainRepository } from '../db/repositories/chain.repository.js';
4
+ import type { CalibrationRepository } from '../db/repositories/calibration.repository.js';
5
+ import type { SynapseManager } from '../synapses/synapse-manager.js';
6
+ import type { WeightedGraph } from '../graph/weighted-graph.js';
7
+ import type { CalibrationConfig, LearningConfig } from '../types/config.types.js';
8
+ import { extractPatterns } from './pattern-extractor.js';
9
+ import { calibrate } from './calibrator.js';
10
+ import { getLogger } from '../utils/logger.js';
11
+ import { getEventBus } from '../utils/events.js';
12
+
13
+ export class LearningEngine {
14
+ private timer: ReturnType<typeof setInterval> | null = null;
15
+ private lastPatternExtraction = 0;
16
+ private lastDecay = 0;
17
+ private logger = getLogger();
18
+
19
+ constructor(
20
+ private learningConfig: LearningConfig,
21
+ private cal: CalibrationConfig,
22
+ private tradeRepo: TradeRepository,
23
+ private ruleRepo: RuleRepository,
24
+ private chainRepo: ChainRepository,
25
+ private calRepo: CalibrationRepository,
26
+ private synapseManager: SynapseManager,
27
+ private graph: WeightedGraph,
28
+ ) {}
29
+
30
+ start(): void {
31
+ this.timer = setInterval(() => this.runCycle(), this.learningConfig.intervalMs);
32
+ this.logger.info(`Learning engine started (interval: ${this.learningConfig.intervalMs}ms)`);
33
+ }
34
+
35
+ stop(): void {
36
+ if (this.timer) {
37
+ clearInterval(this.timer);
38
+ this.timer = null;
39
+ }
40
+ }
41
+
42
+ getCalibration(): CalibrationConfig {
43
+ return { ...this.cal };
44
+ }
45
+
46
+ runCycle(): void {
47
+ const bus = getEventBus();
48
+ const outcomeCount = this.tradeRepo.count();
49
+
50
+ // Pattern extraction
51
+ if (outcomeCount - this.lastPatternExtraction >= this.cal.patternExtractionInterval) {
52
+ const trades = this.tradeRepo.getAll();
53
+ const rules = extractPatterns(trades, this.cal);
54
+ if (rules.length > 0) {
55
+ this.ruleRepo.replaceAll(rules);
56
+ this.lastPatternExtraction = outcomeCount;
57
+ this.logger.info(`Extracted ${rules.length} rules from ${trades.length} trades`);
58
+ bus.emit('patterns:extracted', { ruleCount: rules.length });
59
+ }
60
+ }
61
+
62
+ // Recalibrate every 25 trades
63
+ if (outcomeCount > 0 && outcomeCount % 25 === 0) {
64
+ const newCal = calibrate(this.cal, outcomeCount, this.synapseManager.count());
65
+ this.cal = newCal;
66
+ this.synapseManager.updateCalibration(newCal);
67
+ this.calRepo.save(newCal);
68
+ this.logger.info(`Recalibrated — lr: ${newCal.learningRate}, z: ${newCal.wilsonZ}, halfLife: ${newCal.decayHalfLifeDays}d`);
69
+ bus.emit('calibration:updated', { outcomeCount, learningRate: newCal.learningRate });
70
+ }
71
+
72
+ // Daily decay
73
+ const now = Date.now();
74
+ const dayMs = 86400000;
75
+ if (now - this.lastDecay > dayMs) {
76
+ const halfLifeMs = this.cal.decayHalfLifeDays * dayMs;
77
+ const synDecayed = this.synapseManager.runDecay();
78
+ this.graph.decayEdges(halfLifeMs);
79
+ this.lastDecay = now;
80
+ this.logger.info(`Decay applied — ${synDecayed} synapses, ${this.graph.getEdgeCount()} edges`);
81
+ bus.emit('decay:applied', { synapseCount: synDecayed, edgeCount: this.graph.getEdgeCount() });
82
+ }
83
+
84
+ // Prune old chains
85
+ this.chainRepo.pruneOldest(this.learningConfig.maxChains);
86
+ }
87
+
88
+ /** Manual trigger for a full learning cycle */
89
+ runManual(): { rules: number; calibration: CalibrationConfig } {
90
+ this.runCycle();
91
+ const rules = this.ruleRepo.getAll();
92
+ return { rules: rules.length, calibration: this.getCalibration() };
93
+ }
94
+ }
@@ -0,0 +1,53 @@
1
+ import type { TradeRecord } from '../db/repositories/trade.repository.js';
2
+ import { fingerprintSimilarity } from '../signals/fingerprint.js';
3
+ import { wilsonScore } from '../signals/wilson-score.js';
4
+ import type { CalibrationConfig } from '../types/config.types.js';
5
+
6
+ export interface ExtractedRule {
7
+ pattern: string;
8
+ confidence: number;
9
+ sample_count: number;
10
+ win_rate: number;
11
+ avg_profit: number;
12
+ }
13
+
14
+ /**
15
+ * Extract trading rules from outcome data using fingerprint grouping + Wilson Score.
16
+ * Ported from tradingBrain.js _extractPatterns().
17
+ */
18
+ export function extractPatterns(trades: TradeRecord[], cal: CalibrationConfig): ExtractedRule[] {
19
+ if (trades.length < cal.patternMinSamples) return [];
20
+
21
+ // Group by similar fingerprints (threshold: 0.7)
22
+ const groups: Record<string, TradeRecord[]> = {};
23
+ for (const trade of trades) {
24
+ let assigned = false;
25
+ for (const key of Object.keys(groups)) {
26
+ if (fingerprintSimilarity(trade.fingerprint, key) >= 0.7) {
27
+ groups[key]!.push(trade);
28
+ assigned = true;
29
+ break;
30
+ }
31
+ }
32
+ if (!assigned) {
33
+ groups[trade.fingerprint] = [trade];
34
+ }
35
+ }
36
+
37
+ const rules: ExtractedRule[] = [];
38
+ for (const [pattern, group] of Object.entries(groups)) {
39
+ if (group.length < cal.patternMinSamples) continue;
40
+
41
+ const wins = group.filter(o => o.win === 1).length;
42
+ const total = group.length;
43
+ const winRate = wins / total;
44
+ const confidence = wilsonScore(wins, total, cal.wilsonZ);
45
+
46
+ if (confidence > cal.patternWilsonThreshold) {
47
+ const avgProfit = group.reduce((s, o) => s + o.profit_pct, 0) / total;
48
+ rules.push({ pattern, confidence, sample_count: total, win_rate: winRate, avg_profit: avgProfit });
49
+ }
50
+ }
51
+
52
+ return rules.sort((a, b) => b.confidence - a.confidence);
53
+ }
@@ -0,0 +1,118 @@
1
+ import http from 'node:http';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
5
+ import { getLogger } from '../utils/logger.js';
6
+ import type { IpcRouter } from '../ipc/router.js';
7
+ import { registerToolsDirect } from './tools.js';
8
+
9
+ export class McpHttpServer {
10
+ private server: http.Server | null = null;
11
+ private transports = new Map<string, SSEServerTransport>();
12
+ private logger = getLogger();
13
+
14
+ constructor(
15
+ private port: number,
16
+ private router: IpcRouter,
17
+ ) {}
18
+
19
+ start(): void {
20
+ this.server = http.createServer((req, res) => {
21
+ res.setHeader('Access-Control-Allow-Origin', '*');
22
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
23
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
24
+
25
+ if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
26
+
27
+ const url = new URL(req.url ?? '/', `http://localhost:${this.port}`);
28
+
29
+ if (url.pathname === '/sse' && req.method === 'GET') { this.handleSSE(res); return; }
30
+ if (url.pathname === '/messages' && req.method === 'POST') { this.handleMessage(req, res, url); return; }
31
+
32
+ if (url.pathname === '/' && req.method === 'GET') {
33
+ res.writeHead(200, { 'Content-Type': 'application/json' });
34
+ res.end(JSON.stringify({
35
+ name: 'trading-brain',
36
+ version: '1.0.0',
37
+ protocol: 'MCP',
38
+ transport: 'sse',
39
+ endpoints: { sse: '/sse', messages: '/messages' },
40
+ clients: this.transports.size,
41
+ }));
42
+ return;
43
+ }
44
+
45
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
46
+ res.end('Not Found');
47
+ });
48
+
49
+ this.server.listen(this.port, () => {
50
+ this.logger.info(`MCP HTTP server (SSE) started on http://localhost:${this.port}`);
51
+ });
52
+ }
53
+
54
+ stop(): void {
55
+ for (const transport of this.transports.values()) {
56
+ try { transport.close?.(); } catch { /* ignore */ }
57
+ }
58
+ this.transports.clear();
59
+ this.server?.close();
60
+ this.server = null;
61
+ this.logger.info('MCP HTTP server stopped');
62
+ }
63
+
64
+ getClientCount(): number {
65
+ return this.transports.size;
66
+ }
67
+
68
+ private async handleSSE(res: http.ServerResponse): Promise<void> {
69
+ try {
70
+ const transport = new SSEServerTransport('/messages', res);
71
+ const sessionId = transport.sessionId ?? randomUUID();
72
+ this.transports.set(sessionId, transport);
73
+
74
+ const server = new McpServer({ name: 'trading-brain', version: '1.0.0' });
75
+ registerToolsDirect(server, this.router);
76
+
77
+ res.on('close', () => {
78
+ this.transports.delete(sessionId);
79
+ this.logger.debug(`MCP SSE client disconnected: ${sessionId}`);
80
+ });
81
+
82
+ await server.connect(transport);
83
+ this.logger.info(`MCP SSE client connected: ${sessionId}`);
84
+ } catch (err) {
85
+ this.logger.error('MCP SSE connection error:', err);
86
+ if (!res.headersSent) {
87
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
88
+ res.end('Internal Server Error');
89
+ }
90
+ }
91
+ }
92
+
93
+ private async handleMessage(req: http.IncomingMessage, res: http.ServerResponse, url: URL): Promise<void> {
94
+ try {
95
+ const sessionId = url.searchParams.get('sessionId');
96
+ if (!sessionId) {
97
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
98
+ res.end('Missing sessionId parameter');
99
+ return;
100
+ }
101
+
102
+ const transport = this.transports.get(sessionId);
103
+ if (!transport) {
104
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
105
+ res.end('Session not found. Connect to /sse first.');
106
+ return;
107
+ }
108
+
109
+ await transport.handlePostMessage(req, res);
110
+ } catch (err) {
111
+ this.logger.error('MCP message error:', err);
112
+ if (!res.headersSent) {
113
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
114
+ res.end('Internal Server Error');
115
+ }
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,72 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { spawn } from 'node:child_process';
4
+ import path from 'node:path';
5
+ import { IpcClient } from '../ipc/client.js';
6
+ import { getPipeName } from '../utils/paths.js';
7
+ import { registerTools } from './tools.js';
8
+
9
+ function spawnDaemon(): void {
10
+ const entryPoint = path.resolve(import.meta.dirname, '../index.js');
11
+ const child = spawn(process.execPath, [
12
+ '--import', 'tsx',
13
+ entryPoint, 'daemon',
14
+ ], {
15
+ detached: true,
16
+ stdio: 'ignore',
17
+ cwd: path.resolve(import.meta.dirname, '../..'),
18
+ });
19
+ child.unref();
20
+ process.stderr.write(`Trading Brain: Auto-started daemon (PID: ${child.pid})\n`);
21
+ }
22
+
23
+ async function connectWithRetry(ipc: IpcClient, retries: number, delayMs: number): Promise<void> {
24
+ for (let i = 0; i < retries; i++) {
25
+ try {
26
+ await ipc.connect();
27
+ return;
28
+ } catch {
29
+ if (i < retries - 1) {
30
+ await new Promise(r => setTimeout(r, delayMs));
31
+ }
32
+ }
33
+ }
34
+ throw new Error('Could not connect to daemon after retries');
35
+ }
36
+
37
+ export async function startMcpServer(): Promise<void> {
38
+ const server = new McpServer({
39
+ name: 'trading-brain',
40
+ version: '1.0.0',
41
+ });
42
+
43
+ const ipc = new IpcClient(getPipeName());
44
+
45
+ try {
46
+ await ipc.connect();
47
+ } catch {
48
+ process.stderr.write('Trading Brain: Daemon not running, starting automatically...\n');
49
+ spawnDaemon();
50
+ try {
51
+ await connectWithRetry(ipc, 10, 500);
52
+ } catch {
53
+ process.stderr.write('Trading Brain: Could not connect to daemon after auto-start. Check logs.\n');
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ registerTools(server, ipc);
59
+
60
+ const transport = new StdioServerTransport();
61
+ await server.connect(transport);
62
+
63
+ process.on('SIGINT', () => {
64
+ ipc.disconnect();
65
+ process.exit(0);
66
+ });
67
+
68
+ process.on('SIGTERM', () => {
69
+ ipc.disconnect();
70
+ process.exit(0);
71
+ });
72
+ }
@@ -0,0 +1,256 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import type { IpcClient } from '../ipc/client.js';
4
+ import type { IpcRouter } from '../ipc/router.js';
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ type AnyResult = any;
8
+ type BrainCall = (method: string, params?: unknown) => Promise<unknown> | unknown;
9
+
10
+ function textResult(data: unknown): { content: Array<{ type: 'text'; text: string }> } {
11
+ const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
12
+ return { content: [{ type: 'text' as const, text }] };
13
+ }
14
+
15
+ /** Register tools using IPC client (for stdio MCP transport) */
16
+ export function registerTools(server: McpServer, ipc: IpcClient): void {
17
+ registerToolsWithCaller(server, (method, params) => ipc.request(method, params));
18
+ }
19
+
20
+ /** Register tools using router directly (for HTTP MCP transport inside daemon) */
21
+ export function registerToolsDirect(server: McpServer, router: IpcRouter): void {
22
+ registerToolsWithCaller(server, (method, params) => router.handle(method, params));
23
+ }
24
+
25
+ function registerToolsWithCaller(server: McpServer, call: BrainCall): void {
26
+
27
+ // 1. trading_record_outcome
28
+ server.tool(
29
+ 'trading_record_outcome',
30
+ 'Record a trade outcome. Main entry point for the learning loop — updates synapses, graph, chains, and triggers pattern extraction.',
31
+ {
32
+ pair: z.string().describe('Trading pair (e.g. BTC/USDT)'),
33
+ bot_type: z.string().describe('Bot type (e.g. DCA, Grid, SmartTrader)'),
34
+ profit_pct: z.number().describe('Profit percentage of the trade'),
35
+ win: z.boolean().describe('Whether the trade was profitable'),
36
+ rsi14: z.number().optional().describe('RSI-14 value at entry'),
37
+ macd: z.number().optional().describe('MACD value at entry'),
38
+ trend_score: z.number().optional().describe('Trend score at entry'),
39
+ volatility: z.number().optional().describe('Volatility at entry'),
40
+ regime: z.string().optional().describe('Market regime (e.g. bullish_trend, ranging)'),
41
+ },
42
+ async (params) => {
43
+ const result: AnyResult = await call('trade.recordOutcome', {
44
+ signals: { rsi14: params.rsi14, macd: params.macd, trendScore: params.trend_score, volatility: params.volatility },
45
+ regime: params.regime,
46
+ profitPct: params.profit_pct,
47
+ win: params.win,
48
+ botType: params.bot_type,
49
+ pair: params.pair,
50
+ });
51
+ return textResult(`Trade #${result.tradeId} recorded (${params.win ? 'WIN' : 'LOSS'}, ${params.profit_pct.toFixed(2)}%). Fingerprint: ${result.fingerprint}. Synapse weight: ${result.synapseWeight.toFixed(3)}`);
52
+ },
53
+ );
54
+
55
+ // 2. trading_signal_weights
56
+ server.tool(
57
+ 'trading_signal_weights',
58
+ 'Get brain-weighted signal strengths based on learned experience. Returns adjusted weights for each signal type.',
59
+ {
60
+ rsi14: z.number().optional().describe('RSI-14 value'),
61
+ macd: z.number().optional().describe('MACD value'),
62
+ trend_score: z.number().optional().describe('Trend score'),
63
+ volatility: z.number().optional().describe('Volatility'),
64
+ regime: z.string().optional().describe('Market regime'),
65
+ },
66
+ async (params) => {
67
+ const result = await call('signal.weights', {
68
+ signals: { rsi14: params.rsi14, macd: params.macd, trendScore: params.trend_score, volatility: params.volatility },
69
+ regime: params.regime,
70
+ });
71
+ return textResult(result);
72
+ },
73
+ );
74
+
75
+ // 3. trading_signal_confidence
76
+ server.tool(
77
+ 'trading_signal_confidence',
78
+ 'Get Wilson Score confidence for a signal pattern. Returns 0-1 confidence based on historical win rate.',
79
+ {
80
+ rsi14: z.number().optional().describe('RSI-14 value'),
81
+ macd: z.number().optional().describe('MACD value'),
82
+ trend_score: z.number().optional().describe('Trend score'),
83
+ volatility: z.number().optional().describe('Volatility'),
84
+ regime: z.string().optional().describe('Market regime'),
85
+ },
86
+ async (params) => {
87
+ const confidence = await call('signal.confidence', {
88
+ signals: { rsi14: params.rsi14, macd: params.macd, trendScore: params.trend_score, volatility: params.volatility },
89
+ regime: params.regime,
90
+ });
91
+ return textResult(`Confidence: ${((confidence as number) * 100).toFixed(1)}%`);
92
+ },
93
+ );
94
+
95
+ // 4. trading_dca_multiplier
96
+ server.tool(
97
+ 'trading_dca_multiplier',
98
+ 'Get brain-recommended DCA position size multiplier based on regime success history.',
99
+ {
100
+ regime: z.string().describe('Market regime'),
101
+ rsi: z.number().describe('Current RSI value'),
102
+ volatility: z.number().describe('Current volatility'),
103
+ },
104
+ async (params) => {
105
+ const result = await call('strategy.dcaMultiplier', params);
106
+ return textResult(result);
107
+ },
108
+ );
109
+
110
+ // 5. trading_grid_params
111
+ server.tool(
112
+ 'trading_grid_params',
113
+ 'Get brain-recommended grid spacing parameters based on volatility history.',
114
+ {
115
+ regime: z.string().describe('Market regime'),
116
+ volatility: z.number().describe('Current volatility'),
117
+ pair: z.string().describe('Trading pair'),
118
+ },
119
+ async (params) => {
120
+ const result = await call('strategy.gridParams', params);
121
+ return textResult(result);
122
+ },
123
+ );
124
+
125
+ // 6. trading_explore
126
+ server.tool(
127
+ 'trading_explore',
128
+ 'Explore the brain network using spreading activation. Find related nodes from a starting concept.',
129
+ {
130
+ query: z.string().describe('Node ID, label, or partial match to start exploration from'),
131
+ },
132
+ async (params) => {
133
+ const result = await call('synapse.explore', params);
134
+ return textResult(result);
135
+ },
136
+ );
137
+
138
+ // 7. trading_connections
139
+ server.tool(
140
+ 'trading_connections',
141
+ 'Find the shortest path between two nodes in the brain network.',
142
+ {
143
+ from: z.string().describe('Source node ID'),
144
+ to: z.string().describe('Target node ID'),
145
+ },
146
+ async (params) => {
147
+ const path = await call('synapse.findPath', params);
148
+ if (!path) return textResult('No path found between these nodes.');
149
+ return textResult(`Path: ${(path as string[]).join(' → ')}`);
150
+ },
151
+ );
152
+
153
+ // 8. trading_rules
154
+ server.tool(
155
+ 'trading_rules',
156
+ 'Get all learned trading rules with confidence scores and win rates.',
157
+ {},
158
+ async () => {
159
+ const rules = await call('rule.list', {});
160
+ return textResult(rules);
161
+ },
162
+ );
163
+
164
+ // 9. trading_insights
165
+ server.tool(
166
+ 'trading_insights',
167
+ 'Get research insights (trends, gaps, synergies, performance, regime shifts).',
168
+ {
169
+ type: z.string().optional().describe('Filter by type: trend, gap, synergy, performance, regime_shift'),
170
+ limit: z.number().optional().describe('Max results (default 20)'),
171
+ },
172
+ async (params) => {
173
+ const result = params.type
174
+ ? await call('insight.byType', params)
175
+ : await call('insight.list', params);
176
+ return textResult(result);
177
+ },
178
+ );
179
+
180
+ // 10. trading_chains
181
+ server.tool(
182
+ 'trading_chains',
183
+ 'Get detected trade chains (winning/losing streaks).',
184
+ {
185
+ pair: z.string().optional().describe('Filter by trading pair'),
186
+ limit: z.number().optional().describe('Max results (default 20)'),
187
+ },
188
+ async (params) => {
189
+ const result = params.pair
190
+ ? await call('chain.byPair', params)
191
+ : await call('chain.list', params);
192
+ return textResult(result);
193
+ },
194
+ );
195
+
196
+ // 11. trading_query
197
+ server.tool(
198
+ 'trading_query',
199
+ 'Search trades and signals by fingerprint, pair, or bot type.',
200
+ {
201
+ search: z.string().describe('Search query'),
202
+ limit: z.number().optional().describe('Max results (default 50)'),
203
+ },
204
+ async (params) => {
205
+ const result = await call('trade.query', params);
206
+ return textResult(result);
207
+ },
208
+ );
209
+
210
+ // 12. trading_status
211
+ server.tool(
212
+ 'trading_status',
213
+ 'Get brain stats: trades, synapses, graph size, rules, insights, calibration.',
214
+ {},
215
+ async () => {
216
+ const result = await call('analytics.summary', {});
217
+ return textResult(result);
218
+ },
219
+ );
220
+
221
+ // 13. trading_calibration
222
+ server.tool(
223
+ 'trading_calibration',
224
+ 'Get current adaptive calibration parameters (learning rate, Wilson Z, decay half-life, etc.).',
225
+ {},
226
+ async () => {
227
+ const result = await call('calibration.get', {});
228
+ return textResult(result);
229
+ },
230
+ );
231
+
232
+ // 14. trading_learn
233
+ server.tool(
234
+ 'trading_learn',
235
+ 'Manually trigger a learning cycle (pattern extraction, calibration, decay).',
236
+ {},
237
+ async () => {
238
+ const result = await call('learning.run', {});
239
+ return textResult(result);
240
+ },
241
+ );
242
+
243
+ // 15. trading_reset
244
+ server.tool(
245
+ 'trading_reset',
246
+ 'Reset all trading brain data (trades, synapses, graph, rules, insights, chains, calibration).',
247
+ {
248
+ confirm: z.boolean().describe('Must be true to confirm reset'),
249
+ },
250
+ async (params) => {
251
+ if (!params.confirm) return textResult('Reset cancelled. Pass confirm: true to proceed.');
252
+ const result = await call('reset', {});
253
+ return textResult(result);
254
+ },
255
+ );
256
+ }