@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,28 @@
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header } from '../colors.js';
4
+
5
+ export function queryCommand(): Command {
6
+ return new Command('query')
7
+ .description('Search trades and signals')
8
+ .argument('<search>', 'Search query (fingerprint, pair, bot type)')
9
+ .option('-l, --limit <n>', 'Max results', '20')
10
+ .action(async (search, opts) => {
11
+ console.log(header('Trade Search', icons.search));
12
+
13
+ await withIpc(async (client) => {
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ const results: any = await client.request('trade.query', { search, limit: Number(opts.limit) });
16
+ if (!results?.length) {
17
+ console.log(` ${c.dim('No trades found.')}`);
18
+ return;
19
+ }
20
+ console.log(` ${c.info(`Found ${results.length} trades:`)}\n`);
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ for (const t of results) {
23
+ const badge = t.win ? c.green('WIN') : c.red('LOSS');
24
+ console.log(` #${t.id} [${badge}] ${c.cyan(t.pair)} ${c.dim(t.fingerprint)} ${t.profit_pct.toFixed(2)}%`);
25
+ }
26
+ });
27
+ });
28
+ }
@@ -0,0 +1,27 @@
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header } from '../colors.js';
4
+
5
+ export function rulesCommand(): Command {
6
+ return new Command('rules')
7
+ .description('Show learned trading rules')
8
+ .action(async () => {
9
+ console.log(header('Learned Rules', icons.rule));
10
+
11
+ await withIpc(async (client) => {
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ const rules: any = await client.request('rule.list', {});
14
+ if (!rules?.length) {
15
+ console.log(` ${c.dim('No rules learned yet. Brain needs more trades.')}`);
16
+ return;
17
+ }
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ for (const r of rules) {
20
+ const conf = Math.round(r.confidence * 100);
21
+ const wr = Math.round(r.win_rate * 100);
22
+ const confColor = conf > 70 ? c.green : conf > 50 ? c.orange : c.red;
23
+ console.log(` ${confColor(`${conf}%`)} conf ${c.dim('|')} ${c.cyan(`${wr}%`)} WR ${c.dim('|')} n=${r.sample_count} ${c.dim('|')} ${c.value(r.pattern)}`);
24
+ }
25
+ });
26
+ });
27
+ }
@@ -0,0 +1,93 @@
1
+ import { Command } from 'commander';
2
+ import { spawn, type ChildProcess } from 'node:child_process';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { getDataDir } from '../../utils/paths.js';
6
+ import { c, icons } from '../colors.js';
7
+
8
+ const MAX_RESTARTS = 5;
9
+ const RESTART_WINDOW_MS = 5 * 60 * 1000;
10
+ const BASE_BACKOFF_MS = 1000;
11
+
12
+ function spawnDaemon(entryPoint: string, args: string[]): ChildProcess {
13
+ const child = spawn(process.execPath, [entryPoint, ...args], {
14
+ detached: true,
15
+ stdio: 'ignore',
16
+ });
17
+ child.unref();
18
+ return child;
19
+ }
20
+
21
+ function startWatchdog(entryPoint: string, args: string[], pidPath: string): void {
22
+ const restartTimes: number[] = [];
23
+
24
+ function launch(): void {
25
+ const child = spawnDaemon(entryPoint, args);
26
+ console.log(`${icons.trade} ${c.info('Trading Brain daemon starting')} ${c.dim(`(PID: ${child.pid})`)}`);
27
+
28
+ child.on('exit', (code) => {
29
+ if (code === 0 || code === null) return;
30
+
31
+ const now = Date.now();
32
+ restartTimes.push(now);
33
+ const recentRestarts = restartTimes.filter((t) => now - t < RESTART_WINDOW_MS);
34
+ restartTimes.length = 0;
35
+ restartTimes.push(...recentRestarts);
36
+
37
+ if (recentRestarts.length > MAX_RESTARTS) {
38
+ console.error(`${icons.error} ${c.error(`Trading Brain crashed ${MAX_RESTARTS} times in 5 minutes — giving up.`)}`);
39
+ try { fs.unlinkSync(pidPath); } catch { /* ignore */ }
40
+ return;
41
+ }
42
+
43
+ const backoff = BASE_BACKOFF_MS * Math.pow(2, recentRestarts.length - 1);
44
+ console.log(`${icons.warn} ${c.warn(`Trading Brain exited (code ${code}) — restarting in ${backoff / 1000}s...`)}`);
45
+ setTimeout(launch, backoff);
46
+ });
47
+ }
48
+
49
+ launch();
50
+
51
+ setTimeout(() => {
52
+ if (fs.existsSync(pidPath)) {
53
+ console.log(`${icons.ok} ${c.success('Trading Brain daemon started successfully.')} ${c.dim('(watchdog active)')}`);
54
+ } else {
55
+ console.log(`${icons.clock} ${c.warn('Trading Brain may still be starting.')} Check: ${c.cyan('trading status')}`);
56
+ }
57
+ }, 1000);
58
+ }
59
+
60
+ export function startCommand(): Command {
61
+ return new Command('start')
62
+ .description('Start the Trading Brain daemon')
63
+ .option('-f, --foreground', 'Run in foreground (no detach)')
64
+ .option('-c, --config <path>', 'Config file path')
65
+ .action((opts) => {
66
+ const pidPath = path.join(getDataDir(), 'trading-brain.pid');
67
+
68
+ if (fs.existsSync(pidPath)) {
69
+ const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
70
+ try {
71
+ process.kill(pid, 0);
72
+ console.log(`${icons.trade} Trading Brain daemon is ${c.green('already running')} ${c.dim(`(PID: ${pid})`)}`);
73
+ return;
74
+ } catch {
75
+ fs.unlinkSync(pidPath);
76
+ }
77
+ }
78
+
79
+ if (opts.foreground) {
80
+ import('../../trading-core.js').then(({ TradingCore }) => {
81
+ const core = new TradingCore();
82
+ core.start(opts.config);
83
+ });
84
+ return;
85
+ }
86
+
87
+ const args = ['daemon'];
88
+ if (opts.config) args.push('-c', opts.config);
89
+ const entryPoint = path.resolve(import.meta.dirname, '../../index.js');
90
+
91
+ startWatchdog(entryPoint, args, pidPath);
92
+ });
93
+ }
@@ -0,0 +1,64 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getDataDir } from '../../utils/paths.js';
5
+ import { withIpc } from '../ipc-helper.js';
6
+ import { c, icons, header, keyValue, divider } from '../colors.js';
7
+
8
+ export function statusCommand(): Command {
9
+ return new Command('status')
10
+ .description('Show Trading Brain daemon status')
11
+ .action(async () => {
12
+ const pidPath = path.join(getDataDir(), 'trading-brain.pid');
13
+
14
+ if (!fs.existsSync(pidPath)) {
15
+ console.log(`${icons.trade} Trading Brain Daemon: ${c.red.bold('NOT RUNNING')}`);
16
+ return;
17
+ }
18
+
19
+ const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
20
+ let running = false;
21
+ try { process.kill(pid, 0); running = true; } catch { /* not running */ }
22
+
23
+ if (!running) {
24
+ console.log(`${icons.trade} Trading Brain Daemon: ${c.red.bold('NOT RUNNING')} ${c.dim('(stale PID file)')}`);
25
+ return;
26
+ }
27
+
28
+ console.log(header('Trading Brain Status v1.0.0', icons.trade));
29
+ console.log(` ${c.green(`${icons.dot} RUNNING`)} ${c.dim(`(PID ${pid})`)}`);
30
+
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ await withIpc(async (client) => {
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ const summary: any = await client.request('analytics.summary', {});
35
+
36
+ const dbPath = path.join(getDataDir(), 'trading-brain.db');
37
+ let dbSize = '?';
38
+ try {
39
+ const stat = fs.statSync(dbPath);
40
+ dbSize = `${(stat.size / 1024 / 1024).toFixed(1)} MB`;
41
+ } catch { /* ignore */ }
42
+
43
+ console.log(keyValue('Database', `${dbPath} (${dbSize})`));
44
+ console.log();
45
+
46
+ console.log(` ${icons.trade} ${c.green.bold('Trade Brain')}`);
47
+ console.log(` ${c.label('Trades:')} ${c.value(summary.trades?.total ?? 0)} total, ${c.cyan(`${summary.trades?.recentWinRate ?? 0}%`)} recent win-rate`);
48
+ console.log(` ${c.label('Rules:')} ${c.green(summary.rules?.total ?? 0)} learned`);
49
+ console.log();
50
+
51
+ console.log(` ${icons.synapse} ${c.cyan.bold('Synapse Network')}`);
52
+ console.log(` ${c.label('Synapses:')} ${c.value(summary.network?.synapses ?? 0)}`);
53
+ console.log(` ${c.label('Avg weight:')} ${c.value(summary.network?.avgWeight ?? 0)}`);
54
+ console.log(` ${c.label('Graph:')} ${c.value(summary.network?.graphNodes ?? 0)} nodes, ${c.value(summary.network?.graphEdges ?? 0)} edges`);
55
+ console.log();
56
+
57
+ console.log(` ${icons.insight} ${c.orange.bold('Research')}`);
58
+ console.log(` ${c.label('Insights:')} ${c.value(summary.insights?.total ?? 0)}`);
59
+ console.log(` ${c.label('Chains:')} ${c.value(summary.chains?.total ?? 0)}`);
60
+
61
+ console.log(`\n${divider()}`);
62
+ });
63
+ });
64
+ }
@@ -0,0 +1,33 @@
1
+ import { Command } from 'commander';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getDataDir } from '../../utils/paths.js';
5
+ import { c, icons } from '../colors.js';
6
+
7
+ export function stopCommand(): Command {
8
+ return new Command('stop')
9
+ .description('Stop the Trading Brain daemon')
10
+ .action(() => {
11
+ const pidPath = path.join(getDataDir(), 'trading-brain.pid');
12
+
13
+ if (!fs.existsSync(pidPath)) {
14
+ console.log(`${icons.trade} ${c.dim('Trading Brain daemon is not running (no PID file found).')}`);
15
+ return;
16
+ }
17
+
18
+ const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
19
+
20
+ try {
21
+ process.kill(pid, 'SIGTERM');
22
+ console.log(`${icons.trade} ${c.success('Trading Brain daemon stopped')} ${c.dim(`(PID: ${pid})`)}`);
23
+ } catch (err) {
24
+ if ((err as NodeJS.ErrnoException).code === 'ESRCH') {
25
+ console.log(`${icons.trade} ${c.dim('Trading Brain was not running (stale PID file removed).')}`);
26
+ } else {
27
+ console.error(`${icons.error} ${c.error(`Failed to stop daemon: ${err}`)}`);
28
+ }
29
+ }
30
+
31
+ try { fs.unlinkSync(pidPath); } catch { /* ignore */ }
32
+ });
33
+ }
@@ -0,0 +1,22 @@
1
+ import { IpcClient } from '../ipc/client.js';
2
+ import { getPipeName } from '../utils/paths.js';
3
+ import { c, icons } from './colors.js';
4
+
5
+ export async function withIpc<T>(fn: (client: IpcClient) => Promise<T>): Promise<T> {
6
+ const client = new IpcClient(getPipeName(), 5000);
7
+ try {
8
+ await client.connect();
9
+ return await fn(client);
10
+ } catch (err) {
11
+ if (err instanceof Error && err.message.includes('ENOENT')) {
12
+ console.error(`${icons.error} ${c.error('Trading Brain daemon is not running.')} Start it with: ${c.cyan('trading start')}`);
13
+ } else if (err instanceof Error && err.message.includes('ECONNREFUSED')) {
14
+ console.error(`${icons.error} ${c.error('Trading Brain daemon is not responding.')} Try: ${c.cyan('trading stop && trading start')}`);
15
+ } else {
16
+ console.error(`${icons.error} ${c.error(err instanceof Error ? err.message : String(err))}`);
17
+ }
18
+ process.exit(1);
19
+ } finally {
20
+ client.disconnect();
21
+ }
22
+ }
package/src/config.ts ADDED
@@ -0,0 +1,103 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import type { TradingBrainConfig } from './types/config.types.js';
4
+ import { getDataDir, getPipeName } from './utils/paths.js';
5
+
6
+ const defaults: TradingBrainConfig = {
7
+ dataDir: getDataDir(),
8
+ dbPath: path.join(getDataDir(), 'trading-brain.db'),
9
+ ipc: {
10
+ pipeName: getPipeName(),
11
+ timeout: 5000,
12
+ },
13
+ api: {
14
+ port: 7779,
15
+ enabled: true,
16
+ },
17
+ mcpHttp: {
18
+ port: 7780,
19
+ enabled: true,
20
+ },
21
+ calibration: {
22
+ learningRate: 0.15,
23
+ weakenPenalty: 0.7,
24
+ decayHalfLifeDays: 14,
25
+ patternExtractionInterval: 50,
26
+ patternMinSamples: 10,
27
+ patternWilsonThreshold: 0.5,
28
+ wilsonZ: 1.96,
29
+ spreadingActivationDecay: 0.6,
30
+ spreadingActivationThreshold: 0.05,
31
+ minActivationsForWeight: 3,
32
+ minOutcomesForWeights: 5,
33
+ },
34
+ learning: {
35
+ intervalMs: 900_000, // 15 minutes
36
+ fingerprintSimilarityThreshold: 0.7,
37
+ chainMinLength: 3,
38
+ maxChains: 100,
39
+ },
40
+ research: {
41
+ intervalMs: 3_600_000, // 1 hour
42
+ initialDelayMs: 300_000, // 5 minutes
43
+ trendWindowDays: 7,
44
+ minTrades: 20,
45
+ maxInsights: 50,
46
+ },
47
+ log: {
48
+ level: 'info',
49
+ file: path.join(getDataDir(), 'trading-brain.log'),
50
+ maxSize: 10 * 1024 * 1024,
51
+ maxFiles: 3,
52
+ },
53
+ };
54
+
55
+ function applyEnvOverrides(config: TradingBrainConfig): void {
56
+ if (process.env['TRADING_BRAIN_DATA_DIR']) {
57
+ config.dataDir = process.env['TRADING_BRAIN_DATA_DIR'];
58
+ config.dbPath = path.join(config.dataDir, 'trading-brain.db');
59
+ config.log.file = path.join(config.dataDir, 'trading-brain.log');
60
+ }
61
+ if (process.env['TRADING_BRAIN_DB_PATH']) config.dbPath = process.env['TRADING_BRAIN_DB_PATH'];
62
+ if (process.env['TRADING_BRAIN_LOG_LEVEL']) config.log.level = process.env['TRADING_BRAIN_LOG_LEVEL'];
63
+ if (process.env['TRADING_BRAIN_PIPE_NAME']) config.ipc.pipeName = process.env['TRADING_BRAIN_PIPE_NAME'];
64
+ if (process.env['TRADING_BRAIN_API_PORT']) config.api.port = Number(process.env['TRADING_BRAIN_API_PORT']);
65
+ if (process.env['TRADING_BRAIN_API_ENABLED']) config.api.enabled = process.env['TRADING_BRAIN_API_ENABLED'] !== 'false';
66
+ if (process.env['TRADING_BRAIN_API_KEY']) config.api.apiKey = process.env['TRADING_BRAIN_API_KEY'];
67
+ if (process.env['TRADING_BRAIN_MCP_HTTP_PORT']) config.mcpHttp.port = Number(process.env['TRADING_BRAIN_MCP_HTTP_PORT']);
68
+ if (process.env['TRADING_BRAIN_MCP_HTTP_ENABLED']) config.mcpHttp.enabled = process.env['TRADING_BRAIN_MCP_HTTP_ENABLED'] !== 'false';
69
+ }
70
+
71
+ function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): void {
72
+ for (const key of Object.keys(source)) {
73
+ const val = source[key];
74
+ if (val && typeof val === 'object' && !Array.isArray(val) && target[key] && typeof target[key] === 'object') {
75
+ deepMerge(target[key] as Record<string, unknown>, val as Record<string, unknown>);
76
+ } else if (val !== undefined) {
77
+ target[key] = val;
78
+ }
79
+ }
80
+ }
81
+
82
+ export function loadConfig(configPath?: string): TradingBrainConfig {
83
+ const config = structuredClone(defaults);
84
+
85
+ if (configPath) {
86
+ const filePath = path.resolve(configPath);
87
+ if (fs.existsSync(filePath)) {
88
+ const raw = fs.readFileSync(filePath, 'utf-8');
89
+ const fileConfig = JSON.parse(raw) as Partial<TradingBrainConfig>;
90
+ deepMerge(config as unknown as Record<string, unknown>, fileConfig as unknown as Record<string, unknown>);
91
+ }
92
+ } else {
93
+ const defaultConfigPath = path.join(getDataDir(), 'config.json');
94
+ if (fs.existsSync(defaultConfigPath)) {
95
+ const raw = fs.readFileSync(defaultConfigPath, 'utf-8');
96
+ const fileConfig = JSON.parse(raw) as Partial<TradingBrainConfig>;
97
+ deepMerge(config as unknown as Record<string, unknown>, fileConfig as unknown as Record<string, unknown>);
98
+ }
99
+ }
100
+
101
+ applyEnvOverrides(config);
102
+ return config;
103
+ }
@@ -0,0 +1,22 @@
1
+ import Database from 'better-sqlite3';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getLogger } from '../utils/logger.js';
5
+
6
+ export function createConnection(dbPath: string): Database.Database {
7
+ const logger = getLogger();
8
+ const dir = path.dirname(dbPath);
9
+ if (!fs.existsSync(dir)) {
10
+ fs.mkdirSync(dir, { recursive: true });
11
+ }
12
+
13
+ logger.info(`Opening database at ${dbPath}`);
14
+ const db = new Database(dbPath);
15
+
16
+ db.pragma('journal_mode = WAL');
17
+ db.pragma('synchronous = NORMAL');
18
+ db.pragma('cache_size = 10000');
19
+ db.pragma('foreign_keys = ON');
20
+
21
+ return db;
22
+ }
@@ -0,0 +1,43 @@
1
+ import type Database from 'better-sqlite3';
2
+
3
+ export function up(db: Database.Database): void {
4
+ db.exec(`
5
+ CREATE TABLE IF NOT EXISTS trades (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ fingerprint TEXT NOT NULL,
8
+ pair TEXT NOT NULL,
9
+ bot_type TEXT NOT NULL,
10
+ regime TEXT,
11
+ profit_pct REAL NOT NULL,
12
+ win INTEGER NOT NULL DEFAULT 0,
13
+ signals_json TEXT,
14
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
15
+ );
16
+
17
+ CREATE INDEX IF NOT EXISTS idx_trades_fingerprint ON trades(fingerprint);
18
+ CREATE INDEX IF NOT EXISTS idx_trades_pair ON trades(pair);
19
+ CREATE INDEX IF NOT EXISTS idx_trades_bot_type ON trades(bot_type);
20
+ CREATE INDEX IF NOT EXISTS idx_trades_created_at ON trades(created_at);
21
+ CREATE INDEX IF NOT EXISTS idx_trades_win ON trades(win);
22
+
23
+ CREATE TABLE IF NOT EXISTS signal_combos (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ fingerprint TEXT NOT NULL,
26
+ signals_json TEXT NOT NULL,
27
+ regime TEXT,
28
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
29
+ );
30
+
31
+ CREATE INDEX IF NOT EXISTS idx_combos_fingerprint ON signal_combos(fingerprint);
32
+
33
+ CREATE TABLE IF NOT EXISTS pairs (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ name TEXT NOT NULL UNIQUE,
36
+ total_trades INTEGER NOT NULL DEFAULT 0,
37
+ total_wins INTEGER NOT NULL DEFAULT 0,
38
+ total_profit REAL NOT NULL DEFAULT 0,
39
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
40
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
41
+ );
42
+ `);
43
+ }
@@ -0,0 +1,44 @@
1
+ import type Database from 'better-sqlite3';
2
+
3
+ export function up(db: Database.Database): void {
4
+ db.exec(`
5
+ CREATE TABLE IF NOT EXISTS synapses (
6
+ id TEXT PRIMARY KEY,
7
+ fingerprint TEXT NOT NULL,
8
+ weight REAL NOT NULL DEFAULT 0.5,
9
+ wins INTEGER NOT NULL DEFAULT 0,
10
+ losses INTEGER NOT NULL DEFAULT 0,
11
+ activations INTEGER NOT NULL DEFAULT 0,
12
+ total_profit REAL NOT NULL DEFAULT 0,
13
+ last_activated TEXT NOT NULL DEFAULT (datetime('now')),
14
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
15
+ );
16
+
17
+ CREATE INDEX IF NOT EXISTS idx_synapses_fingerprint ON synapses(fingerprint);
18
+ CREATE INDEX IF NOT EXISTS idx_synapses_weight ON synapses(weight);
19
+
20
+ CREATE TABLE IF NOT EXISTS graph_nodes (
21
+ id TEXT PRIMARY KEY,
22
+ type TEXT NOT NULL,
23
+ label TEXT NOT NULL,
24
+ activation REAL NOT NULL DEFAULT 0,
25
+ total_activations INTEGER NOT NULL DEFAULT 0
26
+ );
27
+
28
+ CREATE INDEX IF NOT EXISTS idx_graph_nodes_type ON graph_nodes(type);
29
+
30
+ CREATE TABLE IF NOT EXISTS graph_edges (
31
+ id TEXT PRIMARY KEY,
32
+ source TEXT NOT NULL,
33
+ target TEXT NOT NULL,
34
+ weight REAL NOT NULL DEFAULT 0.5,
35
+ activations INTEGER NOT NULL DEFAULT 0,
36
+ last_activated TEXT NOT NULL DEFAULT (datetime('now')),
37
+ FOREIGN KEY (source) REFERENCES graph_nodes(id),
38
+ FOREIGN KEY (target) REFERENCES graph_nodes(id)
39
+ );
40
+
41
+ CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges(source);
42
+ CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges(target);
43
+ `);
44
+ }
@@ -0,0 +1,49 @@
1
+ import type Database from 'better-sqlite3';
2
+
3
+ export function up(db: Database.Database): void {
4
+ db.exec(`
5
+ CREATE TABLE IF NOT EXISTS rules (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ pattern TEXT NOT NULL,
8
+ confidence REAL NOT NULL,
9
+ sample_count INTEGER NOT NULL,
10
+ win_rate REAL NOT NULL,
11
+ avg_profit REAL NOT NULL DEFAULT 0,
12
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
13
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
14
+ );
15
+
16
+ CREATE INDEX IF NOT EXISTS idx_rules_pattern ON rules(pattern);
17
+ CREATE INDEX IF NOT EXISTS idx_rules_confidence ON rules(confidence);
18
+
19
+ CREATE TABLE IF NOT EXISTS chains (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ pair TEXT NOT NULL,
22
+ type TEXT NOT NULL,
23
+ length INTEGER NOT NULL,
24
+ fingerprints_json TEXT NOT NULL,
25
+ total_profit REAL NOT NULL DEFAULT 0,
26
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
27
+ );
28
+
29
+ CREATE INDEX IF NOT EXISTS idx_chains_pair ON chains(pair);
30
+ CREATE INDEX IF NOT EXISTS idx_chains_type ON chains(type);
31
+
32
+ CREATE TABLE IF NOT EXISTS calibration (
33
+ id TEXT PRIMARY KEY DEFAULT 'main',
34
+ learning_rate REAL NOT NULL,
35
+ weaken_penalty REAL NOT NULL,
36
+ decay_half_life_days INTEGER NOT NULL,
37
+ pattern_extraction_interval INTEGER NOT NULL,
38
+ pattern_min_samples INTEGER NOT NULL,
39
+ pattern_wilson_threshold REAL NOT NULL,
40
+ wilson_z REAL NOT NULL,
41
+ spreading_activation_decay REAL NOT NULL,
42
+ spreading_activation_threshold REAL NOT NULL,
43
+ min_activations_for_weight INTEGER NOT NULL,
44
+ min_outcomes_for_weights INTEGER NOT NULL,
45
+ last_calibration TEXT NOT NULL DEFAULT (datetime('now')),
46
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
47
+ );
48
+ `);
49
+ }
@@ -0,0 +1,30 @@
1
+ import type Database from 'better-sqlite3';
2
+
3
+ export function up(db: Database.Database): void {
4
+ db.exec(`
5
+ CREATE TABLE IF NOT EXISTS insights (
6
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
7
+ type TEXT NOT NULL,
8
+ severity TEXT NOT NULL DEFAULT 'medium',
9
+ title TEXT NOT NULL,
10
+ description TEXT NOT NULL,
11
+ data_json TEXT,
12
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
13
+ );
14
+
15
+ CREATE INDEX IF NOT EXISTS idx_insights_type ON insights(type);
16
+ CREATE INDEX IF NOT EXISTS idx_insights_severity ON insights(severity);
17
+
18
+ CREATE VIRTUAL TABLE IF NOT EXISTS insights_fts USING fts5(
19
+ title, description, content=insights, content_rowid=id
20
+ );
21
+
22
+ CREATE TRIGGER IF NOT EXISTS insights_ai AFTER INSERT ON insights BEGIN
23
+ INSERT INTO insights_fts(rowid, title, description) VALUES (new.id, new.title, new.description);
24
+ END;
25
+
26
+ CREATE TRIGGER IF NOT EXISTS insights_ad AFTER DELETE ON insights BEGIN
27
+ INSERT INTO insights_fts(insights_fts, rowid, title, description) VALUES ('delete', old.id, old.title, old.description);
28
+ END;
29
+ `);
30
+ }
@@ -0,0 +1,60 @@
1
+ import type Database from 'better-sqlite3';
2
+ import { getLogger } from '../../utils/logger.js';
3
+ import { up as coreSchema } from './001_core.js';
4
+ import { up as synapsesSchema } from './002_synapses.js';
5
+ import { up as learningSchema } from './003_learning.js';
6
+ import { up as researchSchema } from './004_research.js';
7
+
8
+ interface Migration {
9
+ version: number;
10
+ name: string;
11
+ up: (db: Database.Database) => void;
12
+ }
13
+
14
+ const migrations: Migration[] = [
15
+ { version: 1, name: '001_core', up: coreSchema },
16
+ { version: 2, name: '002_synapses', up: synapsesSchema },
17
+ { version: 3, name: '003_learning', up: learningSchema },
18
+ { version: 4, name: '004_research', up: researchSchema },
19
+ ];
20
+
21
+ function ensureMigrationsTable(db: Database.Database): void {
22
+ db.exec(`
23
+ CREATE TABLE IF NOT EXISTS migrations (
24
+ version INTEGER PRIMARY KEY,
25
+ name TEXT NOT NULL,
26
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
27
+ );
28
+ `);
29
+ }
30
+
31
+ function getCurrentVersion(db: Database.Database): number {
32
+ const row = db.prepare('SELECT MAX(version) as version FROM migrations').get() as { version: number | null } | undefined;
33
+ return row?.version ?? 0;
34
+ }
35
+
36
+ export function runMigrations(db: Database.Database): void {
37
+ const logger = getLogger();
38
+ ensureMigrationsTable(db);
39
+
40
+ const currentVersion = getCurrentVersion(db);
41
+ const pending = migrations.filter(m => m.version > currentVersion);
42
+
43
+ if (pending.length === 0) {
44
+ logger.info('Database is up to date');
45
+ return;
46
+ }
47
+
48
+ logger.info(`Running ${pending.length} migration(s) from version ${currentVersion}`);
49
+
50
+ const runAll = db.transaction(() => {
51
+ for (const migration of pending) {
52
+ logger.info(`Applying migration ${migration.name}`);
53
+ migration.up(db);
54
+ db.prepare('INSERT INTO migrations (version, name) VALUES (?, ?)').run(migration.version, migration.name);
55
+ }
56
+ });
57
+
58
+ runAll();
59
+ logger.info(`Migrations complete. Now at version ${pending[pending.length - 1]!.version}`);
60
+ }