@reefclaw/openclaw-plugin 0.1.11 → 0.1.12
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/bridge/gateway/event-parser.d.ts +23 -0
- package/bridge/gateway/event-parser.js +36 -0
- package/bridge/providers/gateway.js +5 -1
- package/bridge/types.d.ts +4 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/venues/hyperliquid/hl-live-adapter.d.ts +8 -0
- package/venues/hyperliquid/hl-live-adapter.js +26 -1
|
@@ -155,6 +155,29 @@ export declare function extractBracketField(pos: {
|
|
|
155
155
|
tpPrice?: number;
|
|
156
156
|
state: string;
|
|
157
157
|
} | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Narrow the agent's PLANNED protective levels off a plugin-decorated position.
|
|
160
|
+
*
|
|
161
|
+
* `bracket` above is exchange truth and only exists where a venue-side ledger
|
|
162
|
+
* row does. In PAPER (either venue) there are no exchange legs — the levels
|
|
163
|
+
* live in the position's metadata and the stop-watcher enforces them — so
|
|
164
|
+
* without these fields the dashboard had NO target at all and drew the
|
|
165
|
+
* frozen-at-fill `originalStopPrice` as the stop even after a modify_stop.
|
|
166
|
+
* Both adapters already emit `stopPrice` / `targetPrice` on the CCXT position
|
|
167
|
+
* (ExchangeSimulator.getPositions, LiveAdapter.decoratePositionsWithMetadata);
|
|
168
|
+
* we only re-validate the shape here because CcxtPosition's index signature
|
|
169
|
+
* is `unknown`.
|
|
170
|
+
*
|
|
171
|
+
* ★ `plannedStopPrice` is the CURRENT stop (modify_stop moves it), NOT the
|
|
172
|
+
* frozen `originalStopPrice` R-denominator — never conflate the two. Display
|
|
173
|
+
* precedence is bracket (exchange truth) → planned → original.
|
|
174
|
+
*/
|
|
175
|
+
export declare function extractPlannedLevels(pos: {
|
|
176
|
+
[key: string]: unknown;
|
|
177
|
+
}): {
|
|
178
|
+
plannedStopPrice?: number;
|
|
179
|
+
plannedTargetPrice?: number;
|
|
180
|
+
};
|
|
158
181
|
/** The payload shape received from GatewayWsClient 'agent' event */
|
|
159
182
|
export interface AgentEventPayload {
|
|
160
183
|
runId: string;
|
|
@@ -304,6 +304,42 @@ export function extractBracketField(pos) {
|
|
|
304
304
|
out.tpPrice = tpPrice;
|
|
305
305
|
return out;
|
|
306
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Narrow the agent's PLANNED protective levels off a plugin-decorated position.
|
|
309
|
+
*
|
|
310
|
+
* `bracket` above is exchange truth and only exists where a venue-side ledger
|
|
311
|
+
* row does. In PAPER (either venue) there are no exchange legs — the levels
|
|
312
|
+
* live in the position's metadata and the stop-watcher enforces them — so
|
|
313
|
+
* without these fields the dashboard had NO target at all and drew the
|
|
314
|
+
* frozen-at-fill `originalStopPrice` as the stop even after a modify_stop.
|
|
315
|
+
* Both adapters already emit `stopPrice` / `targetPrice` on the CCXT position
|
|
316
|
+
* (ExchangeSimulator.getPositions, LiveAdapter.decoratePositionsWithMetadata);
|
|
317
|
+
* we only re-validate the shape here because CcxtPosition's index signature
|
|
318
|
+
* is `unknown`.
|
|
319
|
+
*
|
|
320
|
+
* ★ `plannedStopPrice` is the CURRENT stop (modify_stop moves it), NOT the
|
|
321
|
+
* frozen `originalStopPrice` R-denominator — never conflate the two. Display
|
|
322
|
+
* precedence is bracket (exchange truth) → planned → original.
|
|
323
|
+
*/
|
|
324
|
+
export function extractPlannedLevels(pos) {
|
|
325
|
+
const num = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : undefined;
|
|
326
|
+
// A `fixed_target` realization rule carries the same number; use it only as a
|
|
327
|
+
// fallback for entries that pinned the rule without a top-level targetPrice.
|
|
328
|
+
const rule = pos.realizationRule;
|
|
329
|
+
const ruleTarget = rule && typeof rule === 'object'
|
|
330
|
+
? rule.type === 'fixed_target'
|
|
331
|
+
? num(rule.targetPrice)
|
|
332
|
+
: undefined
|
|
333
|
+
: undefined;
|
|
334
|
+
const stop = num(pos.stopPrice);
|
|
335
|
+
const target = num(pos.targetPrice) ?? ruleTarget;
|
|
336
|
+
const out = {};
|
|
337
|
+
if (stop !== undefined)
|
|
338
|
+
out.plannedStopPrice = stop;
|
|
339
|
+
if (target !== undefined)
|
|
340
|
+
out.plannedTargetPrice = target;
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
307
343
|
/**
|
|
308
344
|
* Build the wallet/available/locked tuple from Binance Futures `info.assets[]`
|
|
309
345
|
* when present. Returns null when the field is absent or unusable so the
|
|
@@ -9,7 +9,7 @@ import { isTradingMode } from '../types.js';
|
|
|
9
9
|
import { GatewayHttpClient } from '../gateway/gateway-http-client.js';
|
|
10
10
|
import { GatewayWsClient } from '../gateway/gateway-ws-client.js';
|
|
11
11
|
import { discoverTools } from '../gateway/tool-discovery.js';
|
|
12
|
-
import { EventParser, mapCcxtBalance, mapCcxtOrder, extractLiquidationFields, extractBracketField, } from '../gateway/event-parser.js';
|
|
12
|
+
import { EventParser, mapCcxtBalance, mapCcxtOrder, extractLiquidationFields, extractBracketField, extractPlannedLevels, } from '../gateway/event-parser.js';
|
|
13
13
|
import { Poller } from '../gateway/poller.js';
|
|
14
14
|
import { ensureHeartbeatCron } from '../gateway/heartbeat-cron.js';
|
|
15
15
|
import { computeEquity as _computeEquity, computePositionNotional as _computePositionNotional, computeRiskMetrics as _computeRiskMetrics, DEFAULT_RISK_LIMITS, } from './risk-calculator.js';
|
|
@@ -766,6 +766,8 @@ export class GatewayProvider {
|
|
|
766
766
|
giveBackRatio: typeof pos.giveBackRatio === 'number' ? pos.giveBackRatio : undefined,
|
|
767
767
|
originalStopPrice: typeof pos.originalStopPrice === 'number' ? pos.originalStopPrice : undefined,
|
|
768
768
|
...(bracketField !== undefined ? { bracket: bracketField } : {}),
|
|
769
|
+
// Agent's planned stop/target — the ONLY SL/TP source in paper mode.
|
|
770
|
+
...extractPlannedLevels(pos),
|
|
769
771
|
...liqFields,
|
|
770
772
|
});
|
|
771
773
|
}
|
|
@@ -2063,6 +2065,8 @@ export class GatewayProvider {
|
|
|
2063
2065
|
giveBackRatio: typeof p.giveBackRatio === 'number' ? p.giveBackRatio : undefined,
|
|
2064
2066
|
originalStopPrice: typeof p.originalStopPrice === 'number' ? p.originalStopPrice : undefined,
|
|
2065
2067
|
...(bracketField !== undefined ? { bracket: bracketField } : {}),
|
|
2068
|
+
// Agent's planned stop/target — the ONLY SL/TP source in paper mode.
|
|
2069
|
+
...extractPlannedLevels(p),
|
|
2066
2070
|
...liqFields,
|
|
2067
2071
|
};
|
|
2068
2072
|
});
|
package/bridge/types.d.ts
CHANGED
|
@@ -186,6 +186,8 @@ export interface RiskUpdatePayload {
|
|
|
186
186
|
tpPrice?: number;
|
|
187
187
|
state: string;
|
|
188
188
|
};
|
|
189
|
+
plannedStopPrice?: number;
|
|
190
|
+
plannedTargetPrice?: number;
|
|
189
191
|
}>;
|
|
190
192
|
balance?: {
|
|
191
193
|
total: number;
|
|
@@ -239,6 +241,8 @@ export interface ReconciliationSnapshot {
|
|
|
239
241
|
tpPrice?: number;
|
|
240
242
|
state: string;
|
|
241
243
|
};
|
|
244
|
+
plannedStopPrice?: number;
|
|
245
|
+
plannedTargetPrice?: number;
|
|
242
246
|
}>;
|
|
243
247
|
balance: {
|
|
244
248
|
currency: string;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "reefclaw-paper-trading",
|
|
3
3
|
"name": "ReefClaw Trading",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.12",
|
|
5
5
|
"description": "Supervised trading plugin for the ReefClaw dashboard: paper trading with real market data (no API keys required), and optional live trading on Binance or Hyperliquid behind explicit operator opt-in, exchange API credentials, and always-on protective stop brackets. Includes the dashboard connector bridge, heartbeat automation, and remote SKILL.md instruction updates from the ReefClaw webapp.",
|
|
6
6
|
"author": "ReefClaw",
|
|
7
7
|
"activation": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reefclaw/openclaw-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "ReefClaw supervised trading plugin for OpenClaw \u2014 paper trading with real market data, optional live trading on Binance or Hyperliquid (operator opt-in, API keys, always-on protective brackets), plus the ReefClaw dashboard connector with heartbeat automation and remote SKILL.md updates from the ReefClaw webapp. Install: /plugins install clawhub:@reefclaw/openclaw-plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -79,6 +79,14 @@ export declare class HyperliquidLiveAdapter extends EventEmitter implements IExc
|
|
|
79
79
|
*/
|
|
80
80
|
closePosition(symbol: string, _closeReason?: CloseReason): Promise<CcxtOrder>;
|
|
81
81
|
getBalance(): Promise<CcxtBalance>;
|
|
82
|
+
/** The live SL/TP trigger prices for `symbol`, or undefined when no
|
|
83
|
+
* non-terminal ledger row exists. Mirrors LiveAdapter's Binance-side
|
|
84
|
+
* `lookupBracket` so the dashboard's protective-level surfaces read the same
|
|
85
|
+
* shape on both venues. Reads `_coordinator` directly rather than through
|
|
86
|
+
* the lazy getter — merely displaying positions must not construct the
|
|
87
|
+
* coordinator (which writes a ledger file); with no coordinator there are
|
|
88
|
+
* no brackets to report anyway. */
|
|
89
|
+
private lookupBracket;
|
|
82
90
|
/** Display contract (`?? []`) — the 20+ KPI/display callers. */
|
|
83
91
|
getPositions(symbol?: string): Promise<CcxtPosition[]>;
|
|
84
92
|
/** ★ Decision contract — null means UNKNOWN, and destructive paths must not act. */
|
|
@@ -120,6 +120,11 @@ export class HyperliquidLiveAdapter extends EventEmitter {
|
|
|
120
120
|
// Prime the ADDRESS action budget (the starvation guard, §5.6).
|
|
121
121
|
await this.api.refreshAddressBudget();
|
|
122
122
|
// ---- Bracket wiring (issue #209) ----
|
|
123
|
+
// Build the coordinator here, not on first bracket action: getPositions
|
|
124
|
+
// reads its ledger to surface live SL/TP, and until the first truth-check
|
|
125
|
+
// fired (60s) a fresh boot would otherwise show every open position as
|
|
126
|
+
// unprotected on the dashboard. Construction is idempotent + cheap.
|
|
127
|
+
this.getHlBracketCoordinator();
|
|
123
128
|
// Fast path: the user stream (fills drive attach for resting limits;
|
|
124
129
|
// orderUpdates is the authoritative leg-lifecycle signal — the HL analog
|
|
125
130
|
// of Binance's ALGO_UPDATE). Truth path: T-5 proved the WS replays
|
|
@@ -497,9 +502,29 @@ export class HyperliquidLiveAdapter extends EventEmitter {
|
|
|
497
502
|
enriched.equity = round4(nav.equity);
|
|
498
503
|
return enriched;
|
|
499
504
|
}
|
|
505
|
+
/** The live SL/TP trigger prices for `symbol`, or undefined when no
|
|
506
|
+
* non-terminal ledger row exists. Mirrors LiveAdapter's Binance-side
|
|
507
|
+
* `lookupBracket` so the dashboard's protective-level surfaces read the same
|
|
508
|
+
* shape on both venues. Reads `_coordinator` directly rather than through
|
|
509
|
+
* the lazy getter — merely displaying positions must not construct the
|
|
510
|
+
* coordinator (which writes a ledger file); with no coordinator there are
|
|
511
|
+
* no brackets to report anyway. */
|
|
512
|
+
lookupBracket(symbol) {
|
|
513
|
+
const row = this._coordinator?.getLedger().getBySymbol(symbol);
|
|
514
|
+
if (!row)
|
|
515
|
+
return undefined;
|
|
516
|
+
if (row.state !== 'active' && row.state !== 'partial' && row.state !== 'attaching') {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
return { slPrice: row.stopPrice, tpPrice: row.targetPrice, state: row.state };
|
|
520
|
+
}
|
|
500
521
|
/** Display contract (`?? []`) — the 20+ KPI/display callers. */
|
|
501
522
|
async getPositions(symbol) {
|
|
502
|
-
|
|
523
|
+
const positions = (await this.api.fetchPositions(symbol)) ?? [];
|
|
524
|
+
// HL live keeps no per-symbol metadata map, so the bracket ledger is the
|
|
525
|
+
// ONLY protective-level source here — without this the dashboard drew
|
|
526
|
+
// neither a stop nor a target for an HL live position.
|
|
527
|
+
return positions.map((p) => ({ ...p, bracket: this.lookupBracket(p.symbol) }));
|
|
503
528
|
}
|
|
504
529
|
/** ★ Decision contract — null means UNKNOWN, and destructive paths must not act. */
|
|
505
530
|
async getPositionsOrNull(symbol) {
|