@senpi/runtime 3.0.0-dev.runtime-phase-2.20260623210537 → 3.0.0-dev.runtime-phase-2.20260625112547
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.
- package/README.md +10 -9
- package/dist/actions/action-runtime-module.d.ts.map +1 -1
- package/dist/actions/action-runtime-module.js +1 -1
- package/dist/actions/action-runtime-module.js.map +1 -1
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +3 -0
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +54 -14
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/types.d.ts +10 -2
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js +8 -0
- package/dist/actions/types.js.map +1 -1
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +80 -74
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +0 -108
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/constants/dsl/index.d.ts +2 -0
- package/dist/constants/dsl/index.d.ts.map +1 -1
- package/dist/constants/dsl/index.js +2 -0
- package/dist/constants/dsl/index.js.map +1 -1
- package/dist/dsl/config/parse-tiers.d.ts.map +1 -1
- package/dist/dsl/config/parse-tiers.js +6 -2
- package/dist/dsl/config/parse-tiers.js.map +1 -1
- package/dist/dsl/engine-client/config-mapping.d.ts +7 -0
- package/dist/dsl/engine-client/config-mapping.d.ts.map +1 -1
- package/dist/dsl/engine-client/config-mapping.js +16 -2
- package/dist/dsl/engine-client/config-mapping.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts +18 -2
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +302 -213
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/monitor/create-monitor.d.ts +15 -0
- package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
- package/dist/dsl/monitor/create-monitor.js +46 -9
- package/dist/dsl/monitor/create-monitor.js.map +1 -1
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +138 -77
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts +16 -1
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +35 -4
- package/dist/dsl/plugin/index.js.map +1 -1
- package/dist/dsl/plugin/options.d.ts +18 -0
- package/dist/dsl/plugin/options.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/create-scanner-module.d.ts +4 -3
- package/dist/runtime/create-scanner-module.d.ts.map +1 -1
- package/dist/runtime/create-scanner-module.js +14 -5
- package/dist/runtime/create-scanner-module.js.map +1 -1
- package/dist/runtime/create-scanner-providers-for-run.d.ts.map +1 -1
- package/dist/runtime/create-scanner-providers-for-run.js +12 -1
- package/dist/runtime/create-scanner-providers-for-run.js.map +1 -1
- package/dist/runtime/load-runtime-config.js +1 -1
- package/dist/runtime/load-runtime-config.js.map +1 -1
- package/dist/runtime/map-strategies-registration.js +7 -8
- package/dist/runtime/map-strategies-registration.js.map +1 -1
- package/dist/runtime/map-strategies.d.ts.map +1 -1
- package/dist/runtime/map-strategies.js +12 -3
- package/dist/runtime/map-strategies.js.map +1 -1
- package/dist/runtime/reconcile-decide.d.ts +73 -0
- package/dist/runtime/reconcile-decide.d.ts.map +1 -0
- package/dist/runtime/reconcile-decide.js +186 -0
- package/dist/runtime/reconcile-decide.js.map +1 -0
- package/dist/runtime/reconcile-run.d.ts +62 -0
- package/dist/runtime/reconcile-run.d.ts.map +1 -0
- package/dist/runtime/reconcile-run.js +173 -0
- package/dist/runtime/reconcile-run.js.map +1 -0
- package/dist/runtime/reconcile-state.d.ts +44 -0
- package/dist/runtime/reconcile-state.d.ts.map +1 -1
- package/dist/runtime/reconcile-state.js +80 -44
- package/dist/runtime/reconcile-state.js.map +1 -1
- package/dist/runtime/risk-guard.d.ts.map +1 -1
- package/dist/runtime/risk-guard.js +17 -12
- package/dist/runtime/risk-guard.js.map +1 -1
- package/dist/runtime/runtime-schema.d.ts +2912 -1052
- package/dist/runtime/runtime-schema.d.ts.map +1 -1
- package/dist/runtime/runtime-schema.js +136 -42
- package/dist/runtime/runtime-schema.js.map +1 -1
- package/dist/runtime/runtime.d.ts +26 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +254 -8
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/scanner-registry.d.ts +1 -1
- package/dist/runtime/scanner-registry.d.ts.map +1 -1
- package/dist/runtime/scanner-registry.js +3 -12
- package/dist/runtime/scanner-registry.js.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
- package/dist/runtime/supervisor/orphan-sweep.js +1 -1
- package/dist/runtime/supervisor/orphan-sweep.js.map +1 -1
- package/dist/runtime/supervisor/supervisor-utils.d.ts +1 -1
- package/dist/runtime/supervisor/supervisor-utils.d.ts.map +1 -1
- package/dist/runtime/supervisor/supervisor-utils.js +1 -1
- package/dist/runtime/supervisor/supervisor-utils.js.map +1 -1
- package/dist/runtime-api/intake/acceptance-record.d.ts +10 -0
- package/dist/runtime-api/intake/acceptance-record.d.ts.map +1 -1
- package/dist/runtime-api/intake/acceptance-record.js +28 -0
- package/dist/runtime-api/intake/acceptance-record.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +28 -36
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts +20 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.js +57 -6
- package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.d.ts +18 -4
- package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.js +24 -3
- package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
- package/dist/runtime-api/intake/types.d.ts +16 -0
- package/dist/runtime-api/intake/types.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.d.ts +4 -0
- package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.js +12 -0
- package/dist/runtime-api/routes/signals.schema.js.map +1 -1
- package/dist/scanners/external/scaffold/__main__.py +65 -24
- package/dist/scanners/external/scaffold/ctx.py +78 -0
- package/dist/scanners/external/scaffold/delivery.py +56 -22
- package/dist/scanners/external/scaffold/entrypoint_loader.py +42 -48
- package/dist/scanners/external/scaffold/journal.py +13 -2
- package/dist/scanners/external/scaffold/requirements.txt +11 -0
- package/dist/scanners/external/scaffold/scaffold.py +159 -128
- package/dist/scanners/external/scaffold/senpi_mcp.py +250 -0
- package/dist/scanners/external/scaffold/signal_envelope.py +177 -34
- package/dist/scanners/external/scaffold/state_manager.py +175 -0
- package/dist/scanners/external-scanner-receiver.d.ts +6 -0
- package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
- package/dist/scanners/external-scanner-receiver.js +14 -1
- package/dist/scanners/external-scanner-receiver.js.map +1 -1
- package/dist/scripts/run-dsl-standalone-live.d.ts +1 -1
- package/dist/scripts/run-dsl-standalone-live.js +5 -3
- package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
- package/dist/senpi/client.d.ts +37 -4
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +101 -24
- package/dist/senpi/client.js.map +1 -1
- package/dist/state/state-manager.d.ts +17 -0
- package/dist/state/state-manager.d.ts.map +1 -1
- package/dist/state/state-manager.js +50 -4
- package/dist/state/state-manager.js.map +1 -1
- package/dist/strategy/strategy-state.d.ts +9 -1
- package/dist/strategy/strategy-state.d.ts.map +1 -1
- package/dist/strategy/strategy-state.js +62 -9
- package/dist/strategy/strategy-state.js.map +1 -1
- package/dist/tracking/constants.d.ts.map +1 -1
- package/dist/tracking/constants.js +2 -0
- package/dist/tracking/constants.js.map +1 -1
- package/dist/types/dsl/index.d.ts +1 -1
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/types/scanner.d.ts +7 -0
- package/dist/types/scanner.d.ts.map +1 -1
- package/dist/types/strategy.d.ts +3 -1
- package/dist/types/strategy.d.ts.map +1 -1
- package/dist/utils/keyed-mutex.d.ts +37 -0
- package/dist/utils/keyed-mutex.d.ts.map +1 -0
- package/dist/utils/keyed-mutex.js +77 -0
- package/dist/utils/keyed-mutex.js.map +1 -0
- package/examples/strategies/README.md +17 -12
- package/examples/strategies/iguana/README.md +135 -0
- package/examples/strategies/iguana/recipe.yaml +137 -0
- package/examples/strategies/iguana/scanners/scan.py +243 -0
- package/examples/strategies/iguana/scanners/scoring.py +152 -0
- package/examples/strategies/raptor/README.md +156 -0
- package/examples/strategies/raptor/recipe.yaml +154 -0
- package/examples/strategies/raptor/scanners/scan.py +349 -0
- package/examples/strategies/raptor/scanners/scoring.py +452 -0
- package/examples/strategies/spider/README.md +89 -0
- package/examples/strategies/spider/scalp/recipe.yaml +129 -0
- package/examples/strategies/spider/scalp/scanners/scan.py +219 -0
- package/examples/strategies/spider/scalp/scanners/scoring.py +179 -0
- package/examples/strategies/spider/swing/recipe.yaml +137 -0
- package/examples/strategies/spider/swing/scanners/scan.py +334 -0
- package/examples/strategies/spider/swing/scanners/scoring.py +184 -0
- package/examples/strategies/turbine/README.md +166 -0
- package/examples/strategies/turbine/recipe-runners.yaml +123 -0
- package/examples/strategies/turbine/recipe-volume.yaml +129 -0
- package/examples/strategies/turbine/scanners/scan.py +283 -0
- package/examples/strategies/turbine/scanners/scoring.py +190 -0
- package/package.json +1 -1
- package/dist/scanners/external/liquidation-watchdog/README.md +0 -107
- package/dist/scanners/external/liquidation-watchdog/liquidation-watchdog.yaml +0 -48
- package/dist/scanners/external/liquidation-watchdog/producer.mjs +0 -336
- package/dist/scanners/external/momentum/README.md +0 -148
- package/dist/scanners/external/momentum/momentum.yaml +0 -116
- package/dist/scanners/external/momentum/producer.mjs +0 -251
- package/dist/scanners/external/shared/ingest.mjs +0 -112
- package/examples/README.md +0 -89
- package/examples/scanners-consumer-quickstart.mjs +0 -134
- package/examples/scanners-emerging-movers-v4.mjs +0 -287
- package/examples/scanners-liquidation-watchdog.mjs +0 -341
- package/examples/scanners-live-mcp.mjs +0 -208
- package/examples/scanners-multi-strategy.mjs +0 -165
- package/examples/scanners-position-tracker-e2e.mjs +0 -225
- package/examples/scanners-scheduled-events.mjs +0 -96
- package/examples/scanners-senpi-provider-client.mjs +0 -182
- package/examples/scanners-single-strategy.mjs +0 -198
- package/examples/strategies/dsl-showcase.yaml +0 -68
- package/examples/strategies/dsl.yaml +0 -51
- package/examples/strategies/fox-minimal-loosened.yaml +0 -240
- package/examples/strategies/fox-minimal.yaml +0 -242
- package/examples/strategies/minimal.yaml +0 -46
- package/examples/strategies/momentum/how-it-works.md +0 -48
- package/examples/strategies/momentum/momentum-guarded.yaml +0 -161
- package/examples/strategies/risk-showcase.yaml +0 -93
- package/examples/strategies/viper.yaml +0 -67
- package/examples/strategies/wolf.yaml +0 -72
- package/examples/yamls/sample-with-dsl-and-emerging-movers.yaml +0 -123
|
@@ -0,0 +1,154 @@
|
|
|
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
|
|
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
|
|
@@ -0,0 +1,349 @@
|
|
|
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
|