@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.
- package/README.md +9 -10
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +0 -3
- 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 +2 -6
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +56 -43
- 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 +108 -0
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/constants/dsl/index.d.ts +0 -2
- package/dist/constants/dsl/index.d.ts.map +1 -1
- package/dist/constants/dsl/index.js +0 -2
- package/dist/constants/dsl/index.js.map +1 -1
- package/dist/dsl/engine-client/config-mapping.d.ts +0 -7
- package/dist/dsl/engine-client/config-mapping.d.ts.map +1 -1
- package/dist/dsl/engine-client/config-mapping.js +2 -16
- package/dist/dsl/engine-client/config-mapping.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts +2 -18
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +213 -302
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/monitor/create-monitor.d.ts +0 -15
- package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
- package/dist/dsl/monitor/create-monitor.js +9 -44
- 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 +1 -24
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts +1 -16
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +4 -35
- package/dist/dsl/plugin/index.js.map +1 -1
- package/dist/dsl/plugin/options.d.ts +0 -18
- package/dist/dsl/plugin/options.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime/create-scanner-module.d.ts +3 -4
- package/dist/runtime/create-scanner-module.d.ts.map +1 -1
- package/dist/runtime/create-scanner-module.js +5 -14
- 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 +1 -12
- 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 +8 -7
- 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 +3 -12
- package/dist/runtime/map-strategies.js.map +1 -1
- package/dist/runtime/reconcile-state.d.ts +0 -44
- package/dist/runtime/reconcile-state.d.ts.map +1 -1
- package/dist/runtime/reconcile-state.js +44 -80
- 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 +12 -17
- package/dist/runtime/risk-guard.js.map +1 -1
- package/dist/runtime/runtime-schema.d.ts +995 -2855
- package/dist/runtime/runtime-schema.d.ts.map +1 -1
- package/dist/runtime/runtime-schema.js +40 -133
- package/dist/runtime/runtime-schema.js.map +1 -1
- package/dist/runtime/runtime.d.ts +0 -26
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +5 -233
- 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 +12 -3
- 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/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +36 -28
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.js +0 -0
- package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.d.ts +4 -18
- package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.js +3 -24
- package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
- package/dist/runtime-api/intake/types.d.ts +0 -6
- package/dist/runtime-api/intake/types.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.d.ts +0 -1
- package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.js +0 -5
- package/dist/runtime-api/routes/signals.schema.js.map +1 -1
- package/dist/scanners/external/liquidation-watchdog/README.md +107 -0
- package/dist/scanners/external/liquidation-watchdog/liquidation-watchdog.yaml +48 -0
- package/dist/scanners/external/liquidation-watchdog/producer.mjs +336 -0
- package/dist/scanners/external/momentum/README.md +148 -0
- package/dist/scanners/external/momentum/momentum.yaml +116 -0
- package/dist/scanners/external/momentum/producer.mjs +251 -0
- package/dist/scanners/external/scaffold/__main__.py +24 -65
- package/dist/scanners/external/scaffold/delivery.py +22 -56
- package/dist/scanners/external/scaffold/entrypoint_loader.py +48 -42
- package/dist/scanners/external/scaffold/journal.py +2 -13
- package/dist/scanners/external/scaffold/scaffold.py +128 -159
- package/dist/scanners/external/scaffold/signal_envelope.py +34 -177
- package/dist/scanners/external/shared/ingest.mjs +112 -0
- package/dist/scanners/external-scanner-receiver.d.ts +0 -6
- package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
- package/dist/scanners/external-scanner-receiver.js +1 -14
- 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 +3 -5
- package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
- package/dist/senpi/client.d.ts +0 -12
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +2 -31
- package/dist/senpi/client.js.map +1 -1
- package/dist/state/state-manager.d.ts +0 -17
- package/dist/state/state-manager.d.ts.map +1 -1
- package/dist/state/state-manager.js +0 -28
- package/dist/state/state-manager.js.map +1 -1
- package/dist/strategy/strategy-state.d.ts.map +1 -1
- package/dist/strategy/strategy-state.js +1 -7
- package/dist/strategy/strategy-state.js.map +1 -1
- package/dist/tracking/constants.d.ts.map +1 -1
- package/dist/tracking/constants.js +0 -2
- 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 +0 -7
- package/dist/types/scanner.d.ts.map +1 -1
- package/examples/README.md +89 -0
- package/examples/scanners-consumer-quickstart.mjs +134 -0
- package/examples/scanners-emerging-movers-v4.mjs +287 -0
- package/examples/scanners-liquidation-watchdog.mjs +341 -0
- package/examples/scanners-live-mcp.mjs +208 -0
- package/examples/scanners-multi-strategy.mjs +165 -0
- package/examples/scanners-position-tracker-e2e.mjs +225 -0
- package/examples/scanners-scheduled-events.mjs +96 -0
- package/examples/scanners-senpi-provider-client.mjs +182 -0
- package/examples/scanners-single-strategy.mjs +198 -0
- package/examples/strategies/README.md +12 -17
- package/examples/strategies/dsl-showcase.yaml +68 -0
- package/examples/strategies/dsl.yaml +51 -0
- package/examples/strategies/fox-minimal-loosened.yaml +240 -0
- package/examples/strategies/fox-minimal.yaml +242 -0
- package/examples/strategies/minimal.yaml +46 -0
- package/examples/strategies/momentum/how-it-works.md +48 -0
- package/examples/strategies/momentum/momentum-guarded.yaml +161 -0
- package/examples/strategies/risk-showcase.yaml +93 -0
- package/examples/strategies/viper.yaml +67 -0
- package/examples/strategies/wolf.yaml +72 -0
- package/examples/yamls/sample-with-dsl-and-emerging-movers.yaml +123 -0
- package/package.json +1 -1
- package/dist/runtime/reconcile-decide.d.ts +0 -73
- package/dist/runtime/reconcile-decide.d.ts.map +0 -1
- package/dist/runtime/reconcile-decide.js +0 -166
- package/dist/runtime/reconcile-decide.js.map +0 -1
- package/dist/runtime/reconcile-run.d.ts +0 -62
- package/dist/runtime/reconcile-run.d.ts.map +0 -1
- package/dist/runtime/reconcile-run.js +0 -173
- package/dist/runtime/reconcile-run.js.map +0 -1
- package/dist/scanners/external/scaffold/ctx.py +0 -78
- package/dist/scanners/external/scaffold/requirements.txt +0 -11
- package/dist/scanners/external/scaffold/senpi_mcp.py +0 -250
- package/dist/scanners/external/scaffold/state_manager.py +0 -175
- package/dist/utils/keyed-mutex.d.ts +0 -37
- package/dist/utils/keyed-mutex.d.ts.map +0 -1
- package/dist/utils/keyed-mutex.js +0 -77
- package/dist/utils/keyed-mutex.js.map +0 -1
- package/examples/strategies/iguana/README.md +0 -135
- package/examples/strategies/iguana/recipe.yaml +0 -137
- package/examples/strategies/iguana/scanners/scan.py +0 -243
- package/examples/strategies/iguana/scanners/scoring.py +0 -152
- package/examples/strategies/raptor/README.md +0 -156
- package/examples/strategies/raptor/recipe.yaml +0 -154
- package/examples/strategies/raptor/scanners/scan.py +0 -349
- package/examples/strategies/raptor/scanners/scoring.py +0 -452
- package/examples/strategies/spider/README.md +0 -97
- package/examples/strategies/spider/scalp/recipe.yaml +0 -129
- package/examples/strategies/spider/scalp/scanners/scan.py +0 -219
- package/examples/strategies/spider/scalp/scanners/scoring.py +0 -179
- package/examples/strategies/spider/swing/recipe.yaml +0 -141
- package/examples/strategies/spider/swing/scanners/scan.py +0 -334
- package/examples/strategies/spider/swing/scanners/scoring.py +0 -184
- package/examples/strategies/turbine/README.md +0 -167
- package/examples/strategies/turbine/recipe-runners.yaml +0 -123
- package/examples/strategies/turbine/recipe-volume.yaml +0 -129
- package/examples/strategies/turbine/scanners/scan.py +0 -283
- package/examples/strategies/turbine/scanners/scoring.py +0 -190
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
"""SPIDER SCALP — supervised external scanner entrypoint.
|
|
2
|
-
|
|
3
|
-
Port of senpi-skills/spider (producer v5.1.1, SPIDER_LEG=scalp) to the v2
|
|
4
|
-
`scan(inputs, ctx)` contract. The daemon loop is gone — the runtime supervisor
|
|
5
|
-
calls scan() every interval_seconds.
|
|
6
|
-
|
|
7
|
-
What stayed faithful to the source:
|
|
8
|
-
- score_scalp scoring + all thresholds (see scoring.py — ported verbatim).
|
|
9
|
-
- Static universe (allowedAssets): majors + energy.
|
|
10
|
-
- The recent-signal dedup (RECENT_SIGNAL_TTL): a coin signaled in the last
|
|
11
|
-
`recentSignalTtlSeconds` is skipped. Source kept this in a JSON file; here it
|
|
12
|
-
lives in ctx.state.
|
|
13
|
-
- Leverage clamp to the per-asset HL venue max (clamp_leverage).
|
|
14
|
-
- The dual-DEX equity collapse via max() (NOT sum()) when reading account value.
|
|
15
|
-
|
|
16
|
-
What was simplified vs the source (FLAGGED, same as the swing leg):
|
|
17
|
-
- The producer's `affordable` per-tick emission cap moves to the v2 runtime
|
|
18
|
-
(strategy.slots + risk gates). scan() emits ALL qualifying candidates above
|
|
19
|
-
minScore (held + recently-signaled filtered). The signal-GATING thresholds
|
|
20
|
-
(minScore, held-set, recent-dedup, venue min notional) are preserved exactly.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
import sys
|
|
24
|
-
import time
|
|
25
|
-
|
|
26
|
-
import scoring
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
_DEFAULT_RECENT_TTL = 180
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _dex_for(asset):
|
|
33
|
-
return "xyz" if asset.lower().startswith("xyz:") else ""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _get_universe_meta(ctx):
|
|
37
|
-
"""{name: {"max_leverage": int|None, "ctx": {...}}} for every live
|
|
38
|
-
instrument on both dexes (one market_list_instruments call)."""
|
|
39
|
-
try:
|
|
40
|
-
data = ctx.senpi_mcp.call_tool("market_list_instruments", {})
|
|
41
|
-
except Exception:
|
|
42
|
-
return {}
|
|
43
|
-
out = {}
|
|
44
|
-
if not data:
|
|
45
|
-
return out
|
|
46
|
-
insts = data.get("data", data) if isinstance(data, dict) else data
|
|
47
|
-
if isinstance(insts, dict):
|
|
48
|
-
insts = insts.get("instruments", [])
|
|
49
|
-
for inst in insts or []:
|
|
50
|
-
if not isinstance(inst, dict):
|
|
51
|
-
continue
|
|
52
|
-
if inst.get("is_delisted"):
|
|
53
|
-
continue
|
|
54
|
-
name = inst.get("name") or inst.get("context", {}).get("coin")
|
|
55
|
-
if not name:
|
|
56
|
-
continue
|
|
57
|
-
entry = {
|
|
58
|
-
"max_leverage": inst.get("max_leverage", inst.get("maxLeverage")),
|
|
59
|
-
"ctx": inst.get("context", {}) if isinstance(inst.get("context"), dict) else {},
|
|
60
|
-
}
|
|
61
|
-
out[name] = entry
|
|
62
|
-
out[name.upper()] = entry
|
|
63
|
-
return out
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def _fetch_candles(ctx, asset, intervals):
|
|
67
|
-
try:
|
|
68
|
-
data = ctx.senpi_mcp.call_tool("market_get_asset_data", {
|
|
69
|
-
"asset": asset,
|
|
70
|
-
"candle_intervals": intervals,
|
|
71
|
-
"dex": _dex_for(asset),
|
|
72
|
-
"include_funding": False,
|
|
73
|
-
"include_order_book": False,
|
|
74
|
-
})
|
|
75
|
-
except Exception:
|
|
76
|
-
return None
|
|
77
|
-
if not data or (isinstance(data, dict) and not data.get("success", True)):
|
|
78
|
-
return None
|
|
79
|
-
d = data.get("data", data) if isinstance(data, dict) else data
|
|
80
|
-
if not isinstance(d, dict):
|
|
81
|
-
return None
|
|
82
|
-
return {"candles": d.get("candles", {}) or {}, "ctx": d.get("asset_context", {}) or {}}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _get_account(ctx):
|
|
86
|
-
"""(account_value, [position_dicts]) — dual-DEX equity collapse via max()."""
|
|
87
|
-
try:
|
|
88
|
-
ch = ctx.senpi_mcp.call_tool("strategy_get_clearinghouse_state",
|
|
89
|
-
{"strategy_wallet": ctx.wallet})
|
|
90
|
-
except Exception:
|
|
91
|
-
return 0.0, []
|
|
92
|
-
if not ch:
|
|
93
|
-
return 0.0, []
|
|
94
|
-
data = ch.get("data", ch) if isinstance(ch, dict) else ch
|
|
95
|
-
if not isinstance(data, dict):
|
|
96
|
-
return 0.0, []
|
|
97
|
-
positions, account_value = [], 0.0
|
|
98
|
-
for section in ("main", "xyz"):
|
|
99
|
-
s = data.get(section, {})
|
|
100
|
-
if not isinstance(s, dict):
|
|
101
|
-
continue
|
|
102
|
-
ms = s.get("marginSummary", {})
|
|
103
|
-
account_value = max(account_value, float(ms.get("accountValue", 0) or 0))
|
|
104
|
-
for ap in s.get("assetPositions", []):
|
|
105
|
-
pos = ap.get("position", ap)
|
|
106
|
-
szi = float(pos.get("szi", 0) or 0)
|
|
107
|
-
if szi == 0:
|
|
108
|
-
continue
|
|
109
|
-
positions.append({"coin": pos.get("coin", ""),
|
|
110
|
-
"margin": float(pos.get("marginUsed", 0) or 0)})
|
|
111
|
-
return account_value, positions
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# ── ctx.state recent-signal dedup ──
|
|
115
|
-
|
|
116
|
-
def _load_signaled(ctx):
|
|
117
|
-
if ctx.state is None or len(ctx.state) == 0:
|
|
118
|
-
return {}
|
|
119
|
-
last = ctx.state.last() or {}
|
|
120
|
-
sig = last.get("signaled", {})
|
|
121
|
-
return dict(sig) if isinstance(sig, dict) else {}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def _was_recently_signaled(signaled, coin, ttl, now):
|
|
125
|
-
last = signaled.get(coin.upper())
|
|
126
|
-
if last is None:
|
|
127
|
-
return False
|
|
128
|
-
return (now - last) < ttl
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _prune_signaled(signaled, ttl, now):
|
|
132
|
-
cutoff = now - (ttl * 4)
|
|
133
|
-
return {k: v for k, v in signaled.items() if v >= cutoff}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def scan(inputs, ctx):
|
|
137
|
-
now = time.time()
|
|
138
|
-
min_score = inputs.get("minScore", 4)
|
|
139
|
-
margin_pct = float(inputs.get("marginPct", 0.15))
|
|
140
|
-
leg_max_lev = inputs.get("maxLeverage", 5)
|
|
141
|
-
ttl = float(inputs.get("recentSignalTtlSeconds", _DEFAULT_RECENT_TTL))
|
|
142
|
-
venue_min_notional = float(inputs.get("venueMinNotionalUsd", 10))
|
|
143
|
-
min_notional_pct = float(inputs.get("minNotionalPctOfEquity", 0.01))
|
|
144
|
-
|
|
145
|
-
account_value, positions = _get_account(ctx)
|
|
146
|
-
held_assets = [p["coin"] for p in positions if p.get("coin")]
|
|
147
|
-
held_set = {h.upper() for h in held_assets}
|
|
148
|
-
if account_value <= 0:
|
|
149
|
-
return []
|
|
150
|
-
|
|
151
|
-
min_notional = max(account_value * min_notional_pct, venue_min_notional)
|
|
152
|
-
margin_usd = round(account_value * margin_pct, 2)
|
|
153
|
-
|
|
154
|
-
signaled = _prune_signaled(_load_signaled(ctx), ttl, now)
|
|
155
|
-
|
|
156
|
-
meta_map = _get_universe_meta(ctx)
|
|
157
|
-
allowed = list(inputs.get("allowedAssets", []))
|
|
158
|
-
|
|
159
|
-
candidates = []
|
|
160
|
-
for asset in allowed:
|
|
161
|
-
au = asset.upper()
|
|
162
|
-
if au in held_set:
|
|
163
|
-
continue
|
|
164
|
-
if _was_recently_signaled(signaled, asset, ttl, now):
|
|
165
|
-
continue
|
|
166
|
-
meta = meta_map.get(asset) or meta_map.get(au)
|
|
167
|
-
if not meta:
|
|
168
|
-
continue
|
|
169
|
-
md = _fetch_candles(ctx, asset, ["5m", "15m", "1h"])
|
|
170
|
-
if not md:
|
|
171
|
-
continue
|
|
172
|
-
c15 = md["candles"].get("15m", [])
|
|
173
|
-
c1 = md["candles"].get("1h", [])
|
|
174
|
-
thesis = scoring.score_scalp(asset, c15, c1, meta.get("ctx", {}), inputs)
|
|
175
|
-
if thesis and thesis["score"] >= min_score:
|
|
176
|
-
thesis["_meta"] = meta
|
|
177
|
-
candidates.append(thesis)
|
|
178
|
-
|
|
179
|
-
candidates.sort(key=lambda x: x["score"], reverse=True)
|
|
180
|
-
|
|
181
|
-
out = []
|
|
182
|
-
for th in candidates:
|
|
183
|
-
leverage = scoring.clamp_leverage(leg_max_lev, th["_meta"].get("max_leverage"))
|
|
184
|
-
notional = margin_usd * leverage
|
|
185
|
-
if leverage <= 0 or notional < min_notional:
|
|
186
|
-
continue
|
|
187
|
-
out.append({
|
|
188
|
-
"asset": th["coin"],
|
|
189
|
-
"direction": th["direction"],
|
|
190
|
-
"marginUsd": margin_usd,
|
|
191
|
-
"leverage": leverage,
|
|
192
|
-
"data": {
|
|
193
|
-
"score": th["score"],
|
|
194
|
-
"direction": th["direction"],
|
|
195
|
-
"reasons": th["reasons"],
|
|
196
|
-
"heldAssets": held_assets,
|
|
197
|
-
"trend1h": th.get("trend1h"),
|
|
198
|
-
"rsi": round(th.get("rsi", 0), 1),
|
|
199
|
-
"stretchPct": round(th.get("stretchPct", 0), 3),
|
|
200
|
-
},
|
|
201
|
-
})
|
|
202
|
-
signaled[th["coin"].upper()] = now
|
|
203
|
-
|
|
204
|
-
# If this append fails (e.g. state_history_max_count is 0/unset so append
|
|
205
|
-
# raises, or a transient persist error), the next tick reloads stale dedup
|
|
206
|
-
# state and may re-emit already-suppressed signals. Log-and-continue: don't
|
|
207
|
-
# crash the tick, but make the failure visible on stderr (the runtime
|
|
208
|
-
# supervisor captures the scaffold child's stderr) instead of swallowing it.
|
|
209
|
-
if ctx.state is not None:
|
|
210
|
-
try:
|
|
211
|
-
ctx.state.append({"signaled": signaled})
|
|
212
|
-
except Exception as exc:
|
|
213
|
-
print(
|
|
214
|
-
f"[spider.scalp.scan] WARNING: dedup-state append failed; next "
|
|
215
|
-
f"tick may re-emit suppressed signals: {exc!r}",
|
|
216
|
-
file=sys.stderr,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
return out
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
"""SPIDER SCALP — pure scoring functions (port of spider-producer.py v5.1.1).
|
|
2
|
-
|
|
3
|
-
Ported VERBATIM from senpi-skills/spider/scripts/spider-producer.py (the
|
|
4
|
-
technical helpers + score_scalp). No I/O, no MCP, no daemon — pure and
|
|
5
|
-
unit-testable. `scan.py` fetches candle/market data via ctx.senpi_mcp and
|
|
6
|
-
hands it to score_scalp here.
|
|
7
|
-
|
|
8
|
-
Source thresholds preserved exactly:
|
|
9
|
-
- candle minimums: 15m >= 20 bars, 1h >= 6 bars
|
|
10
|
-
- MA: simple 20-bar on 15m closes; stretch = (price - ma)/ma * 100
|
|
11
|
-
- side select: fade the larger of oversold_mag / overbought_mag
|
|
12
|
-
- LONG: rsi<=20 +3 / <=25 +2 / <=rsiOversold +1; -stretch>=2*thr +2 / >=thr +1;
|
|
13
|
-
1h BULLISH +1 / BEARISH -2; funding<0 +1
|
|
14
|
-
- SHORT: rsi>=80 +3 / >=75 +2 / >=rsiOverbought +1; stretch>=2*thr +2 / >=thr +1;
|
|
15
|
-
1h BEARISH +1 / BULLISH -2; funding>0 +1
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# ── Technical helpers (close=c, high=h, low=l on HL candles) ──
|
|
20
|
-
|
|
21
|
-
def _close(c):
|
|
22
|
-
return float(c.get("close", c.get("c", 0)) or 0)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _high(c):
|
|
26
|
-
return float(c.get("high", c.get("h", 0)) or 0)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _low(c):
|
|
30
|
-
return float(c.get("low", c.get("l", 0)) or 0)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def trend_structure(candles, lookback=6):
|
|
34
|
-
"""Higher lows = BULLISH, lower highs = BEARISH."""
|
|
35
|
-
if len(candles) < lookback:
|
|
36
|
-
return "NEUTRAL", 0
|
|
37
|
-
lows = [_low(c) for c in candles[-lookback:]]
|
|
38
|
-
highs = [_high(c) for c in candles[-lookback:]]
|
|
39
|
-
higher_lows = sum(1 for i in range(1, len(lows)) if lows[i] > lows[i - 1])
|
|
40
|
-
lower_highs = sum(1 for i in range(1, len(highs)) if highs[i] < highs[i - 1])
|
|
41
|
-
total = lookback - 1
|
|
42
|
-
if higher_lows >= total * 0.6:
|
|
43
|
-
return "BULLISH", higher_lows / total
|
|
44
|
-
elif lower_highs >= total * 0.6:
|
|
45
|
-
return "BEARISH", lower_highs / total
|
|
46
|
-
return "NEUTRAL", 0
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def calc_rsi(closes, period=14):
|
|
50
|
-
if len(closes) < period + 1:
|
|
51
|
-
return 50.0
|
|
52
|
-
gains, losses = [], []
|
|
53
|
-
for i in range(1, len(closes)):
|
|
54
|
-
d = closes[i] - closes[i - 1]
|
|
55
|
-
gains.append(max(0, d))
|
|
56
|
-
losses.append(max(0, -d))
|
|
57
|
-
g, l = gains[-period:], losses[-period:]
|
|
58
|
-
avg_g, avg_l = sum(g) / period, sum(l) / period
|
|
59
|
-
if avg_l == 0:
|
|
60
|
-
return 100.0
|
|
61
|
-
return 100.0 - (100.0 / (1.0 + avg_g / avg_l))
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def simple_ma(closes, period):
|
|
65
|
-
if not closes:
|
|
66
|
-
return 0
|
|
67
|
-
window = closes[-period:] if len(closes) >= period else closes
|
|
68
|
-
return sum(window) / len(window)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# ── SCALP scoring — Macro & majors fast mean-reversion, BOTH directions ──
|
|
72
|
-
|
|
73
|
-
def score_scalp(asset, candles_15m, candles_1h, ctx_meta, config):
|
|
74
|
-
"""Score one asset for the SCALP leg.
|
|
75
|
-
|
|
76
|
-
Args:
|
|
77
|
-
asset: symbol (e.g. "BTC" or "xyz:BRENTOIL")
|
|
78
|
-
candles_15m: list of 15m candle dicts (oldest -> newest)
|
|
79
|
-
candles_1h: list of 1h candle dicts
|
|
80
|
-
ctx_meta: the instrument's "ctx" dict (funding)
|
|
81
|
-
config: inputs dict (reads rsiOversold/rsiOverbought/stretchThresholdPct)
|
|
82
|
-
|
|
83
|
-
Returns a thesis dict, or None if data is insufficient OR neither side is
|
|
84
|
-
stretched. The score gate (>= minScore) is applied by the caller.
|
|
85
|
-
"""
|
|
86
|
-
if len(candles_15m) < 20 or len(candles_1h) < 6:
|
|
87
|
-
return None
|
|
88
|
-
closes15 = [_close(c) for c in candles_15m]
|
|
89
|
-
price = closes15[-1]
|
|
90
|
-
|
|
91
|
-
ma = simple_ma(closes15, 20)
|
|
92
|
-
stretch = ((price - ma) / ma * 100) if ma > 0 else 0
|
|
93
|
-
rsi = calc_rsi(closes15)
|
|
94
|
-
trend1, _ = trend_structure(candles_1h)
|
|
95
|
-
|
|
96
|
-
ctx = ctx_meta or {}
|
|
97
|
-
funding = float(ctx.get("funding", 0) or 0)
|
|
98
|
-
|
|
99
|
-
rsi_os = config.get("rsiOversold", 30)
|
|
100
|
-
rsi_ob = config.get("rsiOverbought", 70)
|
|
101
|
-
stretch_thresh = config.get("stretchThresholdPct", 0.8)
|
|
102
|
-
|
|
103
|
-
# Which side is more extreme? Fade it.
|
|
104
|
-
oversold_mag = max(rsi_os - rsi, 0) / max(rsi_os, 1) + max(-stretch, 0) / stretch_thresh
|
|
105
|
-
overbought_mag = max(rsi - rsi_ob, 0) / max(100 - rsi_ob, 1) + max(stretch, 0) / stretch_thresh
|
|
106
|
-
if oversold_mag <= 0 and overbought_mag <= 0:
|
|
107
|
-
return None
|
|
108
|
-
direction = "LONG" if oversold_mag >= overbought_mag else "SHORT"
|
|
109
|
-
|
|
110
|
-
score = 0
|
|
111
|
-
reasons = []
|
|
112
|
-
|
|
113
|
-
if direction == "LONG":
|
|
114
|
-
if rsi <= 20:
|
|
115
|
-
score += 3
|
|
116
|
-
reasons.append(f"rsi_{rsi:.0f}_deep_oversold")
|
|
117
|
-
elif rsi <= 25:
|
|
118
|
-
score += 2
|
|
119
|
-
reasons.append(f"rsi_{rsi:.0f}_oversold")
|
|
120
|
-
elif rsi <= rsi_os:
|
|
121
|
-
score += 1
|
|
122
|
-
reasons.append(f"rsi_{rsi:.0f}_oversold")
|
|
123
|
-
if -stretch >= 2 * stretch_thresh:
|
|
124
|
-
score += 2
|
|
125
|
-
reasons.append(f"stretch_{stretch:+.2f}%")
|
|
126
|
-
elif -stretch >= stretch_thresh:
|
|
127
|
-
score += 1
|
|
128
|
-
reasons.append(f"stretch_{stretch:+.2f}%")
|
|
129
|
-
if trend1 == "BULLISH":
|
|
130
|
-
score += 1
|
|
131
|
-
reasons.append("1h_uptrend_dip")
|
|
132
|
-
elif trend1 == "BEARISH":
|
|
133
|
-
score -= 2
|
|
134
|
-
reasons.append("1h_downtrend_knife")
|
|
135
|
-
if funding < 0:
|
|
136
|
-
score += 1
|
|
137
|
-
reasons.append(f"funding_neg_{funding:+.4f}")
|
|
138
|
-
else: # SHORT
|
|
139
|
-
if rsi >= 80:
|
|
140
|
-
score += 3
|
|
141
|
-
reasons.append(f"rsi_{rsi:.0f}_deep_overbought")
|
|
142
|
-
elif rsi >= 75:
|
|
143
|
-
score += 2
|
|
144
|
-
reasons.append(f"rsi_{rsi:.0f}_overbought")
|
|
145
|
-
elif rsi >= rsi_ob:
|
|
146
|
-
score += 1
|
|
147
|
-
reasons.append(f"rsi_{rsi:.0f}_overbought")
|
|
148
|
-
if stretch >= 2 * stretch_thresh:
|
|
149
|
-
score += 2
|
|
150
|
-
reasons.append(f"stretch_{stretch:+.2f}%")
|
|
151
|
-
elif stretch >= stretch_thresh:
|
|
152
|
-
score += 1
|
|
153
|
-
reasons.append(f"stretch_{stretch:+.2f}%")
|
|
154
|
-
if trend1 == "BEARISH":
|
|
155
|
-
score += 1
|
|
156
|
-
reasons.append("1h_downtrend_rip")
|
|
157
|
-
elif trend1 == "BULLISH":
|
|
158
|
-
score -= 2
|
|
159
|
-
reasons.append("1h_uptrend_knife")
|
|
160
|
-
if funding > 0:
|
|
161
|
-
score += 1
|
|
162
|
-
reasons.append(f"funding_pos_{funding:+.4f}")
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
"coin": asset, "direction": direction, "score": score,
|
|
166
|
-
"reasons": reasons, "price": price, "rsi": rsi,
|
|
167
|
-
"trend1h": trend1, "stretchPct": stretch, "funding": funding,
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def clamp_leverage(desired, venue_max):
|
|
172
|
-
"""Clamp desired leverage to the asset's Hyperliquid venue max."""
|
|
173
|
-
try:
|
|
174
|
-
venue = int(venue_max)
|
|
175
|
-
except (TypeError, ValueError):
|
|
176
|
-
venue = desired
|
|
177
|
-
if venue <= 0:
|
|
178
|
-
venue = desired
|
|
179
|
-
return max(1, min(int(desired), venue))
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# ── SPIDER · SWING leg (v2 supervised) ──────────────────────────────
|
|
2
|
-
# Tech & AI multi-day momentum, LONG only.
|
|
3
|
-
#
|
|
4
|
-
# Ported from senpi-skills/spider (producer v5.1.1, SPIDER_LEG=swing) to the
|
|
5
|
-
# v2 supervised external-scanner contract. The producer daemon is gone: the
|
|
6
|
-
# runtime supervises ./scanners/scan.py and calls scan(inputs, ctx) every
|
|
7
|
-
# `interval_seconds`. All scoring thresholds preserved exactly; see scan.py.
|
|
8
|
-
name: spider-swing
|
|
9
|
-
version: 2.0.0
|
|
10
|
-
group: spider # agent-facing handle (BOTH legs share it)
|
|
11
|
-
description: >
|
|
12
|
-
SPIDER SWING — Tech & AI multi-day momentum, LONG only. Scores a dynamic
|
|
13
|
-
universe (curated tech/AI/space XYZ equities auto-rebuilt from the live
|
|
14
|
-
instrument board + a static crypto-alt pool) on 4h/1h trend structure, 24h
|
|
15
|
-
relative strength, RSI room, funding crowding, and smart-money consensus.
|
|
16
|
-
minScore 5; wide let-winners-run DSL, all time-cuts off, 7d staleness cap.
|
|
17
|
-
Paired with the SCALP leg on a separate wallet.
|
|
18
|
-
|
|
19
|
-
strategy:
|
|
20
|
-
wallet: "${SPIDER_SWING_WALLET}"
|
|
21
|
-
slots: 3 # source maxSlots 3
|
|
22
|
-
margin_pct: 28 # source marginPct 0.28 → margin_usd = account_value * 0.28
|
|
23
|
-
default_leverage: 10 # source swingMaxLeverage cap (clamped per-asset to HL venue max in scan.py)
|
|
24
|
-
trading_risk: moderate
|
|
25
|
-
enabled: true
|
|
26
|
-
|
|
27
|
-
scanners:
|
|
28
|
-
- name: position_tracker # built-in: feeds DSL exit engine
|
|
29
|
-
type: position_tracker
|
|
30
|
-
interval_seconds: 10
|
|
31
|
-
|
|
32
|
-
- name: spider_swing_signals # supervised external scanner
|
|
33
|
-
type: external_scanner
|
|
34
|
-
path: ./scanners
|
|
35
|
-
entrypoint: scan.py
|
|
36
|
-
interval_seconds: 300 # source tickSeconds 300
|
|
37
|
-
timeout_seconds: 180 # source tick_timeout = min(180, max(30, tick-10))
|
|
38
|
-
default_signal_validity_seconds: 600 # 2x tick; LLM gate is pass-through so a fresh re-score arrives next tick
|
|
39
|
-
state_history_max_count: 200 # holds recent-signal dedup + xyz first-seen ledger records
|
|
40
|
-
inputs:
|
|
41
|
-
# ── universe knobs (from spider-swing-config.json) ──
|
|
42
|
-
cryptoAlts: ["SUI", "ONDO", "HYPE", "NIL", "GRASS", "ZEC"]
|
|
43
|
-
xyzIncludeSet: ["NVDA", "AMD", "INTC", "MRVL", "MU", "TSM", "ASML", "ARM", "SMSN", "SKHX", "DRAM", "SNDK", "DELL", "LITE", "CRWV", "PLTR", "ORCL", "GOOGL", "META", "MSFT", "AMZN", "AAPL", "NFLX", "IBM", "COIN", "MSTR", "CRCL", "HOOD", "SPCX", "RKLB", "CBRS"]
|
|
44
|
-
xyzExcludeSet: ["GOLD", "SILVER", "PLATINUM", "PALLADIUM", "COPPER", "CL", "BRENTOIL", "NATGAS", "URANIUM", "URNM", "EUR", "JPY", "GBP", "KRW", "DXY", "SP500", "JP225", "KR200", "NIFTY", "IBOV", "XYZ100", "EWY", "EWJ", "EWT", "EWZ", "XLE", "VIX", "PURRDAT", "BIRD"]
|
|
45
|
-
xyzVolFloorUsd: 5000000
|
|
46
|
-
xyzFreshDays: 21
|
|
47
|
-
xyzMaxNames: 20
|
|
48
|
-
allowedAssets: ["xyz:NVDA", "xyz:AMD", "xyz:INTC", "xyz:MRVL", "xyz:MU", "xyz:TSM", "SUI", "ONDO", "HYPE", "NIL", "GRASS", "ZEC"]
|
|
49
|
-
# ── scoring knobs ──
|
|
50
|
-
minScore: 5
|
|
51
|
-
marginPct: 0.28
|
|
52
|
-
maxLeverage: 10
|
|
53
|
-
maxSlots: 3
|
|
54
|
-
rsiMaxLong: 78
|
|
55
|
-
useSmBonus: true
|
|
56
|
-
venueMinNotionalUsd: 10
|
|
57
|
-
minNotionalPctOfEquity: 0.01
|
|
58
|
-
recentSignalTtlSeconds: 180
|
|
59
|
-
signal_data_schema:
|
|
60
|
-
score: { type: number }
|
|
61
|
-
direction: { type: string }
|
|
62
|
-
reasons: { type: array, required: false }
|
|
63
|
-
trend4h: { type: string, required: false }
|
|
64
|
-
rs: { type: number, required: false }
|
|
65
|
-
smPct: { type: number, required: false }
|
|
66
|
-
heldAssets: { type: array, required: false }
|
|
67
|
-
|
|
68
|
-
actions:
|
|
69
|
-
- name: position_tracker_action # rule-based lifecycle → DSL
|
|
70
|
-
action_type: POSITION_TRACKER
|
|
71
|
-
decision_mode: rule
|
|
72
|
-
scanners: [position_tracker]
|
|
73
|
-
|
|
74
|
-
- name: spider_swing_entry # OPEN_POSITION, rule mode (source LLM gate was pure pass-through)
|
|
75
|
-
action_type: OPEN_POSITION
|
|
76
|
-
decision_mode: rule
|
|
77
|
-
scanners: [spider_swing_signals]
|
|
78
|
-
params:
|
|
79
|
-
order_type: FEE_OPTIMIZED_LIMIT
|
|
80
|
-
fee_optimized_limit_options:
|
|
81
|
-
ensure_execution_as_taker: true
|
|
82
|
-
execution_timeout_seconds: 60
|
|
83
|
-
context:
|
|
84
|
-
- type: signal
|
|
85
|
-
scanner: spider_swing_signals
|
|
86
|
-
|
|
87
|
-
# ── EXIT (DSL — wide let-winners-run; the style rides multi-day) ──
|
|
88
|
-
exit:
|
|
89
|
-
engine: dsl
|
|
90
|
-
interval_seconds: 60
|
|
91
|
-
order_type: FEE_OPTIMIZED_LIMIT
|
|
92
|
-
fee_optimized_limit_options:
|
|
93
|
-
ensure_execution_as_taker: true
|
|
94
|
-
execution_timeout_seconds: 60
|
|
95
|
-
dsl_preset:
|
|
96
|
-
hard_timeout:
|
|
97
|
-
enabled: true
|
|
98
|
-
interval_in_minutes: 10080 # 7d — matches source max hold
|
|
99
|
-
weak_peak_cut:
|
|
100
|
-
enabled: false # patience leg
|
|
101
|
-
interval_in_minutes: 720
|
|
102
|
-
min_value: 3.0
|
|
103
|
-
dead_weight_cut:
|
|
104
|
-
enabled: false # patience leg
|
|
105
|
-
interval_in_minutes: 1440
|
|
106
|
-
phase1:
|
|
107
|
-
enabled: true
|
|
108
|
-
max_loss_pct: 22.0 # source tolerates deep drawdowns
|
|
109
|
-
retrace_threshold: 12
|
|
110
|
-
consecutive_breaches_required: 1
|
|
111
|
-
phase2:
|
|
112
|
-
enabled: true
|
|
113
|
-
tiers:
|
|
114
|
-
# FIDELITY NOTE: the source had a 6th tier { trigger_pct: 150, lock_hw_pct: 90 }.
|
|
115
|
-
# The v2 DSL caps trigger_pct at <= 100 (src/dsl/config/parse-tiers.ts), so the
|
|
116
|
-
# +150% tier cannot be expressed. Dropped; the +100% tier (lock 80%) is now the
|
|
117
|
-
# top of the ladder. All tiers <= 100 are carried verbatim. This is the single
|
|
118
|
-
# threshold that does not map cleanly to the v2 schema.
|
|
119
|
-
- { trigger_pct: 15, lock_hw_pct: 0 }
|
|
120
|
-
- { trigger_pct: 30, lock_hw_pct: 45 }
|
|
121
|
-
- { trigger_pct: 60, lock_hw_pct: 68 }
|
|
122
|
-
- { trigger_pct: 100, lock_hw_pct: 80 }
|
|
123
|
-
|
|
124
|
-
# ── RISK guard rails (from runtime-swing.yaml; minutes → seconds) ──
|
|
125
|
-
risk:
|
|
126
|
-
data_retention_seconds: 604800 # 7d (was data_retention_hours 168)
|
|
127
|
-
guard_rails:
|
|
128
|
-
daily_loss_limit_pct: 15
|
|
129
|
-
max_entries_per_day: 12
|
|
130
|
-
bypass_max_entries_per_day_on_profit: true
|
|
131
|
-
max_consecutive_losses: 4
|
|
132
|
-
cooldown_seconds: 5400 # was cooldown_minutes 90
|
|
133
|
-
drawdown_halt_pct: 25
|
|
134
|
-
drawdown_reset_on_day_rollover: true
|
|
135
|
-
per_asset_cooldown_seconds: 14400 # was per_asset_cooldown_minutes 240 (4h)
|
|
136
|
-
|
|
137
|
-
notifications:
|
|
138
|
-
telegram_chat_id: "${TELEGRAM_CHAT_ID}"
|
|
139
|
-
dsl_lifecycle: true
|
|
140
|
-
dsl_notify_sl_updates: false
|
|
141
|
-
action_lifecycle: true
|