@senpi/runtime 3.0.0-dev.runtime-phase-2.20260623210537 → 3.0.1-dev.openclaw-upgrade-mai.20260625140818

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 (215) hide show
  1. package/README.md +10 -9
  2. package/dist/actions/action-runtime-module.d.ts.map +1 -1
  3. package/dist/actions/action-runtime-module.js +1 -1
  4. package/dist/actions/action-runtime-module.js.map +1 -1
  5. package/dist/actions/decision-engine.d.ts.map +1 -1
  6. package/dist/actions/decision-engine.js +3 -0
  7. package/dist/actions/decision-engine.js.map +1 -1
  8. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  9. package/dist/actions/open-position/open-position.action.js +54 -14
  10. package/dist/actions/open-position/open-position.action.js.map +1 -1
  11. package/dist/actions/types.d.ts +10 -2
  12. package/dist/actions/types.d.ts.map +1 -1
  13. package/dist/actions/types.js +8 -0
  14. package/dist/actions/types.js.map +1 -1
  15. package/dist/cli/guide-commands.d.ts.map +1 -1
  16. package/dist/cli/guide-commands.js +80 -74
  17. package/dist/cli/guide-commands.js.map +1 -1
  18. package/dist/cli/senpi-commands.d.ts.map +1 -1
  19. package/dist/cli/senpi-commands.js +0 -108
  20. package/dist/cli/senpi-commands.js.map +1 -1
  21. package/dist/constants/dsl/index.d.ts +2 -0
  22. package/dist/constants/dsl/index.d.ts.map +1 -1
  23. package/dist/constants/dsl/index.js +2 -0
  24. package/dist/constants/dsl/index.js.map +1 -1
  25. package/dist/dsl/config/parse-tiers.d.ts.map +1 -1
  26. package/dist/dsl/config/parse-tiers.js +6 -2
  27. package/dist/dsl/config/parse-tiers.js.map +1 -1
  28. package/dist/dsl/engine-client/config-mapping.d.ts +7 -0
  29. package/dist/dsl/engine-client/config-mapping.d.ts.map +1 -1
  30. package/dist/dsl/engine-client/config-mapping.js +16 -2
  31. package/dist/dsl/engine-client/config-mapping.js.map +1 -1
  32. package/dist/dsl/events/handlers.d.ts +18 -2
  33. package/dist/dsl/events/handlers.d.ts.map +1 -1
  34. package/dist/dsl/events/handlers.js +302 -213
  35. package/dist/dsl/events/handlers.js.map +1 -1
  36. package/dist/dsl/monitor/create-monitor.d.ts +15 -0
  37. package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
  38. package/dist/dsl/monitor/create-monitor.js +46 -9
  39. package/dist/dsl/monitor/create-monitor.js.map +1 -1
  40. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  41. package/dist/dsl/monitor/process-one-position.js +138 -77
  42. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  43. package/dist/dsl/plugin/index.d.ts +16 -1
  44. package/dist/dsl/plugin/index.d.ts.map +1 -1
  45. package/dist/dsl/plugin/index.js +35 -4
  46. package/dist/dsl/plugin/index.js.map +1 -1
  47. package/dist/dsl/plugin/options.d.ts +18 -0
  48. package/dist/dsl/plugin/options.d.ts.map +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +4 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/runtime/create-scanner-module.d.ts +4 -3
  53. package/dist/runtime/create-scanner-module.d.ts.map +1 -1
  54. package/dist/runtime/create-scanner-module.js +14 -5
  55. package/dist/runtime/create-scanner-module.js.map +1 -1
  56. package/dist/runtime/create-scanner-providers-for-run.d.ts.map +1 -1
  57. package/dist/runtime/create-scanner-providers-for-run.js +12 -1
  58. package/dist/runtime/create-scanner-providers-for-run.js.map +1 -1
  59. package/dist/runtime/load-runtime-config.js +1 -1
  60. package/dist/runtime/load-runtime-config.js.map +1 -1
  61. package/dist/runtime/map-strategies-registration.js +7 -8
  62. package/dist/runtime/map-strategies-registration.js.map +1 -1
  63. package/dist/runtime/map-strategies.d.ts.map +1 -1
  64. package/dist/runtime/map-strategies.js +12 -3
  65. package/dist/runtime/map-strategies.js.map +1 -1
  66. package/dist/runtime/reconcile-decide.d.ts +73 -0
  67. package/dist/runtime/reconcile-decide.d.ts.map +1 -0
  68. package/dist/runtime/reconcile-decide.js +186 -0
  69. package/dist/runtime/reconcile-decide.js.map +1 -0
  70. package/dist/runtime/reconcile-run.d.ts +62 -0
  71. package/dist/runtime/reconcile-run.d.ts.map +1 -0
  72. package/dist/runtime/reconcile-run.js +173 -0
  73. package/dist/runtime/reconcile-run.js.map +1 -0
  74. package/dist/runtime/reconcile-state.d.ts +44 -0
  75. package/dist/runtime/reconcile-state.d.ts.map +1 -1
  76. package/dist/runtime/reconcile-state.js +80 -44
  77. package/dist/runtime/reconcile-state.js.map +1 -1
  78. package/dist/runtime/risk-guard.d.ts.map +1 -1
  79. package/dist/runtime/risk-guard.js +17 -12
  80. package/dist/runtime/risk-guard.js.map +1 -1
  81. package/dist/runtime/runtime-schema.d.ts +2912 -1052
  82. package/dist/runtime/runtime-schema.d.ts.map +1 -1
  83. package/dist/runtime/runtime-schema.js +136 -42
  84. package/dist/runtime/runtime-schema.js.map +1 -1
  85. package/dist/runtime/runtime.d.ts +26 -0
  86. package/dist/runtime/runtime.d.ts.map +1 -1
  87. package/dist/runtime/runtime.js +254 -8
  88. package/dist/runtime/runtime.js.map +1 -1
  89. package/dist/runtime/scanner-registry.d.ts +1 -1
  90. package/dist/runtime/scanner-registry.d.ts.map +1 -1
  91. package/dist/runtime/scanner-registry.js +3 -12
  92. package/dist/runtime/scanner-registry.js.map +1 -1
  93. package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
  94. package/dist/runtime/supervisor/long-running-supervisor.js +1 -1
  95. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  96. package/dist/runtime/supervisor/orphan-sweep.js +1 -1
  97. package/dist/runtime/supervisor/orphan-sweep.js.map +1 -1
  98. package/dist/runtime/supervisor/supervisor-utils.d.ts +1 -1
  99. package/dist/runtime/supervisor/supervisor-utils.d.ts.map +1 -1
  100. package/dist/runtime/supervisor/supervisor-utils.js +1 -1
  101. package/dist/runtime/supervisor/supervisor-utils.js.map +1 -1
  102. package/dist/runtime-api/intake/acceptance-record.d.ts +10 -0
  103. package/dist/runtime-api/intake/acceptance-record.d.ts.map +1 -1
  104. package/dist/runtime-api/intake/acceptance-record.js +28 -0
  105. package/dist/runtime-api/intake/acceptance-record.js.map +1 -1
  106. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  107. package/dist/runtime-api/intake/intake-mount.js +28 -36
  108. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  109. package/dist/runtime-api/intake/process-signal-post.d.ts +20 -1
  110. package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
  111. package/dist/runtime-api/intake/process-signal-post.js +57 -6
  112. package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
  113. package/dist/runtime-api/intake/scanner-launcher.d.ts +18 -4
  114. package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
  115. package/dist/runtime-api/intake/scanner-launcher.js +24 -3
  116. package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
  117. package/dist/runtime-api/intake/types.d.ts +16 -0
  118. package/dist/runtime-api/intake/types.d.ts.map +1 -1
  119. package/dist/runtime-api/routes/signals.schema.d.ts +4 -0
  120. package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
  121. package/dist/runtime-api/routes/signals.schema.js +12 -0
  122. package/dist/runtime-api/routes/signals.schema.js.map +1 -1
  123. package/dist/scanners/external/scaffold/__main__.py +65 -24
  124. package/dist/scanners/external/scaffold/ctx.py +78 -0
  125. package/dist/scanners/external/scaffold/delivery.py +56 -22
  126. package/dist/scanners/external/scaffold/entrypoint_loader.py +42 -48
  127. package/dist/scanners/external/scaffold/journal.py +13 -2
  128. package/dist/scanners/external/scaffold/requirements.txt +11 -0
  129. package/dist/scanners/external/scaffold/scaffold.py +159 -128
  130. package/dist/scanners/external/scaffold/senpi_mcp.py +250 -0
  131. package/dist/scanners/external/scaffold/signal_envelope.py +177 -34
  132. package/dist/scanners/external/scaffold/state_manager.py +175 -0
  133. package/dist/scanners/external-scanner-receiver.d.ts +6 -0
  134. package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
  135. package/dist/scanners/external-scanner-receiver.js +14 -1
  136. package/dist/scanners/external-scanner-receiver.js.map +1 -1
  137. package/dist/scripts/run-dsl-standalone-live.d.ts +1 -1
  138. package/dist/scripts/run-dsl-standalone-live.js +5 -3
  139. package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
  140. package/dist/senpi/client.d.ts +37 -4
  141. package/dist/senpi/client.d.ts.map +1 -1
  142. package/dist/senpi/client.js +101 -24
  143. package/dist/senpi/client.js.map +1 -1
  144. package/dist/state/state-manager.d.ts +17 -0
  145. package/dist/state/state-manager.d.ts.map +1 -1
  146. package/dist/state/state-manager.js +50 -4
  147. package/dist/state/state-manager.js.map +1 -1
  148. package/dist/strategy/strategy-state.d.ts +9 -1
  149. package/dist/strategy/strategy-state.d.ts.map +1 -1
  150. package/dist/strategy/strategy-state.js +62 -9
  151. package/dist/strategy/strategy-state.js.map +1 -1
  152. package/dist/tracking/constants.d.ts.map +1 -1
  153. package/dist/tracking/constants.js +2 -0
  154. package/dist/tracking/constants.js.map +1 -1
  155. package/dist/types/dsl/index.d.ts +1 -1
  156. package/dist/types/dsl/index.d.ts.map +1 -1
  157. package/dist/types/scanner.d.ts +7 -0
  158. package/dist/types/scanner.d.ts.map +1 -1
  159. package/dist/types/strategy.d.ts +3 -1
  160. package/dist/types/strategy.d.ts.map +1 -1
  161. package/dist/utils/keyed-mutex.d.ts +37 -0
  162. package/dist/utils/keyed-mutex.d.ts.map +1 -0
  163. package/dist/utils/keyed-mutex.js +77 -0
  164. package/dist/utils/keyed-mutex.js.map +1 -0
  165. package/examples/strategies/README.md +17 -12
  166. package/examples/strategies/iguana/README.md +135 -0
  167. package/examples/strategies/iguana/recipe.yaml +137 -0
  168. package/examples/strategies/iguana/scanners/scan.py +243 -0
  169. package/examples/strategies/iguana/scanners/scoring.py +152 -0
  170. package/examples/strategies/raptor/README.md +156 -0
  171. package/examples/strategies/raptor/recipe.yaml +154 -0
  172. package/examples/strategies/raptor/scanners/scan.py +349 -0
  173. package/examples/strategies/raptor/scanners/scoring.py +452 -0
  174. package/examples/strategies/spider/README.md +89 -0
  175. package/examples/strategies/spider/scalp/recipe.yaml +129 -0
  176. package/examples/strategies/spider/scalp/scanners/scan.py +219 -0
  177. package/examples/strategies/spider/scalp/scanners/scoring.py +179 -0
  178. package/examples/strategies/spider/swing/recipe.yaml +137 -0
  179. package/examples/strategies/spider/swing/scanners/scan.py +334 -0
  180. package/examples/strategies/spider/swing/scanners/scoring.py +184 -0
  181. package/examples/strategies/turbine/README.md +166 -0
  182. package/examples/strategies/turbine/recipe-runners.yaml +123 -0
  183. package/examples/strategies/turbine/recipe-volume.yaml +129 -0
  184. package/examples/strategies/turbine/scanners/scan.py +283 -0
  185. package/examples/strategies/turbine/scanners/scoring.py +190 -0
  186. package/openclaw.plugin.json +1 -1
  187. package/package.json +1 -1
  188. package/dist/scanners/external/liquidation-watchdog/README.md +0 -107
  189. package/dist/scanners/external/liquidation-watchdog/liquidation-watchdog.yaml +0 -48
  190. package/dist/scanners/external/liquidation-watchdog/producer.mjs +0 -336
  191. package/dist/scanners/external/momentum/README.md +0 -148
  192. package/dist/scanners/external/momentum/momentum.yaml +0 -116
  193. package/dist/scanners/external/momentum/producer.mjs +0 -251
  194. package/dist/scanners/external/shared/ingest.mjs +0 -112
  195. package/examples/README.md +0 -89
  196. package/examples/scanners-consumer-quickstart.mjs +0 -134
  197. package/examples/scanners-emerging-movers-v4.mjs +0 -287
  198. package/examples/scanners-liquidation-watchdog.mjs +0 -341
  199. package/examples/scanners-live-mcp.mjs +0 -208
  200. package/examples/scanners-multi-strategy.mjs +0 -165
  201. package/examples/scanners-position-tracker-e2e.mjs +0 -225
  202. package/examples/scanners-scheduled-events.mjs +0 -96
  203. package/examples/scanners-senpi-provider-client.mjs +0 -182
  204. package/examples/scanners-single-strategy.mjs +0 -198
  205. package/examples/strategies/dsl-showcase.yaml +0 -68
  206. package/examples/strategies/dsl.yaml +0 -51
  207. package/examples/strategies/fox-minimal-loosened.yaml +0 -240
  208. package/examples/strategies/fox-minimal.yaml +0 -242
  209. package/examples/strategies/minimal.yaml +0 -46
  210. package/examples/strategies/momentum/how-it-works.md +0 -48
  211. package/examples/strategies/momentum/momentum-guarded.yaml +0 -161
  212. package/examples/strategies/risk-showcase.yaml +0 -93
  213. package/examples/strategies/viper.yaml +0 -67
  214. package/examples/strategies/wolf.yaml +0 -72
  215. package/examples/yamls/sample-with-dsl-and-emerging-movers.yaml +0 -123
@@ -1,341 +0,0 @@
1
- /**
2
- * Liquidation-watchdog scanner e2e examples.
3
- *
4
- * Run: node examples/scanners-liquidation-watchdog.mjs
5
- *
6
- * Two scenarios:
7
- * 1. Without DSL states — scanner operates with margin/liq checks only.
8
- * 2. With DSL states — scanner enriches liq-distance alerts with DSL floor info.
9
- */
10
-
11
- import { mkdtemp, rm } from "node:fs/promises";
12
- import { tmpdir } from "node:os";
13
- import { join } from "node:path";
14
- import {
15
- createScannerRuntimeModule,
16
- buildCompositionFromScanners,
17
- liquidationWatchdogScanner,
18
- } from "../dist/scanners/index.js";
19
- import { StateManager } from "../dist/state/index.js";
20
-
21
- // ─── Shared fixtures ────────────────────────────────────────────────────────
22
-
23
- function makeClearingHouseState({ bufferPct, positions }) {
24
- // bufferPct controls how much margin room is left:
25
- // buffer = (withdrawable / accountValue) * 100
26
- // → withdrawable = accountValue * (bufferPct / 100)
27
- const accountValue = 10_000;
28
- const withdrawable = accountValue * (bufferPct / 100);
29
- const totalMarginUsed = accountValue - withdrawable;
30
- return {
31
- accountValue,
32
- withdrawable,
33
- totalMarginUsed,
34
- totalUnrealizedPnl: 0,
35
- totalNtlPos: 0,
36
- positions: positions ?? [],
37
- };
38
- }
39
-
40
- function makePosition({ asset, direction, markPrice, liqPrice, roe, marginType }) {
41
- // Scanner expects StrategyOpenPosition from types/strategy.ts:
42
- // coin, dex, szi, entryPx, leverage, leverageType, unrealizedPnl, marginUsed, liquidationPx, returnOnEquity
43
- // szi sign encodes direction: positive = LONG, negative = SHORT
44
- // returnOnEquity is a ratio (e.g. 0.05 for 5%), scanner multiplies by 100
45
- const dir = direction ?? "LONG";
46
- const szi = dir === "SHORT" ? -1 : 1;
47
- const entryPx = markPrice * 0.95;
48
- const unrealizedPnl = (markPrice - entryPx) * szi;
49
- return {
50
- coin: asset,
51
- dex: "",
52
- szi,
53
- entryPx,
54
- leverage: 5,
55
- leverageType: marginType ?? "cross",
56
- unrealizedPnl,
57
- marginUsed: entryPx / 5,
58
- liquidationPx: liqPrice ?? null,
59
- returnOnEquity: (roe ?? 5) / 100,
60
- };
61
- }
62
-
63
- // ─── Scenario 1: Without DSL states ─────────────────────────────────────────
64
-
65
- async function scenarioWithoutDslStates() {
66
- console.log("\n=== Scenario 1: Without DSL states ===\n");
67
-
68
- const stateDir = await mkdtemp(join(tmpdir(), "watchdog-no-dsl-"));
69
-
70
- // Two runs: first healthy, second critical
71
- let run = 0;
72
- const clearingHouseRuns = [
73
- // Run 1: healthy — 80% buffer, positions far from liq
74
- makeClearingHouseState({
75
- bufferPct: 80,
76
- positions: [
77
- makePosition({ asset: "BTC", markPrice: 60000, liqPrice: 20000, roe: 12 }),
78
- makePosition({ asset: "ETH", markPrice: 3000, liqPrice: 1000, roe: 8 }),
79
- ],
80
- }),
81
- // Run 2: critical — 20% buffer, one position with bad ROE and tight liq
82
- makeClearingHouseState({
83
- bufferPct: 20,
84
- positions: [
85
- makePosition({ asset: "BTC", markPrice: 60000, liqPrice: 20000, roe: 5 }),
86
- makePosition({ asset: "ETH", markPrice: 3000, liqPrice: 2700, roe: -25, marginType: "cross" }),
87
- ],
88
- }),
89
- ];
90
-
91
- const module = createScannerRuntimeModule({
92
- stateDir,
93
- ...buildCompositionFromScanners([liquidationWatchdogScanner]),
94
- strategies: [
95
- {
96
- id: "wallet-001",
97
- name: "Trader-A",
98
- scannerOverrides: {
99
- "liquidation-watchdog": {
100
- config: {
101
- bufferWarningPct: 50,
102
- bufferCriticalPct: 30,
103
- liqDistanceWarningPct: 30,
104
- roeDangerPct: -15,
105
- },
106
- },
107
- },
108
- },
109
- ],
110
- providers: {
111
- getClearingHouseState: async () => clearingHouseRuns[Math.min(run, clearingHouseRuns.length - 1)],
112
- getActivePositions: async () => [],
113
- getInstruments: async () => [],
114
- // No getDslStatesForStrategy — scanner should handle gracefully
115
- },
116
- });
117
-
118
- try {
119
- // Run 1: healthy
120
- run = 0;
121
- const result1 = await module.runOnce("wallet-001", "liquidation-watchdog");
122
- console.log("Run 1 (healthy):");
123
- console.log(" scannedCount:", result1.scannedCount);
124
- console.log(" signals:", result1.signals.length);
125
- console.log(" summary.marginBuffer:", result1.summary.marginBuffer);
126
- console.log(" summary.alertCount:", result1.summary.alertCount);
127
- console.log(" summary.consecutiveCleanChecks:", result1.summary.consecutiveCleanChecks);
128
- console.assert(result1.signals.length === 0, "Expected 0 signals for healthy state");
129
- console.assert(result1.summary.consecutiveCleanChecks === 1, "Expected 1 clean check");
130
-
131
- // Run 2: critical buffer + tight liq + bad ROE
132
- run = 1;
133
- const result2 = await module.runOnce("wallet-001", "liquidation-watchdog");
134
- console.log("\nRun 2 (critical):");
135
- console.log(" scannedCount:", result2.scannedCount);
136
- console.log(" signals:", result2.signals.length);
137
- console.log(" summary.marginBuffer:", result2.summary.marginBuffer);
138
- console.log(" summary.alertCount:", result2.summary.alertCount);
139
- console.log(" summary.consecutiveCleanChecks:", result2.summary.consecutiveCleanChecks);
140
-
141
- const closeSignals = result2.signals.filter((s) => s.signalType === "WATCHDOG_CLOSE");
142
- const alertSignals = result2.signals.filter((s) => s.signalType === "WATCHDOG_ALERT");
143
- console.log(" WATCHDOG_CLOSE signals:", closeSignals.length);
144
- console.log(" WATCHDOG_ALERT signals:", alertSignals.length);
145
-
146
- if (closeSignals.length > 0) {
147
- console.log(" Close target asset:", closeSignals[0].asset, "(lowest ROE)");
148
- console.log(" Close meta:", JSON.stringify(closeSignals[0].meta, null, 2));
149
- }
150
-
151
- // Verify no alert message contains "DSL floor" since no DSL states were provided
152
- const allMessages = alertSignals.map((s) => s.meta.message);
153
- const hasDslFloorNote = allMessages.some((m) => typeof m === "string" && m.includes("DSL floor"));
154
- console.assert(!hasDslFloorNote, "Expected no DSL floor notes without dslStates provider");
155
- console.assert(closeSignals.length >= 1, "Expected at least 1 WATCHDOG_CLOSE signal");
156
- console.assert(closeSignals[0]?.asset === "ETH", "Expected ETH (lowest ROE) as close target");
157
- console.assert(result2.summary.consecutiveCleanChecks === 0, "Expected 0 clean checks after alert");
158
-
159
- console.log("\n [PASS] Scenario 1 completed successfully");
160
- } finally {
161
- await module.stop();
162
- await rm(stateDir, { recursive: true, force: true });
163
- }
164
- }
165
-
166
- // ─── Scenario 2: With DSL states from StateManager ──────────────────────────
167
-
168
- async function scenarioWithDslStates() {
169
- console.log("\n=== Scenario 2: With DSL states from StateManager ===\n");
170
-
171
- const stateDir = await mkdtemp(join(tmpdir(), "watchdog-with-dsl-"));
172
-
173
- // Seed the StateManager with DSL state files (as the DSL plugin would)
174
- const stateManager = new StateManager(stateDir);
175
-
176
- const baseDslState = {
177
- version: 1,
178
- address: "wallet-002",
179
- wallet: "wallet-002",
180
- dex: "",
181
- highWaterPrice: 0,
182
- hwTimestamp: new Date().toISOString(),
183
- currentBreachCount: 0,
184
- currentTierIndex: null,
185
- tierFloorPrice: 0,
186
- tiers: [],
187
- phase1: { retraceThreshold: 5, absoluteFloor: 0, consecutiveBreachesRequired: 3 },
188
- phase2: { retraceFromHW: 4, consecutiveBreachesRequired: 2 },
189
- phase2TriggerTier: 0,
190
- slOrderId: null,
191
- lastSyncedFloorPrice: null,
192
- slOrderIdUpdatedAt: null,
193
- lastSlSyncError: null,
194
- pendingClose: false,
195
- active: true,
196
- createdAt: new Date().toISOString(),
197
- lastCheck: new Date().toISOString(),
198
- consecutiveFetchFailures: 0,
199
- tickCount: 0,
200
- peakROE: 0,
201
- lastError: null,
202
- lastErrorAt: null,
203
- closedAt: null,
204
- closeReason: null,
205
- };
206
-
207
- await stateManager.setDslState("wallet-002", "BTC", "", {
208
- ...baseDslState,
209
- asset: "BTC",
210
- direction: "LONG",
211
- entryPrice: 58000,
212
- size: 1,
213
- leverage: 5,
214
- highWaterPrice: 62000,
215
- phase: 2,
216
- currentTierIndex: 1,
217
- tierFloorPrice: 57000,
218
- floorPrice: 56500, // DSL floor for BTC
219
- lastPrice: 60000,
220
- peakROE: 15,
221
- tickCount: 100,
222
- });
223
-
224
- await stateManager.setDslState("wallet-002", "SOL", "", {
225
- ...baseDslState,
226
- asset: "SOL",
227
- direction: "LONG",
228
- entryPrice: 140,
229
- size: 10,
230
- leverage: 5,
231
- highWaterPrice: 155,
232
- phase: 1,
233
- tierFloorPrice: 135,
234
- floorPrice: 133, // DSL floor for SOL
235
- lastPrice: 150,
236
- peakROE: 10,
237
- tickCount: 50,
238
- });
239
-
240
- // Verify StateManager has the states
241
- const loaded = await stateManager.listDslStates("wallet-002");
242
- console.log(" StateManager DSL states loaded:", loaded.length);
243
- console.assert(loaded.length === 2, "Expected 2 DSL states in StateManager");
244
-
245
- // Critical clearinghouse scenario
246
- const clearingHouse = makeClearingHouseState({
247
- bufferPct: 25, // CRITICAL
248
- positions: [
249
- makePosition({ asset: "BTC", markPrice: 60000, liqPrice: 54000, roe: 3 }), // 10% liq dist
250
- makePosition({ asset: "SOL", markPrice: 150, liqPrice: 130, roe: -18 }), // ~13.3% liq dist
251
- ],
252
- });
253
-
254
- // Wire getDslStatesForStrategy to read from the StateManager — same as Runtime does
255
- const module = createScannerRuntimeModule({
256
- stateDir,
257
- ...buildCompositionFromScanners([liquidationWatchdogScanner]),
258
- strategies: [
259
- {
260
- id: "wallet-002",
261
- name: "Trader-B",
262
- scannerOverrides: {
263
- "liquidation-watchdog": {
264
- config: {
265
- bufferWarningPct: 50,
266
- bufferCriticalPct: 30,
267
- liqDistanceWarningPct: 30,
268
- roeDangerPct: -15,
269
- },
270
- },
271
- },
272
- },
273
- ],
274
- providers: {
275
- getClearingHouseState: async () => clearingHouse,
276
- getActivePositions: async () => [],
277
- getInstruments: async () => [],
278
- getDslStatesForStrategy: async (address) => stateManager.listDslStates(address),
279
- },
280
- });
281
-
282
- try {
283
- const result = await module.runOnce("wallet-002", "liquidation-watchdog");
284
-
285
- console.log("Run (critical + DSL from StateManager):");
286
- console.log(" scannedCount:", result.scannedCount);
287
- console.log(" signals:", result.signals.length);
288
- console.log(" summary.marginBuffer:", result.summary.marginBuffer);
289
- console.log(" summary.alertCount:", result.summary.alertCount);
290
-
291
- const closeSignals = result.signals.filter((s) => s.signalType === "WATCHDOG_CLOSE");
292
- const alertSignals = result.signals.filter((s) => s.signalType === "WATCHDOG_ALERT");
293
- console.log(" WATCHDOG_CLOSE signals:", closeSignals.length);
294
- console.log(" WATCHDOG_ALERT signals:", alertSignals.length);
295
-
296
- if (closeSignals.length > 0) {
297
- console.log(" Close target:", closeSignals[0].asset, "ROE:", closeSignals[0].meta.roe);
298
- }
299
-
300
- // Check that liq-distance alerts include DSL floor notes from StateManager
301
- const liqAlertMessages = alertSignals
302
- .filter((s) => typeof s.meta.message === "string" && s.meta.message.includes("Liquidation distance"))
303
- .map((s) => s.meta.message);
304
-
305
- console.log("\n Liquidation distance alert messages:");
306
- for (const msg of liqAlertMessages) {
307
- console.log(" -", msg);
308
- }
309
-
310
- const hasDslFloorNote = liqAlertMessages.some((m) => m.includes("DSL floor"));
311
- console.assert(hasDslFloorNote, "Expected at least one liq alert with DSL floor note");
312
- console.assert(closeSignals.length >= 1, "Expected WATCHDOG_CLOSE on critical buffer");
313
- console.assert(closeSignals[0]?.asset === "SOL", "Expected SOL (lowest ROE -18) as close target");
314
-
315
- // Verify BTC alert includes DSL floor $56500.00 (from StateManager)
316
- const btcAlert = liqAlertMessages.find((m) => m.includes("BTC"));
317
- console.assert(btcAlert?.includes("DSL floor: $56500.00"), "Expected BTC alert to contain DSL floor $56500.00 from StateManager");
318
-
319
- // Verify SOL alert includes DSL floor $133.00 (from StateManager)
320
- const solAlert = liqAlertMessages.find((m) => m.includes("SOL"));
321
- console.assert(solAlert?.includes("DSL floor: $133.00"), "Expected SOL alert to contain DSL floor $133.00 from StateManager");
322
-
323
- console.log("\n [PASS] Scenario 2 completed successfully");
324
- } finally {
325
- await module.stop();
326
- await rm(stateDir, { recursive: true, force: true });
327
- }
328
- }
329
-
330
- // ─── Main ───────────────────────────────────────────────────────────────────
331
-
332
- async function main() {
333
- await scenarioWithoutDslStates();
334
- await scenarioWithDslStates();
335
- console.log("\nAll scenarios passed.\n");
336
- }
337
-
338
- main().catch((error) => {
339
- console.error(error);
340
- process.exit(1);
341
- });
@@ -1,208 +0,0 @@
1
- /**
2
- * Live MCP example — connects to the real Senpi MCP server and runs all
3
- * four built-in scanners against live market data.
4
- *
5
- * Requires two environment variables:
6
- *
7
- * export SENPI_API_KEY="your-api-key"
8
- * export SENPI_MCP_URL="https://mcp.prod.senpi.ai/mcp"
9
- * node examples/scanners-live-mcp.mjs
10
- */
11
-
12
- import { mkdtemp, rm } from "node:fs/promises";
13
- import { tmpdir } from "node:os";
14
- import { join } from "node:path";
15
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
16
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
17
- import {
18
- createScannerRuntimeModule,
19
- createSenpiScannerProviders,
20
- prescreenerScanner,
21
- oiTrackerScanner,
22
- momentumScanner,
23
- emergingMoversScanner,
24
- } from "../dist/scanners/index.js";
25
-
26
- // ─── Env guard ──────────────────────────────────────────────────────────────
27
-
28
- const SENPI_API_KEY = process.env.SENPI_API_KEY;
29
- const SENPI_MCP_URL = process.env.SENPI_MCP_URL;
30
-
31
- if (!SENPI_API_KEY || !SENPI_MCP_URL) {
32
- console.error(
33
- "Missing required env vars. Set SENPI_API_KEY and SENPI_MCP_URL before running.",
34
- );
35
- process.exit(1);
36
- }
37
-
38
- // ─── MCP → SenpiToolClient bridge ──────────────────────────────────────────
39
-
40
- /**
41
- * Wraps the MCP SDK `Client` so it satisfies the `SenpiToolClient` interface
42
- * expected by `createSenpiScannerProviders`.
43
- *
44
- * The MCP `callTool` returns `{ content: [{ type: "text", text: "…" }] }`.
45
- * This bridge parses the JSON from the first text content item back into
46
- * a plain object.
47
- */
48
- function createSenpiToolClient(mcpClient) {
49
- return {
50
- async callTool(name, args) {
51
- const result = await mcpClient.callTool({ name, arguments: args });
52
-
53
- if (result.isError) {
54
- const errorText =
55
- Array.isArray(result.content) &&
56
- result.content.length > 0 &&
57
- result.content[0].type === "text"
58
- ? result.content[0].text
59
- : JSON.stringify(result.content);
60
- throw new Error(`MCP tool "${name}" returned error: ${errorText}`);
61
- }
62
-
63
- // Prefer structuredContent when available.
64
- if (result.structuredContent !== undefined) {
65
- return result.structuredContent;
66
- }
67
-
68
- // Fall back to parsing the first text content item.
69
- if (Array.isArray(result.content) && result.content.length > 0) {
70
- const first = result.content[0];
71
- if (first.type === "text") {
72
- try {
73
- return JSON.parse(first.text);
74
- } catch {
75
- return first.text;
76
- }
77
- }
78
- }
79
-
80
- return result.content;
81
- },
82
- };
83
- }
84
-
85
- // ─── Main ───────────────────────────────────────────────────────────────────
86
-
87
- async function main() {
88
- // 1. Connect to the Senpi MCP server.
89
- console.log("Connecting to", SENPI_MCP_URL, "...");
90
- const transport = new StreamableHTTPClientTransport(new URL(SENPI_MCP_URL), {
91
- requestInit: {
92
- headers: { Authorization: `Bearer ${SENPI_API_KEY}` },
93
- },
94
- });
95
- const mcpClient = new Client({ name: "scanners-live-mcp", version: "0.0.1" });
96
- await mcpClient.connect(transport);
97
- console.log("Connected.\n");
98
-
99
- // 2. Wire up scanner providers backed by the real MCP client.
100
- const providers = createSenpiScannerProviders({
101
- client: createSenpiToolClient(mcpClient),
102
- logLevel: "info",
103
- maxCandleAssets: 5,
104
- candleIntervals: ["1h"],
105
- cacheTtlMs: { instruments: 0, smData: 0, candles: 0 },
106
- timeouts: {
107
- marketListInstruments: 30_000,
108
- marketGetAssetData: 30_000,
109
- leaderboardGetMarkets: 30_000,
110
- },
111
- retries: 2,
112
- });
113
-
114
- // 3. Create the runtime module.
115
- const stateDir = await mkdtemp(join(tmpdir(), "scanners-live-mcp-"));
116
- const mod = createScannerRuntimeModule({
117
- stateDir,
118
- scanners: [prescreenerScanner, oiTrackerScanner, momentumScanner, emergingMoversScanner],
119
- strategies: [
120
- {
121
- id: "live-demo",
122
- name: "Live Demo",
123
- scannerOverrides: {
124
- momentum: {
125
- config: {
126
- timeframe: "1h",
127
- minMovePct: 0.5,
128
- maxCandidates: 5,
129
- minDayVolume: 1_000_000,
130
- minSignalScore: 0.1,
131
- requirePrescreened: true,
132
- },
133
- },
134
- "emerging-movers": {
135
- config: {
136
- topN: 10,
137
- minRankJump: 1,
138
- minTopTradersGain: 0.05,
139
- minScansBeforeSignals: 2,
140
- historyLimit: 30,
141
- },
142
- },
143
- },
144
- },
145
- ],
146
- providers,
147
- });
148
-
149
- try {
150
- // 4. Run scanners in order.
151
- console.log("── prescreener ──");
152
- const prescreener = await mod.runOnce("live-demo", "prescreener");
153
- console.log(" status:", prescreener.status);
154
- console.log(" scanned:", prescreener.scannedCount);
155
- console.log(" summary:", prescreener.summary);
156
- console.log();
157
-
158
- console.log("── oi-tracker ──");
159
- const oiTracker = await mod.runOnce("live-demo", "oi-tracker");
160
- console.log(" status:", oiTracker.status);
161
- console.log(" scanned:", oiTracker.scannedCount);
162
- console.log(" summary:", oiTracker.summary);
163
- console.log();
164
-
165
- console.log("── momentum ──");
166
- const momentum = await mod.runOnce("live-demo", "momentum");
167
- console.log(" status:", momentum.status);
168
- console.log(" scanned:", momentum.scannedCount);
169
- console.log(" signals:", momentum.signals.length);
170
- for (const sig of momentum.signals) {
171
- console.log(` ${sig.direction} ${sig.asset} score=${sig.score.toFixed(2)} change=${sig.meta.changePct}%`);
172
- }
173
- console.log();
174
-
175
- console.log("── emerging-movers (run 1 — warmup) ──");
176
- const em1 = await mod.runOnce("live-demo", "emerging-movers");
177
- console.log(" status:", em1.status);
178
- console.log(" scanned:", em1.scannedCount);
179
- console.log();
180
-
181
- console.log("── emerging-movers (run 2 — can signal) ──");
182
- const em2 = await mod.runOnce("live-demo", "emerging-movers");
183
- console.log(" status:", em2.status);
184
- console.log(" scanned:", em2.scannedCount);
185
- console.log(" signals:", em2.signals.length);
186
- for (const sig of em2.signals) {
187
- console.log(` ${sig.direction} ${sig.asset} score=${sig.score.toFixed(2)} jump=${sig.meta.jump}`);
188
- }
189
- console.log();
190
-
191
- // 5. Query persisted signals across all scanners.
192
- const allSignals = await mod.querySignals({ strategyId: "live-demo" });
193
- console.log("── querySignals ──");
194
- console.log(" total persisted:", allSignals.length);
195
- for (const sig of allSignals) {
196
- console.log(` [${sig.scannerId}] ${sig.direction} ${sig.asset} score=${sig.score.toFixed(2)}`);
197
- }
198
- } finally {
199
- await mod.stop();
200
- await transport.close();
201
- await rm(stateDir, { recursive: true, force: true });
202
- }
203
- }
204
-
205
- main().catch((error) => {
206
- console.error(error);
207
- process.exit(1);
208
- });