@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,223 @@
1
+ import type { TradeRepository, TradeRecord } from '../db/repositories/trade.repository.js';
2
+ import type { InsightRepository } from '../db/repositories/insight.repository.js';
3
+ import type { ResearchConfig } from '../types/config.types.js';
4
+ import { getLogger } from '../utils/logger.js';
5
+ import { getEventBus } from '../utils/events.js';
6
+
7
+ interface NewInsight {
8
+ type: string;
9
+ severity: string;
10
+ title: string;
11
+ description: string;
12
+ data?: unknown;
13
+ }
14
+
15
+ function mode(arr: string[]): string | null {
16
+ if (arr.length === 0) return null;
17
+ const freq: Record<string, number> = {};
18
+ arr.forEach(v => { freq[v] = (freq[v] || 0) + 1; });
19
+ let maxCount = 0, maxVal: string | null = null;
20
+ for (const [val, count] of Object.entries(freq)) {
21
+ if (count > maxCount) { maxCount = count; maxVal = val; }
22
+ }
23
+ return maxVal;
24
+ }
25
+
26
+ export class ResearchEngine {
27
+ private timer: ReturnType<typeof setInterval> | null = null;
28
+ private logger = getLogger();
29
+
30
+ constructor(
31
+ private config: ResearchConfig,
32
+ private tradeRepo: TradeRepository,
33
+ private insightRepo: InsightRepository,
34
+ ) {}
35
+
36
+ start(): void {
37
+ // Initial delay before first run
38
+ setTimeout(() => {
39
+ this.runCycle();
40
+ this.timer = setInterval(() => this.runCycle(), this.config.intervalMs);
41
+ }, this.config.initialDelayMs);
42
+ this.logger.info(`Research engine started (interval: ${this.config.intervalMs}ms)`);
43
+ }
44
+
45
+ stop(): void {
46
+ if (this.timer) {
47
+ clearInterval(this.timer);
48
+ this.timer = null;
49
+ }
50
+ }
51
+
52
+ runCycle(): void {
53
+ const bus = getEventBus();
54
+ const trades = this.tradeRepo.getAll();
55
+ if (trades.length < this.config.minTrades) return;
56
+
57
+ const now = Date.now();
58
+ const insights: NewInsight[] = [];
59
+
60
+ this.detectTrends(trades, now, insights);
61
+ this.detectGaps(trades, insights);
62
+ this.detectSynergies(trades, insights);
63
+ this.detectPerformance(trades, insights);
64
+ this.detectRegimeShifts(trades, now, insights);
65
+
66
+ // Save insights
67
+ for (const ins of insights) {
68
+ const id = this.insightRepo.create(ins);
69
+ bus.emit('insight:created', { insightId: id, type: ins.type });
70
+ }
71
+
72
+ // Prune old insights
73
+ this.insightRepo.pruneOldest(this.config.maxInsights);
74
+
75
+ if (insights.length > 0) {
76
+ this.logger.info(`Research: ${insights.length} new insights`);
77
+ }
78
+ }
79
+
80
+ private detectTrends(trades: TradeRecord[], now: number, insights: NewInsight[]): void {
81
+ const recentCutoff = new Date(now - this.config.trendWindowDays * 86400000).toISOString();
82
+ const olderCutoff = new Date(now - 30 * 86400000).toISOString();
83
+
84
+ const recent = trades.filter(o => o.created_at > recentCutoff);
85
+ const older = trades.filter(o => o.created_at > olderCutoff && o.created_at <= recentCutoff);
86
+
87
+ if (recent.length >= 5 && older.length >= 5) {
88
+ const recentWinRate = recent.filter(o => o.win === 1).length / recent.length;
89
+ const olderWinRate = older.filter(o => o.win === 1).length / older.length;
90
+ const delta = recentWinRate - olderWinRate;
91
+
92
+ if (Math.abs(delta) > 0.1) {
93
+ insights.push({
94
+ type: 'trend',
95
+ severity: Math.abs(delta) > 0.2 ? 'high' : 'medium',
96
+ title: delta > 0 ? 'Win-Rate steigt' : 'Win-Rate sinkt',
97
+ description: `Win-Rate ${delta > 0 ? 'gestiegen' : 'gesunken'}: ${(olderWinRate * 100).toFixed(0)}% → ${(recentWinRate * 100).toFixed(0)}% (letzte ${this.config.trendWindowDays} Tage vs. vorher)`,
98
+ data: { recentWinRate, olderWinRate, delta },
99
+ });
100
+ }
101
+ }
102
+ }
103
+
104
+ private detectGaps(trades: TradeRecord[], insights: NewInsight[]): void {
105
+ const regimeGroups: Record<string, TradeRecord[]> = {};
106
+ trades.forEach(o => {
107
+ const parts = o.fingerprint.split('|');
108
+ const regime = parts[4] || 'unknown';
109
+ if (!regimeGroups[regime]) regimeGroups[regime] = [];
110
+ regimeGroups[regime]!.push(o);
111
+ });
112
+
113
+ for (const [regime, group] of Object.entries(regimeGroups)) {
114
+ if (group.length < 5 && group.length > 0) {
115
+ insights.push({
116
+ type: 'gap',
117
+ severity: 'low',
118
+ title: `Datenlücke: ${regime}`,
119
+ description: `Nur ${group.length} Trades im Regime "${regime}" — Brain braucht mehr Daten für zuverlässige Gewichtung`,
120
+ data: { regime, count: group.length },
121
+ });
122
+ }
123
+ }
124
+ }
125
+
126
+ private detectSynergies(trades: TradeRecord[], insights: NewInsight[]): void {
127
+ const botTypeGroups: Record<string, TradeRecord[]> = {};
128
+ trades.forEach(o => {
129
+ if (!botTypeGroups[o.bot_type]) botTypeGroups[o.bot_type] = [];
130
+ botTypeGroups[o.bot_type]!.push(o);
131
+ });
132
+
133
+ const botTypes = Object.keys(botTypeGroups);
134
+ if (botTypes.length < 2) return;
135
+
136
+ for (let i = 0; i < botTypes.length; i++) {
137
+ for (let j = i + 1; j < botTypes.length; j++) {
138
+ const a = botTypeGroups[botTypes[i]!]!;
139
+ const b = botTypeGroups[botTypes[j]!]!;
140
+ const aFps = new Set(a.map(o => o.fingerprint));
141
+ const bFps = new Set(b.map(o => o.fingerprint));
142
+ let shared = 0;
143
+ aFps.forEach(fp => { if (bFps.has(fp)) shared++; });
144
+
145
+ if (shared > 0) {
146
+ insights.push({
147
+ type: 'synergy',
148
+ severity: shared > 3 ? 'high' : 'medium',
149
+ title: `Synergie: ${botTypes[i]} ↔ ${botTypes[j]}`,
150
+ description: `${shared} gemeinsame Signal-Muster — Erfahrungen von ${botTypes[i]} helfen ${botTypes[j]}`,
151
+ data: { botA: botTypes[i], botB: botTypes[j], sharedPatterns: shared },
152
+ });
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ private detectPerformance(trades: TradeRecord[], insights: NewInsight[]): void {
159
+ const fpGroups: Record<string, TradeRecord[]> = {};
160
+ trades.forEach(o => {
161
+ if (!fpGroups[o.fingerprint]) fpGroups[o.fingerprint] = [];
162
+ fpGroups[o.fingerprint]!.push(o);
163
+ });
164
+
165
+ let bestFp: string | null = null, worstFp: string | null = null;
166
+ let bestWR = 0, worstWR = 1;
167
+
168
+ for (const [fp, group] of Object.entries(fpGroups)) {
169
+ if (group.length < 5) continue;
170
+ const wr = group.filter(o => o.win === 1).length / group.length;
171
+ if (wr > bestWR) { bestWR = wr; bestFp = fp; }
172
+ if (wr < worstWR) { worstWR = wr; worstFp = fp; }
173
+ }
174
+
175
+ if (bestFp && bestWR > 0.6) {
176
+ insights.push({
177
+ type: 'performance',
178
+ severity: 'high',
179
+ title: 'Stärkstes Signal-Muster',
180
+ description: `"${bestFp.split('|').join(' + ')}" hat ${(bestWR * 100).toFixed(0)}% Win-Rate (n=${fpGroups[bestFp]!.length})`,
181
+ data: { fingerprint: bestFp, winRate: bestWR, count: fpGroups[bestFp]!.length },
182
+ });
183
+ }
184
+ if (worstFp && worstWR < 0.35) {
185
+ insights.push({
186
+ type: 'performance',
187
+ severity: 'high',
188
+ title: 'Schwächstes Signal-Muster',
189
+ description: `"${worstFp.split('|').join(' + ')}" hat nur ${(worstWR * 100).toFixed(0)}% Win-Rate (n=${fpGroups[worstFp]!.length}) — Brain reduziert Gewichtung`,
190
+ data: { fingerprint: worstFp, winRate: worstWR, count: fpGroups[worstFp]!.length },
191
+ });
192
+ }
193
+ }
194
+
195
+ private detectRegimeShifts(trades: TradeRecord[], now: number, insights: NewInsight[]): void {
196
+ const recentCutoff = new Date(now - this.config.trendWindowDays * 86400000).toISOString();
197
+ const recent = trades.filter(o => o.created_at > recentCutoff);
198
+ const older = trades.filter(o => o.created_at <= recentCutoff).slice(-10);
199
+
200
+ if (recent.length < 3) return;
201
+
202
+ const recentRegimes = recent.map(o => o.fingerprint.split('|')[4] || 'unknown');
203
+ const prevRegimes = older.map(o => o.fingerprint.split('|')[4] || 'unknown');
204
+ const recentMode = mode(recentRegimes);
205
+ const prevMode = mode(prevRegimes);
206
+
207
+ if (recentMode && prevMode && recentMode !== prevMode) {
208
+ insights.push({
209
+ type: 'regime_shift',
210
+ severity: 'high',
211
+ title: 'Regime-Wechsel erkannt',
212
+ description: `Marktregime hat sich verschoben: "${prevMode}" → "${recentMode}"`,
213
+ data: { from: prevMode, to: recentMode },
214
+ });
215
+ }
216
+ }
217
+
218
+ /** Manual trigger */
219
+ runManual(): number {
220
+ this.runCycle();
221
+ return this.insightRepo.count();
222
+ }
223
+ }
@@ -0,0 +1,68 @@
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 { InsightRepository } from '../db/repositories/insight.repository.js';
5
+ import type { SynapseManager } from '../synapses/synapse-manager.js';
6
+ import type { WeightedGraph } from '../graph/weighted-graph.js';
7
+
8
+ export class AnalyticsService {
9
+ constructor(
10
+ private tradeRepo: TradeRepository,
11
+ private ruleRepo: RuleRepository,
12
+ private chainRepo: ChainRepository,
13
+ private insightRepo: InsightRepository,
14
+ private synapseManager: SynapseManager,
15
+ private graph: WeightedGraph,
16
+ ) {}
17
+
18
+ getSummary(): Record<string, unknown> {
19
+ const tradeCount = this.tradeRepo.count();
20
+ const recentTrades = this.tradeRepo.getRecent(10);
21
+ const rules = this.ruleRepo.getAll();
22
+ const chains = this.chainRepo.getRecent(5);
23
+ const insights = this.insightRepo.getRecent(5);
24
+
25
+ const wins = recentTrades.filter(t => t.win === 1).length;
26
+ const recentWinRate = recentTrades.length > 0 ? wins / recentTrades.length : 0;
27
+
28
+ const topRule = rules.length > 0 ? {
29
+ pattern: rules[0]!.pattern,
30
+ winRate: Math.round(rules[0]!.win_rate * 100),
31
+ confidence: Math.round(rules[0]!.confidence * 100),
32
+ sampleCount: rules[0]!.sample_count,
33
+ } : null;
34
+
35
+ return {
36
+ trades: {
37
+ total: tradeCount,
38
+ recentWinRate: Math.round(recentWinRate * 100),
39
+ },
40
+ rules: {
41
+ total: rules.length,
42
+ topRule,
43
+ },
44
+ chains: {
45
+ total: this.chainRepo.count(),
46
+ recent: chains.map(c => ({
47
+ pair: c.pair,
48
+ type: c.type,
49
+ length: c.length,
50
+ })),
51
+ },
52
+ insights: {
53
+ total: this.insightRepo.count(),
54
+ recent: insights.map(i => ({
55
+ type: i.type,
56
+ severity: i.severity,
57
+ title: i.title,
58
+ })),
59
+ },
60
+ network: {
61
+ synapses: this.synapseManager.count(),
62
+ avgWeight: Number(this.synapseManager.getAvgWeight().toFixed(3)),
63
+ graphNodes: this.graph.getNodeCount(),
64
+ graphEdges: this.graph.getEdgeCount(),
65
+ },
66
+ };
67
+ }
68
+ }
@@ -0,0 +1,29 @@
1
+ import type { InsightRepository, InsightRecord } from '../db/repositories/insight.repository.js';
2
+
3
+ export class InsightService {
4
+ constructor(private insightRepo: InsightRepository) {}
5
+
6
+ getAll(): InsightRecord[] {
7
+ return this.insightRepo.getAll();
8
+ }
9
+
10
+ getRecent(limit: number = 10): InsightRecord[] {
11
+ return this.insightRepo.getRecent(limit);
12
+ }
13
+
14
+ getByType(type: string): InsightRecord[] {
15
+ return this.insightRepo.getByType(type);
16
+ }
17
+
18
+ getBySeverity(severity: string): InsightRecord[] {
19
+ return this.insightRepo.getBySeverity(severity);
20
+ }
21
+
22
+ search(query: string, limit: number = 20): InsightRecord[] {
23
+ return this.insightRepo.search(query, limit);
24
+ }
25
+
26
+ count(): number {
27
+ return this.insightRepo.count();
28
+ }
29
+ }
@@ -0,0 +1,109 @@
1
+ import type { SynapseManager } from '../synapses/synapse-manager.js';
2
+ import type { WeightedGraph } from '../graph/weighted-graph.js';
3
+ import type { CalibrationConfig } from '../types/config.types.js';
4
+ import { fingerprint, classifyVolatility, type SignalInput } from '../signals/fingerprint.js';
5
+ import { wilsonScore } from '../signals/wilson-score.js';
6
+ import { NODE_TYPES } from '../graph/weighted-graph.js';
7
+
8
+ const DEFAULT_WEIGHTS: Record<string, number> = {
9
+ rsi_oversold: 30,
10
+ rsi_overbought: 30,
11
+ rsi7_oversold: 15,
12
+ rsi7_overbought: 15,
13
+ macd_bullish: 20,
14
+ macd_bearish: 20,
15
+ trend_up: 15,
16
+ trend_down: 15,
17
+ mean_reversion_buy: 10,
18
+ mean_reversion_sell: 10,
19
+ combo_bonus: 0,
20
+ };
21
+
22
+ export class SignalService {
23
+ constructor(
24
+ private synapseManager: SynapseManager,
25
+ private graph: WeightedGraph,
26
+ private cal: CalibrationConfig,
27
+ private tradeCount: () => number,
28
+ ) {}
29
+
30
+ updateCalibration(cal: CalibrationConfig): void {
31
+ this.cal = cal;
32
+ }
33
+
34
+ /**
35
+ * Get brain-weighted signal strengths based on learned experience.
36
+ * Ported from tradingBrain.js getSignalWeights().
37
+ */
38
+ getSignalWeights(signals: SignalInput, regime?: string): Record<string, number> {
39
+ const weights = { ...DEFAULT_WEIGHTS };
40
+ if (this.tradeCount() < this.cal.minOutcomesForWeights) return weights;
41
+
42
+ const fp = fingerprint({ ...signals, regime });
43
+ const synapse = this.synapseManager.getByFingerprint(fp);
44
+
45
+ // Direct synapse match (fast path)
46
+ if (synapse && synapse.activations >= this.cal.minActivationsForWeight) {
47
+ const factor = synapse.weight / 0.5;
48
+ for (const key of Object.keys(DEFAULT_WEIGHTS)) {
49
+ if (key !== 'combo_bonus') {
50
+ weights[key] = Math.round(DEFAULT_WEIGHTS[key]! * factor);
51
+ }
52
+ }
53
+ }
54
+
55
+ // Spreading activation for combo bonus
56
+ const comboNodeId = `combo_${fp}`;
57
+ if (this.graph.nodes[comboNodeId]) {
58
+ const activated = this.graph.spreadingActivation(
59
+ comboNodeId, 1.0,
60
+ this.cal.spreadingActivationDecay,
61
+ this.cal.spreadingActivationThreshold,
62
+ 3,
63
+ );
64
+
65
+ let winEnergy = 0;
66
+ let lossEnergy = 0;
67
+ for (const node of activated) {
68
+ if (node.id === 'outcome_win') winEnergy = node.activation;
69
+ if (node.id === 'outcome_loss') lossEnergy = node.activation;
70
+ }
71
+
72
+ const netEnergy = winEnergy - lossEnergy;
73
+ if (Math.abs(netEnergy) > 0.05) {
74
+ const spreadBonus = Math.round(netEnergy * 30);
75
+ weights['combo_bonus'] = Math.max(-20, Math.min(30, spreadBonus));
76
+ }
77
+
78
+ // Similar combo nodes boost
79
+ let similarBoost = 0;
80
+ for (const node of activated) {
81
+ if (node.type === NODE_TYPES.COMBO && node.id !== comboNodeId && node.activation > 0.1) {
82
+ const simSyn = this.synapseManager.getByFingerprint(node.label);
83
+ if (simSyn && simSyn.weight > 0.6 && simSyn.activations >= 3) {
84
+ similarBoost += Math.round((simSyn.weight - 0.5) * 10 * node.activation);
85
+ }
86
+ }
87
+ }
88
+ weights['combo_bonus'] = Math.max(-20, Math.min(30, (weights['combo_bonus'] ?? 0) + similarBoost));
89
+ }
90
+
91
+ return weights;
92
+ }
93
+
94
+ /**
95
+ * Get Wilson Score confidence for signal pattern.
96
+ * Ported from tradingBrain.js getConfidence().
97
+ */
98
+ getConfidence(signals: SignalInput, regime?: string): number {
99
+ if (this.tradeCount() < this.cal.minOutcomesForWeights) return 0.5;
100
+
101
+ const fp = fingerprint({ ...signals, regime });
102
+ const synapse = this.synapseManager.getByFingerprint(fp);
103
+
104
+ if (!synapse || synapse.activations < this.cal.minActivationsForWeight) return 0.5;
105
+
106
+ const total = synapse.wins + synapse.losses;
107
+ return wilsonScore(synapse.wins, total, this.cal.wilsonZ);
108
+ }
109
+ }
@@ -0,0 +1,130 @@
1
+ import type { SynapseManager } from '../synapses/synapse-manager.js';
2
+ import type { WeightedGraph } from '../graph/weighted-graph.js';
3
+ import type { CalibrationConfig } from '../types/config.types.js';
4
+ import { fingerprint, fingerprintSimilarity, classifyVolatility, type SignalInput } from '../signals/fingerprint.js';
5
+
6
+ export interface DCAMultiplierResult {
7
+ multiplier: number;
8
+ reason: string;
9
+ }
10
+
11
+ export interface GridParamsResult {
12
+ spacingMultiplier: number;
13
+ reason: string;
14
+ }
15
+
16
+ export class StrategyService {
17
+ constructor(
18
+ private synapseManager: SynapseManager,
19
+ private graph: WeightedGraph,
20
+ private cal: CalibrationConfig,
21
+ private tradeCount: () => number,
22
+ ) {}
23
+
24
+ updateCalibration(cal: CalibrationConfig): void {
25
+ this.cal = cal;
26
+ }
27
+
28
+ /**
29
+ * Brain-recommended DCA multiplier based on regime success.
30
+ * Ported from tradingBrain.js getDCAMultiplier().
31
+ */
32
+ getDCAMultiplier(regime: string, rsi: number, volatility: number): DCAMultiplierResult {
33
+ const result: DCAMultiplierResult = { multiplier: 1.0, reason: 'Standard' };
34
+ if (this.tradeCount() < 10) return result;
35
+
36
+ const signals: SignalInput = { rsi14: rsi, macd: 0, trendScore: 0, volatility };
37
+ const fp = fingerprint({ ...signals, regime });
38
+
39
+ // Try spreading activation from regime node
40
+ const regimeNodeId = `regime_${regime}`;
41
+ let bestWeight = 0.5;
42
+ let bestActivations = 0;
43
+
44
+ if (this.graph.nodes[regimeNodeId]) {
45
+ const activated = this.graph.spreadingActivation(regimeNodeId, 1.0, 0.5, 0.05, 3);
46
+ const winNode = activated.find(n => n.id === 'outcome_win');
47
+ const lossNode = activated.find(n => n.id === 'outcome_loss');
48
+ if (winNode || lossNode) {
49
+ const winE = winNode?.activation || 0;
50
+ const lossE = lossNode?.activation || 0;
51
+ const total = winE + lossE;
52
+ if (total > 0) {
53
+ bestWeight = winE / total;
54
+ bestActivations = 10;
55
+ }
56
+ }
57
+ }
58
+
59
+ // Fallback: fingerprint similarity scan
60
+ if (bestActivations < 5) {
61
+ for (const syn of this.synapseManager.getAll()) {
62
+ const sim = fingerprintSimilarity(fp, syn.fingerprint);
63
+ if (sim >= 0.6 && syn.activations > bestActivations) {
64
+ bestWeight = syn.weight;
65
+ bestActivations = syn.activations;
66
+ }
67
+ }
68
+ }
69
+
70
+ if (bestActivations >= 5) {
71
+ result.multiplier = Math.max(0.3, Math.min(2.5, bestWeight * 2));
72
+ const winRate = bestWeight > 0.5 ? 'hohe' : 'niedrige';
73
+ result.reason = `Brain: ${winRate} Recovery-Rate (n=${bestActivations})`;
74
+ }
75
+
76
+ return result;
77
+ }
78
+
79
+ /**
80
+ * Brain-recommended grid spacing based on volatility history.
81
+ * Ported from tradingBrain.js getGridParams().
82
+ */
83
+ getGridParams(regime: string, volatility: number, pair: string): GridParamsResult {
84
+ const result: GridParamsResult = { spacingMultiplier: 1.0, reason: 'Standard' };
85
+ if (this.tradeCount() < 10) return result;
86
+
87
+ const volClass = classifyVolatility(volatility);
88
+ const volNodeId = `sig_vol_${volClass}`;
89
+
90
+ if (this.graph.nodes[volNodeId]) {
91
+ const activated = this.graph.spreadingActivation(volNodeId, 1.0, 0.5, 0.05, 3);
92
+ const winNode = activated.find(n => n.id === 'outcome_win');
93
+ const lossNode = activated.find(n => n.id === 'outcome_loss');
94
+ if (winNode || lossNode) {
95
+ const winE = winNode?.activation || 0;
96
+ const lossE = lossNode?.activation || 0;
97
+ const ratio = (winE - lossE) / Math.max(winE + lossE, 0.01);
98
+ if (ratio < -0.2) {
99
+ result.spacingMultiplier = 1.3 + Math.abs(ratio) * 0.5;
100
+ result.reason = `Brain: ${volClass} Vol historisch schwach → breitere Grids`;
101
+ } else if (ratio > 0.2) {
102
+ result.spacingMultiplier = 0.7 + (1 - ratio) * 0.3;
103
+ result.reason = `Brain: ${volClass} Vol historisch stark → engere Grids`;
104
+ }
105
+ return result;
106
+ }
107
+ }
108
+
109
+ // Fallback: synapse scan
110
+ const signals: SignalInput = { rsi14: 50, macd: 0, trendScore: 0, volatility };
111
+ const fp = fingerprint({ ...signals, regime });
112
+ let totalWeight = 0, count = 0;
113
+ for (const syn of this.synapseManager.getAll()) {
114
+ const sim = fingerprintSimilarity(fp, syn.fingerprint);
115
+ if (sim >= 0.6 && syn.activations >= 3) { totalWeight += syn.weight; count++; }
116
+ }
117
+ if (count >= 2) {
118
+ const avgWeight = totalWeight / count;
119
+ if (avgWeight < 0.4) {
120
+ result.spacingMultiplier = 1.3 + (0.4 - avgWeight);
121
+ result.reason = `Brain: historisch schwach → breitere Grids`;
122
+ } else if (avgWeight > 0.6) {
123
+ result.spacingMultiplier = 0.7 + (1.0 - avgWeight) * 0.5;
124
+ result.reason = `Brain: historisch stark → engere Grids`;
125
+ }
126
+ }
127
+
128
+ return result;
129
+ }
130
+ }
@@ -0,0 +1,58 @@
1
+ import type { SynapseManager } from '../synapses/synapse-manager.js';
2
+ import type { WeightedGraph, ActivatedNode } from '../graph/weighted-graph.js';
3
+
4
+ export class SynapseService {
5
+ constructor(
6
+ private synapseManager: SynapseManager,
7
+ private graph: WeightedGraph,
8
+ ) {}
9
+
10
+ explore(query: string): ActivatedNode[] {
11
+ // Find matching node (exact or fuzzy)
12
+ let startNode: string | null = null;
13
+ for (const node of Object.values(this.graph.nodes)) {
14
+ if (node.id === query || node.label === query) {
15
+ startNode = node.id;
16
+ break;
17
+ }
18
+ }
19
+
20
+ if (!startNode) {
21
+ const queryLower = query.toLowerCase();
22
+ for (const node of Object.values(this.graph.nodes)) {
23
+ if (node.label.toLowerCase().includes(queryLower) || node.id.toLowerCase().includes(queryLower)) {
24
+ startNode = node.id;
25
+ break;
26
+ }
27
+ }
28
+ }
29
+
30
+ if (!startNode) return [];
31
+ return this.graph.spreadingActivation(startNode, 1.0, 0.6, 0.05, 4);
32
+ }
33
+
34
+ findPath(fromId: string, toId: string): string[] | null {
35
+ return this.graph.findPath(fromId, toId);
36
+ }
37
+
38
+ getStats(): {
39
+ totalSynapses: number;
40
+ avgWeight: number;
41
+ graphNodes: number;
42
+ graphEdges: number;
43
+ strongest: Array<{ id: string; weight: number; activations: number }>;
44
+ } {
45
+ const strongest = this.synapseManager.getStrongest(5).map(s => ({
46
+ id: s.id,
47
+ weight: s.weight,
48
+ activations: s.activations,
49
+ }));
50
+ return {
51
+ totalSynapses: this.synapseManager.count(),
52
+ avgWeight: this.synapseManager.getAvgWeight(),
53
+ graphNodes: this.graph.getNodeCount(),
54
+ graphEdges: this.graph.getEdgeCount(),
55
+ strongest,
56
+ };
57
+ }
58
+ }