@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
|
@@ -51,7 +51,7 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
51
51
|
normalizeDslCoinDex(event.coin, event.dex ?? "").coin &&
|
|
52
52
|
s.direction === newDirection);
|
|
53
53
|
}
|
|
54
|
-
async function handlePositionOpened(payload, triggerReason
|
|
54
|
+
async function handlePositionOpened(payload, triggerReason) {
|
|
55
55
|
let event = null;
|
|
56
56
|
if (isHookEvent(payload)) {
|
|
57
57
|
event = hookToPositionOpenedEvent(payload);
|
|
@@ -72,125 +72,105 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
72
72
|
return;
|
|
73
73
|
event = normalizePositionOpenedEvent(event);
|
|
74
74
|
const dexKey = event.dex ?? "";
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await stateManager.withKeyLock(posKey, async () => {
|
|
79
|
-
// Re-read inside the lock so a racing adopt that already wrote is visible.
|
|
80
|
-
const existing = await stateManager.getDslState(event.address, event.coin, dexKey);
|
|
81
|
-
if (existing?.active) {
|
|
82
|
-
logger.warn("[dsl] position.opened ignored: DSL state already active for this asset/dex", {
|
|
83
|
-
address: event.address,
|
|
84
|
-
asset: event.coin,
|
|
85
|
-
dex: dexKey,
|
|
86
|
-
});
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (await isDuplicatePositionEvent(event)) {
|
|
90
|
-
logger.warn("[dsl] duplicate position event ignored (same entry, size, coin, direction)", {
|
|
91
|
-
address: event.address,
|
|
92
|
-
asset: event.coin,
|
|
93
|
-
});
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const presetName = event.preset ?? config.preset;
|
|
97
|
-
const preset = config.presets[presetName];
|
|
98
|
-
if (!preset) {
|
|
99
|
-
logger.warn("preset not found, skipping DSL", { presetName, asset: event.coin });
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
const entryPrice = parseFloat(String(event.entryPx));
|
|
103
|
-
const direction = parseFloat(String(event.szi)) > 0 ? "LONG" : "SHORT";
|
|
104
|
-
const leverage = event.leverage ?? 1;
|
|
105
|
-
const phase1Floor = phase1FloorFromPreset(entryPrice, direction, preset.maxLossPct, leverage);
|
|
106
|
-
const resolvedTrigger = triggerReason ?? "position_opened";
|
|
107
|
-
const state = buildInitialState(event, preset, phase1Floor, resolvedTrigger);
|
|
108
|
-
const floor = state.floorPrice;
|
|
109
|
-
logger.info("[dsl] position.opened handling", {
|
|
75
|
+
const existing = await stateManager.getDslState(event.address, event.coin, dexKey);
|
|
76
|
+
if (existing?.active) {
|
|
77
|
+
logger.warn("[dsl] position.opened ignored: DSL state already active for this asset/dex", {
|
|
110
78
|
address: event.address,
|
|
111
79
|
asset: event.coin,
|
|
112
|
-
dex:
|
|
113
|
-
|
|
114
|
-
|
|
80
|
+
dex: dexKey,
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (await isDuplicatePositionEvent(event)) {
|
|
85
|
+
logger.warn("[dsl] duplicate position event ignored (same entry, size, coin, direction)", {
|
|
86
|
+
address: event.address,
|
|
87
|
+
asset: event.coin,
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const presetName = event.preset ?? config.preset;
|
|
92
|
+
const preset = config.presets[presetName];
|
|
93
|
+
if (!preset) {
|
|
94
|
+
logger.warn("preset not found, skipping DSL", { presetName, asset: event.coin });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const entryPrice = parseFloat(String(event.entryPx));
|
|
98
|
+
const direction = parseFloat(String(event.szi)) > 0 ? "LONG" : "SHORT";
|
|
99
|
+
const leverage = event.leverage ?? 1;
|
|
100
|
+
const phase1Floor = phase1FloorFromPreset(entryPrice, direction, preset.maxLossPct, leverage);
|
|
101
|
+
const resolvedTrigger = triggerReason ?? "position_opened";
|
|
102
|
+
const state = buildInitialState(event, preset, phase1Floor, resolvedTrigger);
|
|
103
|
+
const floor = state.floorPrice;
|
|
104
|
+
logger.info("[dsl] position.opened handling", {
|
|
105
|
+
address: event.address,
|
|
106
|
+
asset: event.coin,
|
|
107
|
+
dex: event.dex ?? "",
|
|
108
|
+
preset: presetName,
|
|
109
|
+
floor,
|
|
110
|
+
wallet: event.wallet,
|
|
111
|
+
});
|
|
112
|
+
const strategy = strategyStates.get(event.address);
|
|
113
|
+
if (!strategy) {
|
|
114
|
+
logger.warn("[dsl] no strategy found for address, skipping initial SL", { address: event.address });
|
|
115
|
+
state.slOrderId = null;
|
|
116
|
+
state.lastSlSyncError = "no strategy state for address";
|
|
117
|
+
state.lastError = state.lastSlSyncError;
|
|
118
|
+
state.lastErrorAt = now();
|
|
119
|
+
await stateManager.setDslState(event.address, event.coin, event.dex ?? "", state);
|
|
120
|
+
bus.emit(DSL_EVENTS.CREATED, buildDslCreatedPayload(event, presetName, preset, state, now()));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const posKey = stateKey(event.address, event.coin, event.dex ?? "");
|
|
124
|
+
slPlacementInProgress.add(posKey);
|
|
125
|
+
try {
|
|
126
|
+
await stateManager.setDslState(event.address, event.coin, event.dex ?? "", state);
|
|
127
|
+
logger.info("[dsl] initial state written, calling editPosition for initial SL", {
|
|
128
|
+
asset: event.coin,
|
|
115
129
|
wallet: event.wallet,
|
|
130
|
+
dex: event.dex || undefined,
|
|
131
|
+
floor,
|
|
116
132
|
});
|
|
117
|
-
const strategy = strategyStates.get(event.address);
|
|
118
|
-
if (!strategy) {
|
|
119
|
-
logger.warn("[dsl] no strategy found for address, skipping initial SL", { address: event.address });
|
|
120
|
-
state.slOrderId = null;
|
|
121
|
-
state.lastSlSyncError = "no strategy state for address";
|
|
122
|
-
state.lastError = state.lastSlSyncError;
|
|
123
|
-
state.lastErrorAt = now();
|
|
124
|
-
await stateManager.setDslState(event.address, event.coin, event.dex ?? "", state);
|
|
125
|
-
bus.emit(DSL_EVENTS.CREATED, buildDslCreatedPayload(event, presetName, preset, state, now()));
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const shouldPlaceSl = opts?.placeSl !== false;
|
|
129
|
-
if (!shouldPlaceSl) {
|
|
130
|
-
// Adoption with deferred SL: monitor places it on the next tick via shouldSyncExchangeSl.
|
|
131
|
-
state.slOrderId = null;
|
|
132
|
-
state.lastSlSyncError = "adopt: SL deferred to monitor";
|
|
133
|
-
logger.info("[dsl] initial state written, SL deferred to monitor (adopt)", {
|
|
134
|
-
asset: event.coin,
|
|
135
|
-
wallet: event.wallet,
|
|
136
|
-
dex: event.dex || undefined,
|
|
137
|
-
floor,
|
|
138
|
-
});
|
|
139
|
-
await stateManager.setDslState(event.address, event.coin, event.dex ?? "", state);
|
|
140
|
-
bus.emit(DSL_EVENTS.CREATED, buildDslCreatedPayload(event, presetName, preset, state, now()));
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
slPlacementInProgress.add(posKey);
|
|
144
133
|
try {
|
|
145
|
-
await
|
|
146
|
-
|
|
147
|
-
asset: event.coin,
|
|
148
|
-
wallet: event.wallet,
|
|
134
|
+
const res = await withRetry(() => strategy.editPosition({
|
|
135
|
+
coin: event.coin,
|
|
149
136
|
dex: event.dex || undefined,
|
|
150
|
-
floor,
|
|
137
|
+
stopLoss: { triggerPx: floor },
|
|
138
|
+
}), {
|
|
139
|
+
retries: DSL_EXTERNAL_RETRIES,
|
|
140
|
+
delayMs: DSL_EXTERNAL_RETRY_DELAY_MS,
|
|
141
|
+
label: "editPosition (initial SL)",
|
|
142
|
+
onRetry: (attempt, remaining, err) => logger.warn("[dsl] editPosition retry", { attempt, remaining, asset: event.coin, err }),
|
|
151
143
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}), {
|
|
158
|
-
retries: DSL_EXTERNAL_RETRIES,
|
|
159
|
-
delayMs: DSL_EXTERNAL_RETRY_DELAY_MS,
|
|
160
|
-
label: "editPosition (initial SL)",
|
|
161
|
-
onRetry: (attempt, remaining, err) => logger.warn("[dsl] editPosition retry", { attempt, remaining, asset: event.coin, err }),
|
|
162
|
-
});
|
|
163
|
-
state.slOrderId = res.slOrderId ?? null;
|
|
164
|
-
state.lastSyncedFloorPrice = floor;
|
|
165
|
-
state.slOrderIdUpdatedAt = now();
|
|
166
|
-
state.lastSlSyncError = null;
|
|
167
|
-
logger.info("[dsl] exchange SL created (position.opened)", {
|
|
168
|
-
asset: event.coin,
|
|
169
|
-
address: event.address,
|
|
170
|
-
slOrderId: state.slOrderId,
|
|
171
|
-
floorPrice: floor,
|
|
172
|
-
entryPrice: state.entryPrice,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
catch (err) {
|
|
176
|
-
state.slOrderId = null;
|
|
177
|
-
state.lastSlSyncError = String(err);
|
|
178
|
-
state.lastError = String(err);
|
|
179
|
-
state.lastErrorAt = now();
|
|
180
|
-
logger.warn("initial SL place failed", { asset: event.coin, err });
|
|
181
|
-
}
|
|
182
|
-
logger.info("[dsl] writing state after initial SL", {
|
|
144
|
+
state.slOrderId = res.slOrderId ?? null;
|
|
145
|
+
state.lastSyncedFloorPrice = floor;
|
|
146
|
+
state.slOrderIdUpdatedAt = now();
|
|
147
|
+
state.lastSlSyncError = null;
|
|
148
|
+
logger.info("[dsl] exchange SL created (position.opened)", {
|
|
183
149
|
asset: event.coin,
|
|
150
|
+
address: event.address,
|
|
184
151
|
slOrderId: state.slOrderId,
|
|
185
|
-
|
|
152
|
+
floorPrice: floor,
|
|
153
|
+
entryPrice: state.entryPrice,
|
|
186
154
|
});
|
|
187
|
-
await stateManager.setDslState(event.address, event.coin, event.dex ?? "", state);
|
|
188
|
-
bus.emit(DSL_EVENTS.CREATED, buildDslCreatedPayload(event, presetName, preset, state, now()));
|
|
189
155
|
}
|
|
190
|
-
|
|
191
|
-
|
|
156
|
+
catch (err) {
|
|
157
|
+
state.slOrderId = null;
|
|
158
|
+
state.lastSlSyncError = String(err);
|
|
159
|
+
state.lastError = String(err);
|
|
160
|
+
state.lastErrorAt = now();
|
|
161
|
+
logger.warn("initial SL place failed", { asset: event.coin, err });
|
|
192
162
|
}
|
|
193
|
-
|
|
163
|
+
logger.info("[dsl] writing state after initial SL", {
|
|
164
|
+
asset: event.coin,
|
|
165
|
+
slOrderId: state.slOrderId,
|
|
166
|
+
key: posKey,
|
|
167
|
+
});
|
|
168
|
+
await stateManager.setDslState(event.address, event.coin, event.dex ?? "", state);
|
|
169
|
+
bus.emit(DSL_EVENTS.CREATED, buildDslCreatedPayload(event, presetName, preset, state, now()));
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
slPlacementInProgress.delete(posKey);
|
|
173
|
+
}
|
|
194
174
|
}
|
|
195
175
|
async function handlePositionClosed(payload) {
|
|
196
176
|
let event = null;
|
|
@@ -208,65 +188,62 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
208
188
|
asset: event.asset,
|
|
209
189
|
dex: event.dex ?? "",
|
|
210
190
|
});
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (status?.filled)
|
|
230
|
-
reason = CLOSE_REASON.EXCHANGE_SL_HIT;
|
|
231
|
-
}
|
|
232
|
-
catch (e) {
|
|
233
|
-
logger.warn("getOrderStatus failed, assuming manual_close", { asset: event.asset, err: e });
|
|
234
|
-
}
|
|
191
|
+
const state = await stateManager.getDslStateByAsset(event.address, event.asset, event.dex);
|
|
192
|
+
if (!state?.active)
|
|
193
|
+
return;
|
|
194
|
+
logger.info("[dsl] closing active state", { asset: state.asset, slOrderId: state.slOrderId });
|
|
195
|
+
let reason = CLOSE_REASON.MANUAL_CLOSE;
|
|
196
|
+
if (state.slOrderId != null) {
|
|
197
|
+
try {
|
|
198
|
+
const strategy = strategyStates.get(state.address);
|
|
199
|
+
const status = strategy
|
|
200
|
+
? await withRetry(() => strategy.getOrderStatus(state.slOrderId), {
|
|
201
|
+
retries: DSL_EXTERNAL_RETRIES,
|
|
202
|
+
delayMs: DSL_EXTERNAL_RETRY_DELAY_MS,
|
|
203
|
+
label: "getOrderStatus (position.closed)",
|
|
204
|
+
onRetry: (attempt, remaining, err) => logger.warn("[dsl] getOrderStatus retry", { attempt, remaining, asset: event.asset, err }),
|
|
205
|
+
})
|
|
206
|
+
: null;
|
|
207
|
+
if (status?.filled)
|
|
208
|
+
reason = CLOSE_REASON.EXCHANGE_SL_HIT;
|
|
235
209
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const archiveCloseReason = reason === CLOSE_REASON.MANUAL_CLOSE && dataCloseReason ? dataCloseReason : reason;
|
|
239
|
-
logger.info("[dsl] archiving position (position.closed)", {
|
|
240
|
-
address: event.address,
|
|
241
|
-
asset: state.asset,
|
|
242
|
-
closeReason: archiveCloseReason,
|
|
243
|
-
slOrderId: state.slOrderId,
|
|
244
|
-
fromHook: fromHook,
|
|
245
|
-
});
|
|
246
|
-
const closedState = { ...state, active: false, closedAt: now(), closeReason: archiveCloseReason };
|
|
247
|
-
await stateManager.archiveAndDeleteDslState(state.address, state.asset, state.dex, closedState);
|
|
248
|
-
if (state.handoffStatus === "ok" && state.backendDslPositionId != null) {
|
|
249
|
-
fireAndForgetBackendDelete(state, "position.closed");
|
|
210
|
+
catch (e) {
|
|
211
|
+
logger.warn("getOrderStatus failed, assuming manual_close", { asset: event.asset, err: e });
|
|
250
212
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
213
|
+
}
|
|
214
|
+
const rawCloseReason = fromHook ? payload.data?.closeReason : undefined;
|
|
215
|
+
const dataCloseReason = typeof rawCloseReason === "string" ? rawCloseReason : null;
|
|
216
|
+
const archiveCloseReason = reason === CLOSE_REASON.MANUAL_CLOSE && dataCloseReason ? dataCloseReason : reason;
|
|
217
|
+
logger.info("[dsl] archiving position (position.closed)", {
|
|
218
|
+
address: event.address,
|
|
219
|
+
asset: state.asset,
|
|
220
|
+
closeReason: archiveCloseReason,
|
|
221
|
+
slOrderId: state.slOrderId,
|
|
222
|
+
fromHook: fromHook,
|
|
223
|
+
});
|
|
224
|
+
const closedState = { ...state, active: false, closedAt: now(), closeReason: archiveCloseReason };
|
|
225
|
+
await stateManager.archiveAndDeleteDslState(state.address, state.asset, state.dex, closedState);
|
|
226
|
+
if (state.handoffStatus === "ok" && state.backendDslPositionId != null) {
|
|
227
|
+
fireAndForgetBackendDelete(state, "position.closed");
|
|
228
|
+
}
|
|
229
|
+
const closedTs = now();
|
|
230
|
+
bus.emit(EVENTS.POSITION_CLOSED, {
|
|
231
|
+
address: state.address,
|
|
232
|
+
asset: state.asset,
|
|
233
|
+
wallet: state.wallet,
|
|
234
|
+
reason,
|
|
235
|
+
closeReason: archiveCloseReason,
|
|
236
|
+
timestamp: closedTs,
|
|
237
|
+
// Provenance marker (data only, no consumer branches on it): this emit re-broadcasts a close
|
|
238
|
+
// after archiving DSL state; the recording subscriber skips it to avoid double-counting a
|
|
239
|
+
// close already recorded from the original (enveloped or flat) emit.
|
|
240
|
+
source: "dsl_archive",
|
|
269
241
|
});
|
|
242
|
+
bus.emit(DSL_EVENTS.CLOSED, buildDslClosedEventFromState(closedState, {
|
|
243
|
+
reason: archiveCloseReason,
|
|
244
|
+
closeReason: archiveCloseReason,
|
|
245
|
+
timestamp: closedTs,
|
|
246
|
+
}));
|
|
270
247
|
}
|
|
271
248
|
async function handlePositionFlipped(payload) {
|
|
272
249
|
let event = null;
|
|
@@ -283,36 +260,26 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
283
260
|
archiveAsset: event.asset,
|
|
284
261
|
newCoin: event.coin,
|
|
285
262
|
});
|
|
286
|
-
// Archive the OLD state under its own key — lock released before calling handlePositionOpened
|
|
287
|
-
// (which locks the NEW key itself; keys differ after a flip).
|
|
288
263
|
const oldState = await stateManager.getDslStateByAsset(event.address, event.asset, event.dex);
|
|
289
264
|
if (oldState?.active) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
logger.info("[dsl] archiving position (position.flipped)", {
|
|
297
|
-
address: event.address,
|
|
298
|
-
asset: currentOld.asset,
|
|
299
|
-
dex: currentOld.dex ?? "",
|
|
300
|
-
closeReason: CLOSE_REASON.FLIPPED,
|
|
301
|
-
slOrderId: currentOld.slOrderId,
|
|
302
|
-
});
|
|
303
|
-
const closedState = { ...currentOld, active: false, closedAt: now(), closeReason: CLOSE_REASON.FLIPPED };
|
|
304
|
-
// Must use canonical keys from loaded state — not event.asset (e.g. xyz:BASE vs BASE + dex xyz
|
|
305
|
-
// produce different stateKey paths; wrong asset would archive nothing and leave the live file active).
|
|
306
|
-
await stateManager.archiveAndDeleteDslState(event.address, currentOld.asset, currentOld.dex ?? "", closedState);
|
|
307
|
-
if (currentOld.handoffStatus === "ok" && currentOld.backendDslPositionId != null) {
|
|
308
|
-
fireAndForgetBackendDelete(currentOld, "position.flipped");
|
|
309
|
-
}
|
|
265
|
+
logger.info("[dsl] archiving position (position.flipped)", {
|
|
266
|
+
address: event.address,
|
|
267
|
+
asset: oldState.asset,
|
|
268
|
+
dex: oldState.dex ?? "",
|
|
269
|
+
closeReason: CLOSE_REASON.FLIPPED,
|
|
270
|
+
slOrderId: oldState.slOrderId,
|
|
310
271
|
});
|
|
272
|
+
const closedState = { ...oldState, active: false, closedAt: now(), closeReason: CLOSE_REASON.FLIPPED };
|
|
273
|
+
// Must use canonical keys from loaded state — not event.asset (e.g. xyz:BASE vs BASE + dex xyz
|
|
274
|
+
// produce different stateKey paths; wrong asset would archive nothing and leave the live file active).
|
|
275
|
+
await stateManager.archiveAndDeleteDslState(event.address, oldState.asset, oldState.dex ?? "", closedState);
|
|
276
|
+
if (oldState.handoffStatus === "ok" && oldState.backendDslPositionId != null) {
|
|
277
|
+
fireAndForgetBackendDelete(oldState, "position.flipped");
|
|
278
|
+
}
|
|
311
279
|
}
|
|
312
280
|
else {
|
|
313
281
|
logger.warn("[dsl] position.flipped: no active DSL state matched for archive (check asset/dex vs state file). If a live file still exists for this market, the next position.opened may be ignored as duplicate.", { address: event.address, asset: event.asset, dex: event.dex ?? "" });
|
|
314
282
|
}
|
|
315
|
-
// Old-key lock is released above. handlePositionOpened locks the NEW key itself.
|
|
316
283
|
// New row uses `coin` (and dex) from the flip payload — not `asset`, which identifies the pre-flip state for archival.
|
|
317
284
|
const openedPayload = {
|
|
318
285
|
address: event.address,
|
|
@@ -338,13 +305,8 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
338
305
|
if (!raw)
|
|
339
306
|
return;
|
|
340
307
|
const event = normalizePositionOpenedEvent(raw);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const archiveKey = stateKey(event.address, event.coin, event.dex ?? "");
|
|
344
|
-
await stateManager.withKeyLock(archiveKey, async () => {
|
|
345
|
-
const oldState = await stateManager.getDslStateByAsset(event.address, event.coin, event.dex);
|
|
346
|
-
if (!oldState?.active)
|
|
347
|
-
return;
|
|
308
|
+
const oldState = await stateManager.getDslStateByAsset(event.address, event.coin, event.dex);
|
|
309
|
+
if (oldState?.active) {
|
|
348
310
|
const closedState = {
|
|
349
311
|
...oldState,
|
|
350
312
|
active: false,
|
|
@@ -357,8 +319,7 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
357
319
|
if (oldState.handoffStatus === "ok" && oldState.backendDslPositionId != null) {
|
|
358
320
|
fireAndForgetBackendDelete(oldState, "position.increased");
|
|
359
321
|
}
|
|
360
|
-
}
|
|
361
|
-
// archiveKey lock is released; handlePositionOpened re-acquires it safely.
|
|
322
|
+
}
|
|
362
323
|
await handlePositionOpened(event, "position_increased");
|
|
363
324
|
}
|
|
364
325
|
async function handlePositionDecreased(payload) {
|
|
@@ -372,13 +333,8 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
372
333
|
if (!raw)
|
|
373
334
|
return;
|
|
374
335
|
const event = normalizePositionOpenedEvent(raw);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const archiveKey = stateKey(event.address, event.coin, event.dex ?? "");
|
|
378
|
-
await stateManager.withKeyLock(archiveKey, async () => {
|
|
379
|
-
const oldState = await stateManager.getDslStateByAsset(event.address, event.coin, event.dex);
|
|
380
|
-
if (!oldState?.active)
|
|
381
|
-
return;
|
|
336
|
+
const oldState = await stateManager.getDslStateByAsset(event.address, event.coin, event.dex);
|
|
337
|
+
if (oldState?.active) {
|
|
382
338
|
const closedState = {
|
|
383
339
|
...oldState,
|
|
384
340
|
active: false,
|
|
@@ -391,8 +347,7 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
391
347
|
if (oldState.handoffStatus === "ok" && oldState.backendDslPositionId != null) {
|
|
392
348
|
fireAndForgetBackendDelete(oldState, "position.decreased");
|
|
393
349
|
}
|
|
394
|
-
}
|
|
395
|
-
// archiveKey lock is released; handlePositionOpened re-acquires it safely.
|
|
350
|
+
}
|
|
396
351
|
await handlePositionOpened(event, "position_decreased");
|
|
397
352
|
}
|
|
398
353
|
async function deleteDslsForStrategy(address) {
|
|
@@ -400,70 +355,62 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
400
355
|
const ts = now();
|
|
401
356
|
let abandoned = 0;
|
|
402
357
|
for (const key of keys) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (!peeked?.active)
|
|
358
|
+
const state = await stateManager.getOrLoad(key);
|
|
359
|
+
if (!state?.active)
|
|
406
360
|
continue;
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
delayMs: DSL_EXTERNAL_RETRY_DELAY_MS,
|
|
420
|
-
label: "editPosition (cancel SL, dsl delete)",
|
|
421
|
-
onRetry: (attempt, remaining, err) => logger.warn("[dsl] editPosition cancel SL retry", { attempt, remaining, err }),
|
|
422
|
-
}).then(() => undefined).catch((err) => {
|
|
423
|
-
logger.warn("[dsl] cancel SL failed during deleteDslsForStrategy; continuing archive", {
|
|
424
|
-
address,
|
|
425
|
-
asset: state.asset,
|
|
426
|
-
err,
|
|
427
|
-
});
|
|
428
|
-
}));
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
logger.warn("[dsl] no strategy found for address, skipping SL cancel", { address });
|
|
432
|
-
}
|
|
433
|
-
if (state.handoffStatus === "ok" && state.backendDslPositionId != null && strategy) {
|
|
434
|
-
cleanupPromises.push(strategy.deleteRatchetStop({
|
|
361
|
+
abandoned += 1;
|
|
362
|
+
const strategy = strategyStates.get(state.address);
|
|
363
|
+
const cleanupPromises = [];
|
|
364
|
+
if (strategy) {
|
|
365
|
+
cleanupPromises.push(withRetry(() => strategy.editPosition({ coin: state.asset, dex: state.dex || undefined }), {
|
|
366
|
+
retries: DSL_EXTERNAL_RETRIES,
|
|
367
|
+
delayMs: DSL_EXTERNAL_RETRY_DELAY_MS,
|
|
368
|
+
label: "editPosition (cancel SL, dsl delete)",
|
|
369
|
+
onRetry: (attempt, remaining, err) => logger.warn("[dsl] editPosition cancel SL retry", { attempt, remaining, err }),
|
|
370
|
+
}).then(() => undefined).catch((err) => {
|
|
371
|
+
logger.warn("[dsl] cancel SL failed during deleteDslsForStrategy; continuing archive", {
|
|
372
|
+
address,
|
|
435
373
|
asset: state.asset,
|
|
436
|
-
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
asset: state.asset,
|
|
446
|
-
err,
|
|
447
|
-
});
|
|
448
|
-
}));
|
|
449
|
-
}
|
|
450
|
-
await Promise.all(cleanupPromises);
|
|
451
|
-
const closedState = {
|
|
452
|
-
...state,
|
|
453
|
-
active: false,
|
|
454
|
-
closedAt: ts,
|
|
455
|
-
closeReason: CLOSE_REASON.DSL_DELETED,
|
|
456
|
-
};
|
|
457
|
-
await stateManager.archiveAndDeleteDslState(state.address, state.asset, state.dex, closedState);
|
|
458
|
-
bus.emit(DSL_EVENTS.DELETED, {
|
|
459
|
-
address: state.address,
|
|
374
|
+
err,
|
|
375
|
+
});
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
logger.warn("[dsl] no strategy found for address, skipping SL cancel", { address });
|
|
380
|
+
}
|
|
381
|
+
if (state.handoffStatus === "ok" && state.backendDslPositionId != null && strategy) {
|
|
382
|
+
cleanupPromises.push(strategy.deleteRatchetStop({
|
|
460
383
|
asset: state.asset,
|
|
461
|
-
dex: state.dex
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
384
|
+
dex: state.dex || undefined,
|
|
385
|
+
}).then(() => {
|
|
386
|
+
logger.info("[dsl] deleted backend dsl position (deleteDslsForStrategy)", {
|
|
387
|
+
address,
|
|
388
|
+
asset: state.asset,
|
|
389
|
+
});
|
|
390
|
+
}).catch((err) => {
|
|
391
|
+
logger.warn("[dsl] failed to delete backend dsl position (deleteDslsForStrategy)", {
|
|
392
|
+
address,
|
|
393
|
+
asset: state.asset,
|
|
394
|
+
err,
|
|
395
|
+
});
|
|
396
|
+
}));
|
|
397
|
+
}
|
|
398
|
+
await Promise.all(cleanupPromises);
|
|
399
|
+
const closedState = {
|
|
400
|
+
...state,
|
|
401
|
+
active: false,
|
|
402
|
+
closedAt: ts,
|
|
403
|
+
closeReason: CLOSE_REASON.DSL_DELETED,
|
|
404
|
+
};
|
|
405
|
+
await stateManager.archiveAndDeleteDslState(state.address, state.asset, state.dex, closedState);
|
|
406
|
+
bus.emit(DSL_EVENTS.DELETED, {
|
|
407
|
+
address: state.address,
|
|
408
|
+
asset: state.asset,
|
|
409
|
+
dex: state.dex ?? "",
|
|
410
|
+
triggerReason: state.triggerReason,
|
|
411
|
+
__positionId: state.positionId ?? state.correlationId,
|
|
412
|
+
__correlationId: state.correlationId,
|
|
413
|
+
timestamp: ts,
|
|
467
414
|
});
|
|
468
415
|
}
|
|
469
416
|
return abandoned;
|
|
@@ -471,40 +418,6 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
471
418
|
function isSlPlacementInProgress(key) {
|
|
472
419
|
return slPlacementInProgress.has(key);
|
|
473
420
|
}
|
|
474
|
-
/**
|
|
475
|
-
* Adopt an orphaned exchange position into DSL.
|
|
476
|
-
* Calls handlePositionOpened directly — NO bus emit of on_position_opened / position.opened.
|
|
477
|
-
* triggerReason is always "adopted" so formatDslCreated returns null (no user "Opened" message).
|
|
478
|
-
*/
|
|
479
|
-
async function adoptPosition(payload, opts) {
|
|
480
|
-
await handlePositionOpened(payload, "adopted", opts);
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Resize: archive any existing ACTIVE DSL state for the (coin, dex), then call adoptPosition.
|
|
484
|
-
* Archive uses CLOSE_REASON.RECONCILE_RESIZE. Lock is released before the nested adopt
|
|
485
|
-
* (same pattern as handlePositionIncreased) to avoid deadlock.
|
|
486
|
-
*/
|
|
487
|
-
async function resizePosition(payload, opts) {
|
|
488
|
-
const dexKey = payload.dex ?? "";
|
|
489
|
-
const archiveKey = stateKey(payload.address, payload.coin, dexKey);
|
|
490
|
-
await stateManager.withKeyLock(archiveKey, async () => {
|
|
491
|
-
const oldState = await stateManager.getDslStateByAsset(payload.address, payload.coin, dexKey);
|
|
492
|
-
if (!oldState?.active)
|
|
493
|
-
return;
|
|
494
|
-
const closedState = {
|
|
495
|
-
...oldState,
|
|
496
|
-
active: false,
|
|
497
|
-
closedAt: now(),
|
|
498
|
-
closeReason: CLOSE_REASON.RECONCILE_RESIZE,
|
|
499
|
-
};
|
|
500
|
-
await stateManager.archiveAndDeleteDslState(payload.address, oldState.asset, oldState.dex, closedState);
|
|
501
|
-
if (oldState.handoffStatus === "ok" && oldState.backendDslPositionId != null) {
|
|
502
|
-
fireAndForgetBackendDelete(oldState, "reconcile_resize");
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
// archiveKey lock released above; adoptPosition (via handlePositionOpened) re-acquires it safely.
|
|
506
|
-
await adoptPosition(payload, opts);
|
|
507
|
-
}
|
|
508
421
|
function fireAndForgetBackendDelete(state, trigger) {
|
|
509
422
|
const resolveAndDelete = async () => {
|
|
510
423
|
const strategy = strategyStates.get(state.address);
|
|
@@ -552,8 +465,6 @@ export function createEventHandlers(config, stateManager, strategyStates, bus, _
|
|
|
552
465
|
handlePositionDecreased,
|
|
553
466
|
deleteDslsForStrategy,
|
|
554
467
|
isSlPlacementInProgress,
|
|
555
|
-
adoptPosition,
|
|
556
|
-
resizePosition,
|
|
557
468
|
};
|
|
558
469
|
}
|
|
559
470
|
//# sourceMappingURL=handlers.js.map
|