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

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 (196) hide show
  1. package/README.md +9 -10
  2. package/dist/actions/decision-engine.d.ts.map +1 -1
  3. package/dist/actions/decision-engine.js +0 -3
  4. package/dist/actions/decision-engine.js.map +1 -1
  5. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  6. package/dist/actions/open-position/open-position.action.js +2 -6
  7. package/dist/actions/open-position/open-position.action.js.map +1 -1
  8. package/dist/cli/guide-commands.d.ts.map +1 -1
  9. package/dist/cli/guide-commands.js +56 -43
  10. package/dist/cli/guide-commands.js.map +1 -1
  11. package/dist/cli/senpi-commands.d.ts.map +1 -1
  12. package/dist/cli/senpi-commands.js +108 -0
  13. package/dist/cli/senpi-commands.js.map +1 -1
  14. package/dist/constants/dsl/index.d.ts +0 -2
  15. package/dist/constants/dsl/index.d.ts.map +1 -1
  16. package/dist/constants/dsl/index.js +0 -2
  17. package/dist/constants/dsl/index.js.map +1 -1
  18. package/dist/dsl/engine-client/config-mapping.d.ts +0 -7
  19. package/dist/dsl/engine-client/config-mapping.d.ts.map +1 -1
  20. package/dist/dsl/engine-client/config-mapping.js +2 -16
  21. package/dist/dsl/engine-client/config-mapping.js.map +1 -1
  22. package/dist/dsl/events/handlers.d.ts +2 -18
  23. package/dist/dsl/events/handlers.d.ts.map +1 -1
  24. package/dist/dsl/events/handlers.js +213 -302
  25. package/dist/dsl/events/handlers.js.map +1 -1
  26. package/dist/dsl/monitor/create-monitor.d.ts +0 -15
  27. package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
  28. package/dist/dsl/monitor/create-monitor.js +9 -44
  29. package/dist/dsl/monitor/create-monitor.js.map +1 -1
  30. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  31. package/dist/dsl/monitor/process-one-position.js +1 -24
  32. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  33. package/dist/dsl/plugin/index.d.ts +1 -16
  34. package/dist/dsl/plugin/index.d.ts.map +1 -1
  35. package/dist/dsl/plugin/index.js +4 -35
  36. package/dist/dsl/plugin/index.js.map +1 -1
  37. package/dist/dsl/plugin/options.d.ts +0 -18
  38. package/dist/dsl/plugin/options.d.ts.map +1 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +0 -4
  41. package/dist/index.js.map +1 -1
  42. package/dist/runtime/create-scanner-module.d.ts +3 -4
  43. package/dist/runtime/create-scanner-module.d.ts.map +1 -1
  44. package/dist/runtime/create-scanner-module.js +5 -14
  45. package/dist/runtime/create-scanner-module.js.map +1 -1
  46. package/dist/runtime/create-scanner-providers-for-run.d.ts.map +1 -1
  47. package/dist/runtime/create-scanner-providers-for-run.js +1 -12
  48. package/dist/runtime/create-scanner-providers-for-run.js.map +1 -1
  49. package/dist/runtime/load-runtime-config.js +1 -1
  50. package/dist/runtime/load-runtime-config.js.map +1 -1
  51. package/dist/runtime/map-strategies-registration.js +8 -7
  52. package/dist/runtime/map-strategies-registration.js.map +1 -1
  53. package/dist/runtime/map-strategies.d.ts.map +1 -1
  54. package/dist/runtime/map-strategies.js +3 -12
  55. package/dist/runtime/map-strategies.js.map +1 -1
  56. package/dist/runtime/reconcile-state.d.ts +0 -44
  57. package/dist/runtime/reconcile-state.d.ts.map +1 -1
  58. package/dist/runtime/reconcile-state.js +44 -80
  59. package/dist/runtime/reconcile-state.js.map +1 -1
  60. package/dist/runtime/risk-guard.d.ts.map +1 -1
  61. package/dist/runtime/risk-guard.js +12 -17
  62. package/dist/runtime/risk-guard.js.map +1 -1
  63. package/dist/runtime/runtime-schema.d.ts +995 -2855
  64. package/dist/runtime/runtime-schema.d.ts.map +1 -1
  65. package/dist/runtime/runtime-schema.js +40 -133
  66. package/dist/runtime/runtime-schema.js.map +1 -1
  67. package/dist/runtime/runtime.d.ts +0 -26
  68. package/dist/runtime/runtime.d.ts.map +1 -1
  69. package/dist/runtime/runtime.js +5 -233
  70. package/dist/runtime/runtime.js.map +1 -1
  71. package/dist/runtime/scanner-registry.d.ts +1 -1
  72. package/dist/runtime/scanner-registry.d.ts.map +1 -1
  73. package/dist/runtime/scanner-registry.js +12 -3
  74. package/dist/runtime/scanner-registry.js.map +1 -1
  75. package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
  76. package/dist/runtime/supervisor/long-running-supervisor.js +1 -1
  77. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  78. package/dist/runtime/supervisor/orphan-sweep.js +1 -1
  79. package/dist/runtime/supervisor/orphan-sweep.js.map +1 -1
  80. package/dist/runtime/supervisor/supervisor-utils.d.ts +1 -1
  81. package/dist/runtime/supervisor/supervisor-utils.d.ts.map +1 -1
  82. package/dist/runtime/supervisor/supervisor-utils.js +1 -1
  83. package/dist/runtime/supervisor/supervisor-utils.js.map +1 -1
  84. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  85. package/dist/runtime-api/intake/intake-mount.js +36 -28
  86. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  87. package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
  88. package/dist/runtime-api/intake/process-signal-post.js +0 -0
  89. package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
  90. package/dist/runtime-api/intake/scanner-launcher.d.ts +4 -18
  91. package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
  92. package/dist/runtime-api/intake/scanner-launcher.js +3 -24
  93. package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
  94. package/dist/runtime-api/intake/types.d.ts +0 -6
  95. package/dist/runtime-api/intake/types.d.ts.map +1 -1
  96. package/dist/runtime-api/routes/signals.schema.d.ts +0 -1
  97. package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
  98. package/dist/runtime-api/routes/signals.schema.js +0 -5
  99. package/dist/runtime-api/routes/signals.schema.js.map +1 -1
  100. package/dist/scanners/external/liquidation-watchdog/README.md +107 -0
  101. package/dist/scanners/external/liquidation-watchdog/liquidation-watchdog.yaml +48 -0
  102. package/dist/scanners/external/liquidation-watchdog/producer.mjs +336 -0
  103. package/dist/scanners/external/momentum/README.md +148 -0
  104. package/dist/scanners/external/momentum/momentum.yaml +116 -0
  105. package/dist/scanners/external/momentum/producer.mjs +251 -0
  106. package/dist/scanners/external/scaffold/__main__.py +24 -65
  107. package/dist/scanners/external/scaffold/delivery.py +22 -56
  108. package/dist/scanners/external/scaffold/entrypoint_loader.py +48 -42
  109. package/dist/scanners/external/scaffold/journal.py +2 -13
  110. package/dist/scanners/external/scaffold/scaffold.py +128 -159
  111. package/dist/scanners/external/scaffold/signal_envelope.py +34 -177
  112. package/dist/scanners/external/shared/ingest.mjs +112 -0
  113. package/dist/scanners/external-scanner-receiver.d.ts +0 -6
  114. package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
  115. package/dist/scanners/external-scanner-receiver.js +1 -14
  116. package/dist/scanners/external-scanner-receiver.js.map +1 -1
  117. package/dist/scripts/run-dsl-standalone-live.d.ts +1 -1
  118. package/dist/scripts/run-dsl-standalone-live.js +3 -5
  119. package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
  120. package/dist/senpi/client.d.ts +0 -12
  121. package/dist/senpi/client.d.ts.map +1 -1
  122. package/dist/senpi/client.js +2 -31
  123. package/dist/senpi/client.js.map +1 -1
  124. package/dist/state/state-manager.d.ts +0 -17
  125. package/dist/state/state-manager.d.ts.map +1 -1
  126. package/dist/state/state-manager.js +0 -28
  127. package/dist/state/state-manager.js.map +1 -1
  128. package/dist/strategy/strategy-state.d.ts.map +1 -1
  129. package/dist/strategy/strategy-state.js +1 -7
  130. package/dist/strategy/strategy-state.js.map +1 -1
  131. package/dist/tracking/constants.d.ts.map +1 -1
  132. package/dist/tracking/constants.js +0 -2
  133. package/dist/tracking/constants.js.map +1 -1
  134. package/dist/types/dsl/index.d.ts +1 -1
  135. package/dist/types/dsl/index.d.ts.map +1 -1
  136. package/dist/types/scanner.d.ts +0 -7
  137. package/dist/types/scanner.d.ts.map +1 -1
  138. package/examples/README.md +89 -0
  139. package/examples/scanners-consumer-quickstart.mjs +134 -0
  140. package/examples/scanners-emerging-movers-v4.mjs +287 -0
  141. package/examples/scanners-liquidation-watchdog.mjs +341 -0
  142. package/examples/scanners-live-mcp.mjs +208 -0
  143. package/examples/scanners-multi-strategy.mjs +165 -0
  144. package/examples/scanners-position-tracker-e2e.mjs +225 -0
  145. package/examples/scanners-scheduled-events.mjs +96 -0
  146. package/examples/scanners-senpi-provider-client.mjs +182 -0
  147. package/examples/scanners-single-strategy.mjs +198 -0
  148. package/examples/strategies/README.md +12 -17
  149. package/examples/strategies/dsl-showcase.yaml +68 -0
  150. package/examples/strategies/dsl.yaml +51 -0
  151. package/examples/strategies/fox-minimal-loosened.yaml +240 -0
  152. package/examples/strategies/fox-minimal.yaml +242 -0
  153. package/examples/strategies/minimal.yaml +46 -0
  154. package/examples/strategies/momentum/how-it-works.md +48 -0
  155. package/examples/strategies/momentum/momentum-guarded.yaml +161 -0
  156. package/examples/strategies/risk-showcase.yaml +93 -0
  157. package/examples/strategies/viper.yaml +67 -0
  158. package/examples/strategies/wolf.yaml +72 -0
  159. package/examples/yamls/sample-with-dsl-and-emerging-movers.yaml +123 -0
  160. package/package.json +1 -1
  161. package/dist/runtime/reconcile-decide.d.ts +0 -73
  162. package/dist/runtime/reconcile-decide.d.ts.map +0 -1
  163. package/dist/runtime/reconcile-decide.js +0 -166
  164. package/dist/runtime/reconcile-decide.js.map +0 -1
  165. package/dist/runtime/reconcile-run.d.ts +0 -62
  166. package/dist/runtime/reconcile-run.d.ts.map +0 -1
  167. package/dist/runtime/reconcile-run.js +0 -173
  168. package/dist/runtime/reconcile-run.js.map +0 -1
  169. package/dist/scanners/external/scaffold/ctx.py +0 -78
  170. package/dist/scanners/external/scaffold/requirements.txt +0 -11
  171. package/dist/scanners/external/scaffold/senpi_mcp.py +0 -250
  172. package/dist/scanners/external/scaffold/state_manager.py +0 -175
  173. package/dist/utils/keyed-mutex.d.ts +0 -37
  174. package/dist/utils/keyed-mutex.d.ts.map +0 -1
  175. package/dist/utils/keyed-mutex.js +0 -77
  176. package/dist/utils/keyed-mutex.js.map +0 -1
  177. package/examples/strategies/iguana/README.md +0 -135
  178. package/examples/strategies/iguana/recipe.yaml +0 -137
  179. package/examples/strategies/iguana/scanners/scan.py +0 -243
  180. package/examples/strategies/iguana/scanners/scoring.py +0 -152
  181. package/examples/strategies/raptor/README.md +0 -156
  182. package/examples/strategies/raptor/recipe.yaml +0 -154
  183. package/examples/strategies/raptor/scanners/scan.py +0 -349
  184. package/examples/strategies/raptor/scanners/scoring.py +0 -452
  185. package/examples/strategies/spider/README.md +0 -97
  186. package/examples/strategies/spider/scalp/recipe.yaml +0 -129
  187. package/examples/strategies/spider/scalp/scanners/scan.py +0 -219
  188. package/examples/strategies/spider/scalp/scanners/scoring.py +0 -179
  189. package/examples/strategies/spider/swing/recipe.yaml +0 -141
  190. package/examples/strategies/spider/swing/scanners/scan.py +0 -334
  191. package/examples/strategies/spider/swing/scanners/scoring.py +0 -184
  192. package/examples/strategies/turbine/README.md +0 -167
  193. package/examples/strategies/turbine/recipe-runners.yaml +0 -123
  194. package/examples/strategies/turbine/recipe-volume.yaml +0 -129
  195. package/examples/strategies/turbine/scanners/scan.py +0 -283
  196. package/examples/strategies/turbine/scanners/scoring.py +0 -190
@@ -1,154 +0,0 @@
1
- # ── RAPTOR (v2 supervised) ───────────────────────────────────────────
2
- # Whale copy-follow — quality-first hot-streak follower. SINGLE wallet,
3
- # SINGLE recipe, ONE supervised scanner.
4
- #
5
- # Ported from senpi-skills/raptor (producer v4.0.1, config v3.1) to the v2
6
- # supervised external-scanner contract. The producer daemon is gone: the
7
- # runtime supervises ./scanners/scan.py and calls scan(inputs, ctx) every
8
- # `interval_seconds` (180s). All gates + scoring tiers preserved exactly;
9
- # see scan.py + scoring.py.
10
- #
11
- # DOC-vs-CODE: the SKILL/README docs in senpi-skills are STALE (they say
12
- # trader gate $2M / concentration 0.40). The CODE (config v3.1 + producer
13
- # constants) is authoritative and is what this port uses: trader delta gate
14
- # $500k, position gate $100k, concentration 0.35, SM minPct 2 / minTraders 10,
15
- # whale entry-discipline 5%, minScore 6.
16
- name: raptor-tracker
17
- version: 2.0.0
18
- group: raptor # agent-facing handle (harmless for single-wallet)
19
- description: >
20
- RAPTOR — Hot Streak Follower (whale copy-follow). Starts from
21
- discovery_get_top_traders (WEEKLY, ELITE/RELIABLE weekly winners, unrealized
22
- delta-pnl >= $500k), takes each whale's strongest position by |delta_pnl|
23
- (>= $100k) with concentration >= 0.35, confirms smart-money alignment
24
- (pct >= 2 / traders >= 10, direction match) and whale entry-discipline (skip
25
- if price ran > 5% from the whale's entry), then additively scores (max ~16)
26
- and emits the SINGLE best candidate per tick if score >= 6. Conviction-scaled
27
- leverage (7/8/10x) + margin (25%/35%). DSL: 6h hard timeout, weak_peak
28
- 60m@2.5, dead_weight 40m, 5-tier phase2 ladder.
29
-
30
- strategy:
31
- wallet: "${RAPTOR_WALLET}"
32
- slots: 2 # source slots 2 (raptor still emits <=1 signal/tick — see scan.py)
33
- margin_pct: 25 # source margin_pct 25 (% of account per slot; scan.py carries the exact marginUsd)
34
- default_leverage: 7 # source leverage.default 7 (conviction-tier 7/8/10x in scan.py, clamped per-asset)
35
- trading_risk: moderate # source trading_risk moderate
36
- enabled: true
37
-
38
- scanners:
39
- - name: position_tracker # built-in: feeds DSL exit engine
40
- type: position_tracker
41
- interval_seconds: 10 # source position_tracker interval 10s
42
-
43
- - name: raptor_signals # supervised external scanner
44
- type: external_scanner
45
- path: ./scanners
46
- entrypoint: scan.py
47
- interval_seconds: 180 # source producer_daemon interval_seconds 180 (3min)
48
- timeout_seconds: 120 # source tick_timeout 120
49
- default_signal_validity_seconds: 360 # 2x tick; pass-through gate re-scores next tick
50
- state_history_max_count: 200 # holds the per-(trader,asset) 4h event-dedup map records
51
- inputs:
52
- # ── trader-pool + position gates (config.hotStreak) ──
53
- qualityPoolSize: 20 # discovery_get_top_traders limit
54
- positionsFetchLimit: 10 # scan the top-10 quality traders' positions
55
- minDeltaPnl: 500000 # trader unrealized delta-pnl gate ($500k — CODE, not stale $2M docs)
56
- minPositionPnl: 100000 # strongest-position |delta_pnl| gate ($100k)
57
- minConcentration: 0.35 # best/total |delta_pnl| concentration gate (0.35 — CODE, not stale 0.40)
58
- # ── smart-money alignment (config.smAlignment) ──
59
- minSmPct: 2.0 # SM pct_of_top_traders_gain floor
60
- minSmTraders: 10 # SM trader_count floor
61
- requireDirectionMatch: true # SM direction must match the whale's direction
62
- # ── entry + sizing (config.entry / config.leverage) ──
63
- minScore: 6 # additive-score floor to emit
64
- marginPctBase: 0.25 # margin = account_value * 0.25
65
- marginPctHighConv: 0.35 # ... or 0.35 when score >= 10
66
- defaultLeverage: 7 # leverage fallback (tiers: >=10 -> 10x, >=8 -> 8x, >=6 -> 7x)
67
- # ── dedup (config.dedupe) ──
68
- eventDedupeHours: 4 # per-(trader, asset) event dedup window (ctx.state)
69
- signal_data_schema:
70
- score: { type: number }
71
- tcs: { type: string, required: false }
72
- traderId: { type: string, required: false }
73
- traderDeltaPnl: { type: number, required: false }
74
- positionDeltaPnl: { type: number, required: false }
75
- concentration: { type: number, required: false }
76
- smPct: { type: number, required: false }
77
- smTraders: { type: number, required: false }
78
- priceChg4h: { type: number, required: false }
79
- priceChg1h: { type: number, required: false }
80
- whaleEntryPx: { type: number, required: false }
81
- currentPx: { type: number, required: false }
82
- reasons: { type: array, required: false }
83
- heldAssets: { type: array, required: false }
84
-
85
- actions:
86
- - name: position_tracker_action # rule-based lifecycle → DSL
87
- action_type: POSITION_TRACKER
88
- decision_mode: rule
89
- scanners: [position_tracker]
90
-
91
- - name: raptor_entry # OPEN_POSITION, rule mode (source LLM gate was pure pass-through)
92
- action_type: OPEN_POSITION
93
- decision_mode: rule
94
- scanners: [raptor_signals]
95
- params:
96
- order_type: FEE_OPTIMIZED_LIMIT
97
- fee_optimized_limit_options:
98
- ensure_execution_as_taker: true # source entry.ensureExecutionAsTaker true
99
- execution_timeout_seconds: 60
100
- context:
101
- - type: signal
102
- scanner: raptor_signals
103
-
104
- # ── EXIT (DSL — conviction copy-follow; preset preserved from source v3.3) ──
105
- exit:
106
- engine: dsl
107
- interval_seconds: 30 # source exit interval_seconds 30
108
- order_type: FEE_OPTIMIZED_LIMIT
109
- fee_optimized_limit_options:
110
- ensure_execution_as_taker: true
111
- execution_timeout_seconds: 60
112
- dsl_preset:
113
- hard_timeout:
114
- enabled: true
115
- interval_in_minutes: 360 # source hard_timeout 360min (6h absolute cap)
116
- weak_peak_cut:
117
- enabled: true
118
- interval_in_minutes: 60 # source weak_peak 60min
119
- min_value: 2.5 # source weak_peak min 2.5%
120
- dead_weight_cut:
121
- enabled: true
122
- interval_in_minutes: 40 # source dead_weight 40min
123
- phase1:
124
- enabled: true
125
- max_loss_pct: 25.0 # source phase1 max_loss 25
126
- retrace_threshold: 10 # source phase1 retrace 10
127
- consecutive_breaches_required: 1
128
- phase2:
129
- enabled: true
130
- tiers: # source T0..T4 — carried verbatim (all trigger_pct <= 100)
131
- - { trigger_pct: 8, lock_hw_pct: 25 }
132
- - { trigger_pct: 15, lock_hw_pct: 50 }
133
- - { trigger_pct: 25, lock_hw_pct: 65 }
134
- - { trigger_pct: 40, lock_hw_pct: 80 }
135
- - { trigger_pct: 60, lock_hw_pct: 88 }
136
-
137
- # ── RISK guard rails (from source runtime.yaml; minutes/hours → seconds) ──
138
- risk:
139
- data_retention_seconds: 259200 # 72h (was data_retention_hours 72)
140
- guard_rails:
141
- daily_loss_limit_pct: 15
142
- max_entries_per_day: 6
143
- bypass_max_entries_per_day_on_profit: false
144
- max_consecutive_losses: 3
145
- cooldown_seconds: 1800 # was cooldown_minutes 30
146
- drawdown_halt_pct: 25
147
- drawdown_reset_on_day_rollover: false
148
- per_asset_cooldown_seconds: 7200 # was per_asset_cooldown_minutes 120 (2h)
149
-
150
- notifications:
151
- telegram_chat_id: "${TELEGRAM_CHAT_ID}"
152
- dsl_lifecycle: true
153
- dsl_notify_sl_updates: false
154
- action_lifecycle: true
@@ -1,349 +0,0 @@
1
- """RAPTOR — supervised external scanner entrypoint (whale copy-follow).
2
-
3
- Port of senpi-skills/raptor (producer v4.0.1, config v3.1) to the v2
4
- `scan(inputs, ctx)` contract. The daemon loop is gone — the runtime supervisor
5
- calls scan() every interval_seconds (180s).
6
-
7
- THE STRATEGY — quality-first whale copy-follow pipeline (ported VERBATIM; see
8
- scoring.py for the pure gating/scoring):
9
-
10
- 1. Quality hot traders — discovery_get_top_traders(WEEKLY,
11
- sort PROFIT_AND_LOSS_UNREALIZED, consistency ELITE/RELIABLE,
12
- open_position_filter, limit 20); keep traders with unrealized delta-pnl
13
- >= $500k. Sort by delta-pnl desc; scan the top `positionsFetchLimit` (10).
14
- 2. Strongest position — per trader, leaderboard_get_trader_positions; pick the
15
- position with the largest |delta_pnl| (>= $100k).
16
- 3. Concentration — best |delta_pnl| / total |delta_pnl| across the trader's
17
- book must be >= 0.35 (conviction, not a diversified book).
18
- 4. Smart-money alignment — leaderboard_get_markets; the asset must show
19
- SM pct >= 2.0, SM traders >= 10, and (when required) direction match.
20
- 5. Whale entry-discipline — market_get_prices; skip if the price has already
21
- run > 5% from the whale's entry in the whale's direction (don't buy the top).
22
- 6. Additive score (max ~16) — TCS + trader-$ tier + ROI + concentration +
23
- SM strength + 4h/1h confirm + 15m contrib + better-than-whale edge bonus.
24
- 7. Emit the SINGLE best candidate, and only if its score >= minScore (6).
25
-
26
- DESIGN CHOICE — emit best-only vs runtime-owns-slots. Turbine/spider emit ALL
27
- gated candidates and let the runtime apply the slot ceiling. RAPTOR is
28
- deliberately different: it emits AT MOST ONE signal per tick (the single
29
- highest-score whale-aligned setup). This is the strategy, not an oversight —
30
- raptor's whole thesis is "follow the strongest conviction signal right now," and
31
- the source producer pushed exactly one signal per tick after sorting candidates
32
- by score and taking the top. Keeping `slots: 2` in the recipe lets a prior
33
- position still be open while a new tick emits the next-best; the runtime owns
34
- whether a second slot gets filled across ticks. We preserve the per-tick <=1
35
- emission ceiling on purpose.
36
-
37
- Sizing — carried on each signal's data{} so the OPEN_POSITION rule action sizes
38
- identically to the source:
39
- - marginUsd = round(account_value * margin_pct), margin_pct 0.25 (0.35 if
40
- score >= 10). Dual-DEX equity is collapsed via max() (NOT sum()) — one
41
- cross-margined wallet, two sub-DEX views; summing double-counts free balance.
42
- - leverage = conviction tier (>=10 -> 10x, >=8 -> 8x, >=6 -> 7x), clamped to
43
- the per-asset Hyperliquid venue max via strategy_get_asset_trading_limits.
44
-
45
- FLAGS — behaviors that changed under the v2 contract (NOT silent drops):
46
-
47
- 1. Daemon loop / producer_daemon — DROPPED. The runtime supervises this module
48
- and calls scan() each interval_seconds. scan() is single-pass and SYNC.
49
-
50
- 2. push_signal / ingest POST — DROPPED. scan() returns a plain list[dict]; the
51
- scaffold owns delivery, dedup (signal_id), and the wire envelope. The
52
- source's [0,1] normalized wire score (min(score/16, 1.0)) is NOT recomputed
53
- here — the raw additive score rides on data{} and the scaffold/runtime own
54
- wire scoring. (SCORE_NORMALIZATION_DIVISOR is kept in scoring.py for parity.)
55
-
56
- 3. seen-events.json 4h dedup — MOVED to ctx.state. The per-(trader, asset) 4h
57
- event dedup that the source kept in a JSON file lives in ctx.state now: each
58
- tick reads the latest {key: ts} map, prunes by the 4h window, applies it,
59
- stamps the chosen candidate, and appends the updated map for next tick.
60
-
61
- 4. xyz banned — preserved (config.smAlignment.xyzBanned). xyz-dex SM markets
62
- and xyz: positions are dropped in scoring.py.
63
-
64
- 5. ONLY read-only MCP calls. scan() never opens/closes/cancels — it produces
65
- signals. (A test asserts no create/close/cancel tool is ever called.)
66
-
67
- MCP tools used (all read-only): discovery_get_top_traders,
68
- leaderboard_get_trader_positions, leaderboard_get_markets, market_get_prices,
69
- strategy_get_asset_trading_limits, strategy_get_clearinghouse_state.
70
-
71
- State (in ctx.state, newest record wins): {"seen": {dedup_key: epoch_seconds}}.
72
- """
73
-
74
- import sys
75
- import time
76
-
77
- import scoring
78
-
79
-
80
- # ── MCP data fetchers (route the producer's calls through ctx.senpi_mcp) ──
81
-
82
- def _fetch_quality_traders(ctx, limit, min_delta_usd):
83
- """discovery_get_top_traders(WEEKLY, ELITE/RELIABLE, delta gate) -> quality list."""
84
- try:
85
- raw = ctx.senpi_mcp.call_tool("discovery_get_top_traders", {
86
- "time_frame": "WEEKLY",
87
- "sort_by": "PROFIT_AND_LOSS_UNREALIZED",
88
- "consistency": ["ELITE", "RELIABLE"],
89
- "open_position_filter": True,
90
- "limit": limit,
91
- })
92
- except Exception:
93
- return []
94
- return scoring.parse_quality_traders(raw, min_delta_usd=min_delta_usd)
95
-
96
-
97
- def _fetch_trader_positions(ctx, trader_address):
98
- """leaderboard_get_trader_positions -> list of position dicts (nested parser)."""
99
- try:
100
- raw = ctx.senpi_mcp.call_tool("leaderboard_get_trader_positions",
101
- {"trader_id": trader_address})
102
- except Exception:
103
- return []
104
- return scoring.parse_trader_positions(raw)
105
-
106
-
107
- def _fetch_sm_map(ctx):
108
- """leaderboard_get_markets -> {TOKEN: sm_record} (xyz dropped)."""
109
- try:
110
- raw = ctx.senpi_mcp.call_tool("leaderboard_get_markets", {"limit": 100})
111
- except Exception:
112
- return {}
113
- return scoring.parse_sm_map(raw)
114
-
115
-
116
- def _fetch_current_px(ctx, asset):
117
- """market_get_prices -> current price for one asset, or 0.0. Verbatim parse."""
118
- try:
119
- raw = ctx.senpi_mcp.call_tool("market_get_prices", {"assets": [asset]})
120
- except Exception:
121
- return 0.0
122
- if not raw:
123
- return 0.0
124
- data = raw.get("data", raw) if isinstance(raw, dict) else raw
125
- if isinstance(data, dict):
126
- return scoring.safe_float(data.get(asset, 0))
127
- return 0.0
128
-
129
-
130
- def _get_safe_leverage(ctx, asset, requested_leverage):
131
- """Clamp requested leverage to the per-asset HL venue max via
132
- strategy_get_asset_trading_limits. Verbatim from producer.get_safe_leverage:
133
- reads data.leverage.value (or a scalar leverage), min()s with requested."""
134
- try:
135
- limits = ctx.senpi_mcp.call_tool("strategy_get_asset_trading_limits", {
136
- "strategy_wallet": ctx.wallet,
137
- "coin": asset,
138
- })
139
- except Exception:
140
- limits = None
141
- if limits:
142
- data = limits.get("data", limits) if isinstance(limits, dict) else limits
143
- if isinstance(data, dict):
144
- lev = data.get("leverage", {})
145
- if isinstance(lev, dict):
146
- max_lev = int(scoring.safe_float(lev.get("value", 20)))
147
- return scoring.clamp_leverage(requested_leverage, max_lev)
148
- if isinstance(lev, (int, float)):
149
- return scoring.clamp_leverage(requested_leverage, int(lev))
150
- return requested_leverage
151
-
152
-
153
- def _get_account(ctx):
154
- """(account_value, held_assets) from strategy_get_clearinghouse_state.
155
-
156
- Dual-DEX equity collapse: account_value via max() across main/xyz sections
157
- (two views of ONE cross-margined wallet — summing double-counts free
158
- balance -> 2x sizing). Verbatim from raptor_config.get_positions.
159
- """
160
- try:
161
- ch = ctx.senpi_mcp.call_tool("strategy_get_clearinghouse_state",
162
- {"strategy_wallet": ctx.wallet})
163
- except Exception:
164
- return 0.0, []
165
- if not ch:
166
- return 0.0, []
167
- data = ch.get("data", ch) if isinstance(ch, dict) else ch
168
- if not isinstance(data, dict):
169
- return 0.0, []
170
- account_value = 0.0
171
- held = []
172
- for section in ("main", "xyz"):
173
- s = data.get(section, {})
174
- if not isinstance(s, dict):
175
- continue
176
- ms = s.get("marginSummary", {})
177
- account_value = max(account_value, scoring.safe_float(ms.get("accountValue", 0)))
178
- for ap in s.get("assetPositions", []) or []:
179
- pos = ap.get("position", ap)
180
- if scoring.safe_float(pos.get("szi", 0)) == 0:
181
- continue
182
- coin = pos.get("coin", "")
183
- if coin:
184
- held.append(coin)
185
- return account_value, held
186
-
187
-
188
- # ── ctx.state I/O (per-(trader, asset) 4h event dedup) ──
189
-
190
- def _load_seen(ctx):
191
- if ctx.state is None or len(ctx.state) == 0:
192
- return {}
193
- last = ctx.state.last() or {}
194
- seen = last.get("seen", {})
195
- return dict(seen) if isinstance(seen, dict) else {}
196
-
197
-
198
- # ── Candidate evaluation (verbatim pipeline from build_signal) ──
199
-
200
- def _evaluate_trader(ctx, trader, sm_map, inputs):
201
- """Run one quality trader through the full pipeline. Returns a candidate
202
- dict (asset/direction/score/reasons/...) or None on any gate miss."""
203
- positions = _fetch_trader_positions(ctx, trader["address"])
204
- if not positions:
205
- return None
206
-
207
- best_pos, best_abs_pnl, _total, concentration = scoring.pick_strongest_position(positions)
208
- if not best_pos or best_abs_pnl < float(inputs.get("minPositionPnl", scoring.MIN_POSITION_PNL)):
209
- return None
210
- if concentration < float(inputs.get("minConcentration", scoring.MIN_CONCENTRATION)):
211
- return None
212
-
213
- sm = sm_map.get(best_pos["asset"])
214
- if not scoring.sm_alignment_ok(
215
- sm, best_pos["direction"],
216
- min_sm_pct=float(inputs.get("minSmPct", scoring.MIN_SM_PCT)),
217
- min_sm_traders=int(inputs.get("minSmTraders", scoring.MIN_SM_TRADERS)),
218
- require_direction_match=bool(inputs.get("requireDirectionMatch",
219
- scoring.REQUIRE_DIRECTION_MATCH)),
220
- ):
221
- return None
222
-
223
- # v3.3 entry-discipline: fetch current price only when we have a whale entry.
224
- current_px = 0.0
225
- whale_entry_px = scoring.safe_float(best_pos.get("whale_entry_px", 0))
226
- if whale_entry_px > 0:
227
- current_px = _fetch_current_px(ctx, best_pos["asset"])
228
- if not scoring.entry_discipline_ok(best_pos["direction"], whale_entry_px, current_px):
229
- return None
230
-
231
- scored = scoring.score_position(trader, best_pos, concentration, sm, current_px)
232
- if scored is None:
233
- return None
234
- score, reasons = scored
235
-
236
- return {
237
- "asset": best_pos["asset"],
238
- "direction": best_pos["direction"],
239
- "score": score,
240
- "reasons": reasons,
241
- "traderId": trader["address"][:10] + "...",
242
- "fullTraderId": trader["address"],
243
- "tcs": trader["tcs_label"],
244
- "traderDeltaPnl": trader["unrealized_pnl"],
245
- "positionDeltaPnl": best_pos["delta_pnl"],
246
- "concentration": concentration,
247
- "smPct": sm["pct"],
248
- "smTraders": sm["traders"],
249
- "priceChg4h": sm["price_chg_4h"],
250
- "priceChg1h": sm["price_chg_1h"],
251
- "whaleEntryPx": whale_entry_px if whale_entry_px > 0 else None,
252
- "currentPx": current_px if (whale_entry_px > 0 and current_px > 0) else None,
253
- }
254
-
255
-
256
- def scan(inputs, ctx):
257
- now = time.time()
258
- quality_pool_size = int(inputs.get("qualityPoolSize", 20))
259
- positions_fetch_limit = int(inputs.get("positionsFetchLimit", 10))
260
- min_delta_usd = float(inputs.get("minDeltaPnl", scoring.MIN_TRADER_DELTA_PNL))
261
- min_score = int(inputs.get("minScore", scoring.MIN_SCORE))
262
- margin_pct_base = float(inputs.get("marginPctBase", scoring.MARGIN_PCT_BASE))
263
- margin_pct_high = float(inputs.get("marginPctHighConv", scoring.MARGIN_PCT_HIGH_CONV))
264
- dedupe_hours = float(inputs.get("eventDedupeHours", scoring.EVENT_DEDUPE_HOURS))
265
- default_leverage = int(inputs.get("defaultLeverage", scoring.DEFAULT_LEVERAGE))
266
-
267
- account_value, held_assets = _get_account(ctx)
268
- if account_value <= 0:
269
- return []
270
- held_upper = {h.upper() for h in held_assets}
271
-
272
- quality_traders = _fetch_quality_traders(ctx, quality_pool_size, min_delta_usd)
273
- if not quality_traders:
274
- return []
275
-
276
- sm_map = _fetch_sm_map(ctx)
277
- if not sm_map:
278
- return []
279
-
280
- seen = scoring.prune_seen_events(_load_seen(ctx), dedupe_hours, now=now)
281
-
282
- scan_count = min(len(quality_traders), positions_fetch_limit)
283
- candidates = []
284
- for trader in quality_traders[:scan_count]:
285
- cand = _evaluate_trader(ctx, trader, sm_map, inputs)
286
- if not cand:
287
- continue
288
- if scoring.is_event_seen(seen, trader["address"], cand["asset"],
289
- dedupe_hours, now=now):
290
- continue
291
- if cand["asset"] in held_upper:
292
- continue
293
- candidates.append(cand)
294
-
295
- out = []
296
- if candidates:
297
- candidates.sort(key=lambda c: c["score"], reverse=True)
298
- best = candidates[0]
299
- if best["score"] >= min_score:
300
- # Conviction-tier sizing.
301
- margin_pct = margin_pct_high if best["score"] >= 10 else margin_pct_base
302
- margin_usd = round(account_value * margin_pct, 2)
303
- requested = scoring.get_leverage_for_score(
304
- best["score"], scoring.LEVERAGE_TIERS, default_leverage)
305
- leverage = _get_safe_leverage(ctx, best["asset"], requested)
306
-
307
- # Stamp the 4h event dedup for (trader, asset) so we don't re-emit.
308
- seen[scoring.dedup_key(best["fullTraderId"], best["asset"])] = now
309
-
310
- out.append({
311
- "asset": best["asset"],
312
- "direction": best["direction"],
313
- "marginUsd": float(margin_usd),
314
- "leverage": float(leverage),
315
- "data": {
316
- "score": best["score"],
317
- "tcs": best["tcs"],
318
- "traderId": best["traderId"],
319
- "traderDeltaPnl": float(best["traderDeltaPnl"]),
320
- "positionDeltaPnl": float(best["positionDeltaPnl"]),
321
- "concentration": round(best["concentration"], 4),
322
- "smPct": float(best["smPct"]),
323
- "smTraders": int(best["smTraders"]),
324
- "priceChg4h": float(best["priceChg4h"]),
325
- "priceChg1h": float(best["priceChg1h"]),
326
- "whaleEntryPx": best["whaleEntryPx"],
327
- "currentPx": best["currentPx"],
328
- "reasons": best["reasons"],
329
- "heldAssets": held_assets,
330
- },
331
- })
332
-
333
- # Persist the pruned + updated dedup map for next tick.
334
- # If this append fails (e.g. state_history_max_count is 0/unset so append
335
- # raises, or a transient persist error), the next tick reloads stale dedup
336
- # state and may re-emit already-suppressed signals. Log-and-continue: don't
337
- # crash the tick, but make the failure visible on stderr (the runtime
338
- # supervisor captures the scaffold child's stderr) instead of swallowing it.
339
- if ctx.state is not None:
340
- try:
341
- ctx.state.append({"seen": seen})
342
- except Exception as exc:
343
- print(
344
- f"[raptor.scan] WARNING: dedup-state append failed; next tick "
345
- f"may re-emit suppressed signals: {exc!r}",
346
- file=sys.stderr,
347
- )
348
-
349
- return out