@reefclaw/openclaw-plugin 0.1.4 → 0.1.6
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/ccxt/binance-private.d.ts +21 -0
- package/ccxt/binance-private.js +132 -22
- package/config/plugin-config-io.d.ts +6 -0
- package/config/tool-gate.js +3 -0
- package/exchange-adapter.d.ts +16 -0
- package/index.js +567 -57
- package/lifecycle/trading-operation-lock.d.ts +17 -0
- package/lifecycle/trading-operation-lock.js +14 -0
- package/live/bracket-id.d.ts +2 -3
- package/live/bracket-id.js +22 -9
- package/live/live-adapter.d.ts +24 -1
- package/live/live-adapter.js +114 -2
- package/live/local-signal-service.js +11 -6
- package/live/local-strategy-evaluator.js +4 -0
- package/live/proposal-decision-listener.d.ts +6 -0
- package/live/proposal-decision-listener.js +4 -0
- package/live/stop-watcher.d.ts +27 -1
- package/live/stop-watcher.js +59 -2
- package/onboarding/runtime.d.ts +13 -0
- package/onboarding/runtime.js +22 -2
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/persistence/state-manager.d.ts +24 -0
- package/persistence/state-manager.js +62 -4
- package/portfolio/wave9-admission.d.ts +67 -0
- package/portfolio/wave9-admission.js +262 -0
- package/portfolio/wave9-policy.d.ts +36 -0
- package/portfolio/wave9-policy.js +183 -0
- package/signals/conditions/registry.js +50 -0
- package/simulator/exchange-simulator.js +5 -0
- package/simulator/fill-engine.js +5 -1
- package/simulator/types.d.ts +6 -1
- package/strategy/evaluator.d.ts +3 -0
- package/strategy/evaluator.js +5 -0
- package/tools/assessment-validation.d.ts +2 -0
- package/tools/attach-brackets.d.ts +7 -2
- package/tools/attach-brackets.js +36 -0
- package/tools/cancel-all-orders.d.ts +2 -0
- package/tools/cancel-all-orders.js +4 -1
- package/tools/cancel-order.d.ts +4 -0
- package/tools/cancel-order.js +49 -3
- package/tools/close-position.d.ts +23 -0
- package/tools/close-position.js +286 -13
- package/tools/create-order.d.ts +28 -0
- package/tools/create-order.js +1364 -192
- package/tools/get-analytics.js +2 -2
- package/tools/get-basis.js +2 -2
- package/tools/get-cascade-risk.js +2 -2
- package/tools/get-crypto-metrics.js +20 -3
- package/tools/get-cvd.js +2 -2
- package/tools/get-divergences.js +2 -2
- package/tools/get-funding-context.js +2 -2
- package/tools/get-liquidation-levels.js +2 -2
- package/tools/get-liquidation-pulse.js +2 -2
- package/tools/get-pattern-scan.js +2 -2
- package/tools/get-regime.js +2 -2
- package/tools/get-resting-liquidity.js +2 -2
- package/tools/get-risk-scenario.js +2 -2
- package/tools/get-session-review.js +2 -2
- package/tools/get-setup-detail.js +9 -1
- package/tools/get-signals.js +2 -2
- package/tools/get-sizing.js +2 -2
- package/tools/get-trade-feedback.js +2 -2
- package/tools/get-trade-flow.js +2 -2
- package/tools/get-volume-profile.js +2 -2
- package/tools/get-wave9-status.d.ts +127 -0
- package/tools/get-wave9-status.js +796 -0
- package/tools/intel-api.d.ts +20 -0
- package/tools/intel-api.js +67 -0
- package/tools/intel-cache.d.ts +1 -1
- package/tools/intel-cache.js +20 -5
- package/tools/list-strategies.d.ts +11 -1
- package/tools/list-strategies.js +17 -0
- package/tools/modify-stop.d.ts +4 -0
- package/tools/modify-stop.js +63 -24
- package/tools/modify-target.d.ts +4 -0
- package/tools/modify-target.js +62 -23
- package/tools/scan-pairs.js +19 -8
- package/tools/toggle-strategy.js +7 -0
- package/types.d.ts +5 -0
- package/venues/hyperliquid/hl-balance.d.ts +116 -0
- package/venues/hyperliquid/hl-balance.js +145 -0
- package/venues/hyperliquid/hl-brackets.d.ts +102 -0
- package/venues/hyperliquid/hl-brackets.js +172 -0
- package/venues/hyperliquid/hl-cloid.d.ts +22 -0
- package/venues/hyperliquid/hl-cloid.js +82 -0
- package/venues/hyperliquid/hl-info-cache.d.ts +46 -0
- package/venues/hyperliquid/hl-info-cache.js +125 -0
- package/venues/hyperliquid/hl-live-adapter.d.ts +88 -0
- package/venues/hyperliquid/hl-live-adapter.js +353 -0
- package/venues/hyperliquid/hl-precision.d.ts +61 -0
- package/venues/hyperliquid/hl-precision.js +176 -0
- package/venues/hyperliquid/hl-private.d.ts +88 -0
- package/venues/hyperliquid/hl-private.js +357 -0
- package/venues/hyperliquid/hl-public.d.ts +31 -4
- package/venues/hyperliquid/hl-public.js +155 -11
- package/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
- package/venues/hyperliquid/hl-rate-gate.js +220 -0
- package/venues/hyperliquid/hl-user-stream.d.ts +90 -0
- package/venues/hyperliquid/hl-user-stream.js +220 -0
- package/venues/registry.d.ts +28 -9
- package/venues/registry.js +19 -13
- package/venues/symbols.d.ts +43 -0
- package/venues/symbols.js +107 -0
- package/wave9/live-account-capture.d.ts +67 -0
- package/wave9/live-account-capture.js +435 -0
- package/wave9/live-autonomous-protection.d.ts +39 -0
- package/wave9/live-autonomous-protection.js +112 -0
- package/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
- package/wave9/live-durable-reconciliation-scheduler.js +115 -0
- package/wave9/live-execution-ledger.d.ts +107 -0
- package/wave9/live-execution-ledger.js +498 -0
- package/wave9/live-position-confirmation.d.ts +18 -0
- package/wave9/live-position-confirmation.js +111 -0
- package/wave9/live-residual-protection.d.ts +18 -0
- package/wave9/live-residual-protection.js +250 -0
- package/wave9/live-startup-reconciliation.d.ts +38 -0
- package/wave9/live-startup-reconciliation.js +454 -0
- package/wave9/live-symbol-ownership.d.ts +20 -0
- package/wave9/live-symbol-ownership.js +132 -0
- package/wave9/paper-admission-guard.d.ts +199 -0
- package/wave9/paper-admission-guard.js +650 -0
- package/wave9/usdm-evidence-provider.d.ts +42 -0
- package/wave9/usdm-evidence-provider.js +133 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type HlAssetRules, type HlValidationResult, type RoundDirection } from './hl-precision.js';
|
|
2
|
+
interface MetaUniverseEntry {
|
|
3
|
+
name: string;
|
|
4
|
+
szDecimals: number;
|
|
5
|
+
maxLeverage?: number;
|
|
6
|
+
isDelisted?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class HyperliquidInfoCache {
|
|
9
|
+
private readonly fetchMeta;
|
|
10
|
+
private rules;
|
|
11
|
+
private loadedAt;
|
|
12
|
+
constructor(fetchMeta: () => Promise<{
|
|
13
|
+
universe?: MetaUniverseEntry[];
|
|
14
|
+
} | null>);
|
|
15
|
+
/** Load `meta.universe` → per-coin rules. Returns false on failure and KEEPS the
|
|
16
|
+
* previous rules (a transient failure must not zero out the cache and start
|
|
17
|
+
* rejecting every order). */
|
|
18
|
+
load(): Promise<boolean>;
|
|
19
|
+
get isStale(): boolean;
|
|
20
|
+
get size(): number;
|
|
21
|
+
/** Rules for a canonical symbol (BTC/USDC) or a bare coin (BTC). Coin names are
|
|
22
|
+
* CASE-SENSITIVE on HL (kPEPE) — never uppercase the lookup key. */
|
|
23
|
+
getRules(symbolOrCoin: string): HlAssetRules | null;
|
|
24
|
+
/** Same contract as ExchangeInfoCache.validate — round, then check, and return
|
|
25
|
+
* the rounded values the caller must actually submit. */
|
|
26
|
+
validate(args: {
|
|
27
|
+
symbol: string;
|
|
28
|
+
amount: number;
|
|
29
|
+
side: 'buy' | 'sell';
|
|
30
|
+
price?: number;
|
|
31
|
+
referencePrice?: number;
|
|
32
|
+
}): HlValidationResult;
|
|
33
|
+
/** Round a price for a symbol (direction explicit — a protective leg never
|
|
34
|
+
* rounds "nearest"). Throws when the asset is unknown: a stop we cannot round
|
|
35
|
+
* correctly must NOT be silently submitted at an unrounded price. */
|
|
36
|
+
roundPrice(symbol: string, price: number, dir: RoundDirection): number;
|
|
37
|
+
roundSize(symbol: string, size: number): number;
|
|
38
|
+
/** The IOC bound that emulates a market order. */
|
|
39
|
+
marketPrice(args: {
|
|
40
|
+
symbol: string;
|
|
41
|
+
side: 'buy' | 'sell';
|
|
42
|
+
referencePrice: number;
|
|
43
|
+
slippagePct: number;
|
|
44
|
+
}): number;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Hyperliquid asset-rules cache — the HL analog of `live/exchange-info-cache.ts`.
|
|
2
|
+
//
|
|
3
|
+
// Holds `szDecimals` + `assetIndex` + `maxLeverage` per coin, refreshed from
|
|
4
|
+
// `meta` (weight 20) at boot and daily thereafter, and exposes the SAME
|
|
5
|
+
// `validate()` contract the Binance cache exposes so the two adapters' call sites
|
|
6
|
+
// stay symmetric (the shared contract suite depends on that symmetry).
|
|
7
|
+
//
|
|
8
|
+
// ★ NEVER hardcode an assetIndex. Indices are positional in `meta.universe` and
|
|
9
|
+
// DIFFER between mainnet and testnet ([asset-ids]). A hardcoded index is an
|
|
10
|
+
// order on the wrong coin.
|
|
11
|
+
// ★ The rounding rule itself lives in `hl-precision.ts` — this module only
|
|
12
|
+
// supplies the per-asset inputs and caches them.
|
|
13
|
+
import { logger } from '../../logger.js';
|
|
14
|
+
import { toHyperliquidCoin } from '../symbols.js';
|
|
15
|
+
import { validateHlOrder, roundHlPrice, roundHlSize, marketIocPrice, } from './hl-precision.js';
|
|
16
|
+
const TAG = 'hl-info-cache';
|
|
17
|
+
/** Refresh cadence — HL adds/removes perps and can change szDecimals on a
|
|
18
|
+
* listing; daily matches the Binance exchangeInfo refresh. */
|
|
19
|
+
const REFRESH_MS = 24 * 60 * 60 * 1000;
|
|
20
|
+
export class HyperliquidInfoCache {
|
|
21
|
+
fetchMeta;
|
|
22
|
+
rules = new Map(); // keyed by COIN (case-sensitive)
|
|
23
|
+
loadedAt = 0;
|
|
24
|
+
constructor(fetchMeta) {
|
|
25
|
+
this.fetchMeta = fetchMeta;
|
|
26
|
+
}
|
|
27
|
+
/** Load `meta.universe` → per-coin rules. Returns false on failure and KEEPS the
|
|
28
|
+
* previous rules (a transient failure must not zero out the cache and start
|
|
29
|
+
* rejecting every order). */
|
|
30
|
+
async load() {
|
|
31
|
+
try {
|
|
32
|
+
const meta = await this.fetchMeta();
|
|
33
|
+
const universe = meta?.universe;
|
|
34
|
+
if (!Array.isArray(universe) || universe.length === 0) {
|
|
35
|
+
logger.error(TAG, 'meta.universe empty or unreadable — keeping previous rules');
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const next = new Map();
|
|
39
|
+
universe.forEach((entry, index) => {
|
|
40
|
+
if (!entry?.name || typeof entry.szDecimals !== 'number')
|
|
41
|
+
return;
|
|
42
|
+
if (entry.isDelisted)
|
|
43
|
+
return; // never trade a delisted asset
|
|
44
|
+
next.set(entry.name, {
|
|
45
|
+
szDecimals: entry.szDecimals,
|
|
46
|
+
assetIndex: index, // ★ positional — this is the ONLY correct source
|
|
47
|
+
maxLeverage: entry.maxLeverage,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
if (next.size === 0) {
|
|
51
|
+
logger.error(TAG, 'meta.universe parsed to zero assets — keeping previous rules');
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
this.rules = next;
|
|
55
|
+
this.loadedAt = Date.now();
|
|
56
|
+
logger.info(TAG, `Loaded HL asset rules: ${next.size} coins`);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
logger.error(TAG, `load failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
get isStale() {
|
|
65
|
+
return this.loadedAt === 0 || Date.now() - this.loadedAt > REFRESH_MS;
|
|
66
|
+
}
|
|
67
|
+
get size() {
|
|
68
|
+
return this.rules.size;
|
|
69
|
+
}
|
|
70
|
+
/** Rules for a canonical symbol (BTC/USDC) or a bare coin (BTC). Coin names are
|
|
71
|
+
* CASE-SENSITIVE on HL (kPEPE) — never uppercase the lookup key. */
|
|
72
|
+
getRules(symbolOrCoin) {
|
|
73
|
+
const coin = symbolOrCoin.includes('/')
|
|
74
|
+
? toHyperliquidCoin(symbolOrCoin)
|
|
75
|
+
: symbolOrCoin;
|
|
76
|
+
return this.rules.get(coin) ?? null;
|
|
77
|
+
}
|
|
78
|
+
/** Same contract as ExchangeInfoCache.validate — round, then check, and return
|
|
79
|
+
* the rounded values the caller must actually submit. */
|
|
80
|
+
validate(args) {
|
|
81
|
+
const rules = this.getRules(args.symbol);
|
|
82
|
+
if (!rules) {
|
|
83
|
+
return {
|
|
84
|
+
valid: false,
|
|
85
|
+
error: `Unknown Hyperliquid asset for ${args.symbol} — not in meta.universe ` +
|
|
86
|
+
'(delisted, wrong network, or coin-name case mismatch).',
|
|
87
|
+
roundedAmount: 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return validateHlOrder({
|
|
91
|
+
amount: args.amount,
|
|
92
|
+
price: args.price,
|
|
93
|
+
referencePrice: args.referencePrice,
|
|
94
|
+
rules,
|
|
95
|
+
side: args.side,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** Round a price for a symbol (direction explicit — a protective leg never
|
|
99
|
+
* rounds "nearest"). Throws when the asset is unknown: a stop we cannot round
|
|
100
|
+
* correctly must NOT be silently submitted at an unrounded price. */
|
|
101
|
+
roundPrice(symbol, price, dir) {
|
|
102
|
+
const rules = this.getRules(symbol);
|
|
103
|
+
if (!rules)
|
|
104
|
+
throw new Error(`Cannot round price for unknown HL asset ${symbol}`);
|
|
105
|
+
return roundHlPrice(price, rules.szDecimals, dir);
|
|
106
|
+
}
|
|
107
|
+
roundSize(symbol, size) {
|
|
108
|
+
const rules = this.getRules(symbol);
|
|
109
|
+
if (!rules)
|
|
110
|
+
throw new Error(`Cannot round size for unknown HL asset ${symbol}`);
|
|
111
|
+
return roundHlSize(size, rules.szDecimals);
|
|
112
|
+
}
|
|
113
|
+
/** The IOC bound that emulates a market order. */
|
|
114
|
+
marketPrice(args) {
|
|
115
|
+
const rules = this.getRules(args.symbol);
|
|
116
|
+
if (!rules)
|
|
117
|
+
throw new Error(`Cannot price market order for unknown HL asset ${args.symbol}`);
|
|
118
|
+
return marketIocPrice({
|
|
119
|
+
referencePrice: args.referencePrice,
|
|
120
|
+
side: args.side,
|
|
121
|
+
slippagePct: args.slippagePct,
|
|
122
|
+
szDecimals: rules.szDecimals,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { IExchangeAdapter, AdapterReadiness, OrderOptions } from '../../exchange-adapter.js';
|
|
3
|
+
import type { CcxtOrder, CcxtBalance, CcxtPosition, TradingMode } from '../../types.js';
|
|
4
|
+
import type { PositionMetadata, CloseReason } from '../../simulator/types.js';
|
|
5
|
+
import { type HlCredentials } from './hl-private.js';
|
|
6
|
+
import type { BracketId } from '../../live/bracket-types.js';
|
|
7
|
+
export interface HlLiveAdapterOptions {
|
|
8
|
+
credentials: HlCredentials;
|
|
9
|
+
mode: TradingMode;
|
|
10
|
+
marketSlippagePct?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class HyperliquidLiveAdapter extends EventEmitter implements IExchangeAdapter {
|
|
13
|
+
private readonly opts;
|
|
14
|
+
private readonly api;
|
|
15
|
+
private readonly publicApi;
|
|
16
|
+
private readonly infoCache;
|
|
17
|
+
private readonly slippagePct;
|
|
18
|
+
private _readiness;
|
|
19
|
+
private openOrdersUnavailableUntil;
|
|
20
|
+
constructor(opts: HlLiveAdapterOptions);
|
|
21
|
+
/** Boot: load markets + asset rules. Readiness gates `create_order` ONLY —
|
|
22
|
+
* emergency paths never consult it. */
|
|
23
|
+
initialize(): Promise<void>;
|
|
24
|
+
get readiness(): AdapterReadiness;
|
|
25
|
+
get mode(): TradingMode;
|
|
26
|
+
get isLive(): boolean;
|
|
27
|
+
createOrder(symbol: string, side: 'buy' | 'sell', type: 'market' | 'limit', amount: number, price?: number, _metadata?: PositionMetadata, options?: OrderOptions): Promise<CcxtOrder>;
|
|
28
|
+
/** Emergency-safe: never pre-gated, never readiness-gated. */
|
|
29
|
+
cancelOrder(orderId: string, symbol?: string): Promise<CcxtOrder>;
|
|
30
|
+
/**
|
|
31
|
+
* Cancel WORKING orders — protective legs are PRESERVED.
|
|
32
|
+
*
|
|
33
|
+
* This is the plugin-side half of the kill-preserve rule (the skill has its own,
|
|
34
|
+
* broader layer). A Kill must never strip the stop off a live position.
|
|
35
|
+
*/
|
|
36
|
+
cancelAllOrders(symbol?: string): Promise<CcxtOrder[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Close a position at market (reduce-only IOC).
|
|
39
|
+
*
|
|
40
|
+
* ★ null ≠ empty: a FAILED position fetch throws (state unknown — closing
|
|
41
|
+
* against an unknown book could open a NEW position in the opposite direction);
|
|
42
|
+
* a CONFIRMED-flat account is a benign no-op.
|
|
43
|
+
*/
|
|
44
|
+
closePosition(symbol: string, _closeReason?: CloseReason): Promise<CcxtOrder>;
|
|
45
|
+
getBalance(): Promise<CcxtBalance>;
|
|
46
|
+
/** Display contract (`?? []`) — the 20+ KPI/display callers. */
|
|
47
|
+
getPositions(symbol?: string): Promise<CcxtPosition[]>;
|
|
48
|
+
/** ★ Decision contract — null means UNKNOWN, and destructive paths must not act. */
|
|
49
|
+
getPositionsOrNull(symbol?: string): Promise<CcxtPosition[] | null>;
|
|
50
|
+
/**
|
|
51
|
+
* ★ THROWS on a failed fetch — never returns [].
|
|
52
|
+
*
|
|
53
|
+
* An empty list is positive information ("the exchange has no orders"); a failed
|
|
54
|
+
* fetch is not. Collapsing the two is what drove the ATOM naked-bracket loop on
|
|
55
|
+
* Binance. A sticky cooldown then prevents a retry loop from pinning the budget.
|
|
56
|
+
*/
|
|
57
|
+
getOpenOrders(symbol?: string): Promise<CcxtOrder[]>;
|
|
58
|
+
fetchOrder(orderId: string, symbol?: string): Promise<CcxtOrder | null>;
|
|
59
|
+
getLastPrice(symbol: string): Promise<number | null>;
|
|
60
|
+
/** Attach protective legs to a filled position — ONE signed action for both. */
|
|
61
|
+
attachBrackets(args: {
|
|
62
|
+
bracketId: BracketId;
|
|
63
|
+
symbol: string;
|
|
64
|
+
positionSide: 'long' | 'short';
|
|
65
|
+
positionSize: number;
|
|
66
|
+
stopPrice?: number;
|
|
67
|
+
targetPrice?: number;
|
|
68
|
+
}): Promise<CcxtOrder[]>;
|
|
69
|
+
/**
|
|
70
|
+
* ★ T-2 REQUIREMENT: resize protective legs after a scale-in.
|
|
71
|
+
*
|
|
72
|
+
* HL legs are FIXED SIZE and do not follow the position (measured). Without this
|
|
73
|
+
* the added contracts are NAKED. Submit-then-cancel: a moment of over-protection
|
|
74
|
+
* (harmless — reduce-only cannot flip a position) beats a naked window.
|
|
75
|
+
*/
|
|
76
|
+
resizeBrackets(args: {
|
|
77
|
+
bracketId: BracketId;
|
|
78
|
+
symbol: string;
|
|
79
|
+
positionSide: 'long' | 'short';
|
|
80
|
+
positionSize: number;
|
|
81
|
+
}): Promise<{
|
|
82
|
+
resized: boolean;
|
|
83
|
+
}>;
|
|
84
|
+
/** Coverage audit — `covered:false` means part of the position is NAKED. */
|
|
85
|
+
auditBracketCoverage(symbol: string, positionSize: number): Promise<import("./hl-brackets.js").CoverageResult>;
|
|
86
|
+
/** Our protective legs currently on the exchange. Throws on unknown (never []). */
|
|
87
|
+
private readLiveLegs;
|
|
88
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// HyperliquidLiveAdapter — IExchangeAdapter over Hyperliquid (plan §5.1).
|
|
2
|
+
//
|
|
3
|
+
// A SIBLING of Binance's LiveAdapter, not a refactor of it: ~1800 lines of
|
|
4
|
+
// incident-hardened live-money logic stay untouched, and the invariants are held
|
|
5
|
+
// by the SHARED CONTRACT SUITE that runs against both adapters (plan §9.1).
|
|
6
|
+
//
|
|
7
|
+
// THE INHERITED CONTRACTS (each one is an incident, not a preference):
|
|
8
|
+
// ★ null ≠ empty. `getPositionsOrNull()` returns null when the fetch FAILED;
|
|
9
|
+
// `getOpenOrders()` THROWS rather than return `[]` on a failed fetch (a
|
|
10
|
+
// phantom-empty order list once drove ~17 cycles of re-attach against real
|
|
11
|
+
// protective orders — the 2026-05-14 ATOM naked-bracket loop).
|
|
12
|
+
// ★ `closePosition()` distinguishes null (unknown) from [] (confirmed flat).
|
|
13
|
+
// ★ Emergency paths (cancel/close) are NEVER gated by our own pacer or by
|
|
14
|
+
// readiness — an exit must always be attemptable.
|
|
15
|
+
// ★ A sticky cooldown after a failed order-list fetch, so a tight agent retry
|
|
16
|
+
// loop cannot pin the rate budget and blind the whole agent.
|
|
17
|
+
//
|
|
18
|
+
// THE HL-SPECIFIC RULES (all measured on testnet 2026-07-12):
|
|
19
|
+
// ★ No native market order → IOC limit at a slippage-bounded price, and ccxt
|
|
20
|
+
// REQUIRES that reference price (it throws without one).
|
|
21
|
+
// ★ Reduce-only trigger legs auto-cancel on flat and behave OCO (T-1) → no
|
|
22
|
+
// sibling-canceller needed.
|
|
23
|
+
// ★ Legs are FIXED SIZE and do NOT follow a scale-in (T-2) → `resizeBrackets()`
|
|
24
|
+
// exists and the auditor treats an under-sized leg as NAKED size.
|
|
25
|
+
// ★ Positions/balance are queried on the MASTER address; the agent wallet only
|
|
26
|
+
// signs (it holds nothing).
|
|
27
|
+
import { EventEmitter } from 'node:events';
|
|
28
|
+
import { logger } from '../../logger.js';
|
|
29
|
+
import { HyperliquidPrivateApi } from './hl-private.js';
|
|
30
|
+
import { HyperliquidInfoCache } from './hl-info-cache.js';
|
|
31
|
+
import { HyperliquidPublicApi } from './hl-public.js';
|
|
32
|
+
import { planBracket, planResize, buildBracketOrders, bracketCoversPosition, } from './hl-brackets.js';
|
|
33
|
+
import { deriveHlNav, toCcxtBalance } from './hl-balance.js';
|
|
34
|
+
import { buildHlOrderCloid, parseHlBracketCloid } from './hl-cloid.js';
|
|
35
|
+
const TAG = 'hl-live-adapter';
|
|
36
|
+
/** Default IOC slippage bound for an emulated market order. 0.5% — NOT ccxt's
|
|
37
|
+
* 5% default, which would be a silent execution-quality disaster. */
|
|
38
|
+
const DEFAULT_MARKET_SLIPPAGE = 0.005;
|
|
39
|
+
/** Sticky cooldown after a failed open-orders fetch (the Binance lesson: the
|
|
40
|
+
* SKILL.md audit→attach loop re-calls every ~3s and would pin the budget). */
|
|
41
|
+
const OPEN_ORDERS_COOLDOWN_MS = 45_000;
|
|
42
|
+
export class HyperliquidLiveAdapter extends EventEmitter {
|
|
43
|
+
opts;
|
|
44
|
+
api;
|
|
45
|
+
publicApi;
|
|
46
|
+
infoCache;
|
|
47
|
+
slippagePct;
|
|
48
|
+
_readiness = 'INIT_PENDING';
|
|
49
|
+
openOrdersUnavailableUntil = 0;
|
|
50
|
+
constructor(opts) {
|
|
51
|
+
super();
|
|
52
|
+
this.opts = opts;
|
|
53
|
+
this.api = new HyperliquidPrivateApi(opts.credentials);
|
|
54
|
+
this.publicApi = new HyperliquidPublicApi({ testnet: opts.credentials.testnet });
|
|
55
|
+
this.slippagePct = clampSlippage(opts.marketSlippagePct ?? DEFAULT_MARKET_SLIPPAGE);
|
|
56
|
+
this.infoCache = new HyperliquidInfoCache(async () => {
|
|
57
|
+
// `meta` is a keyless info read — the public client owns it.
|
|
58
|
+
return this.publicApi.fetchMeta();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// ---- Lifecycle ----
|
|
62
|
+
/** Boot: load markets + asset rules. Readiness gates `create_order` ONLY —
|
|
63
|
+
* emergency paths never consult it. */
|
|
64
|
+
async initialize() {
|
|
65
|
+
const markets = await this.api.loadMarkets();
|
|
66
|
+
const rules = await this.infoCache.load();
|
|
67
|
+
if (markets && rules && this.infoCache.size > 0) {
|
|
68
|
+
this._readiness = 'READY';
|
|
69
|
+
logger.info(TAG, `HL live adapter READY (${this.infoCache.size} assets, mode=${this.opts.mode})`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// DEGRADED, not BLOCKED: reads/exits still work; entries are refused
|
|
73
|
+
// upstream by the readiness gate.
|
|
74
|
+
this._readiness = 'DEGRADED';
|
|
75
|
+
logger.error(TAG, 'HL live adapter DEGRADED — markets/asset rules unavailable; entries blocked, exits still work');
|
|
76
|
+
}
|
|
77
|
+
// Prime the ADDRESS action budget (the starvation guard, §5.6).
|
|
78
|
+
await this.api.refreshAddressBudget();
|
|
79
|
+
}
|
|
80
|
+
get readiness() {
|
|
81
|
+
return this._readiness;
|
|
82
|
+
}
|
|
83
|
+
get mode() {
|
|
84
|
+
return this.opts.mode;
|
|
85
|
+
}
|
|
86
|
+
get isLive() {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// ---- Core trading ----
|
|
90
|
+
async createOrder(symbol, side, type, amount, price, _metadata, options) {
|
|
91
|
+
// A market order NEEDS a reference price on HL (no native market order; ccxt
|
|
92
|
+
// derives the slippage cap from it and THROWS without one — verified live).
|
|
93
|
+
let referencePrice = price;
|
|
94
|
+
if (type === 'market' || referencePrice === undefined) {
|
|
95
|
+
const mark = await this.publicApi.fetchMarkPrice(symbol);
|
|
96
|
+
if (mark === null || !(mark > 0)) {
|
|
97
|
+
throw new Error(`Cannot price a ${type} order on ${symbol}: mark price unavailable. ` +
|
|
98
|
+
'Hyperliquid has no native market order — an IOC bound needs a reference price.');
|
|
99
|
+
}
|
|
100
|
+
referencePrice = mark;
|
|
101
|
+
}
|
|
102
|
+
const submitPrice = type === 'market'
|
|
103
|
+
? this.infoCache.marketPrice({
|
|
104
|
+
symbol,
|
|
105
|
+
side,
|
|
106
|
+
referencePrice,
|
|
107
|
+
slippagePct: this.slippagePct,
|
|
108
|
+
})
|
|
109
|
+
: this.infoCache.roundPrice(symbol, referencePrice, side === 'buy' ? 'down' : 'up');
|
|
110
|
+
const check = this.infoCache.validate({
|
|
111
|
+
symbol,
|
|
112
|
+
amount,
|
|
113
|
+
side,
|
|
114
|
+
price: type === 'limit' ? submitPrice : undefined,
|
|
115
|
+
referencePrice,
|
|
116
|
+
});
|
|
117
|
+
if (!check.valid)
|
|
118
|
+
throw new Error(check.error ?? 'Hyperliquid order validation failed');
|
|
119
|
+
const cloid = options?.clientOrderId ?? buildHlOrderCloid();
|
|
120
|
+
const order = await this.api.submitOrder({
|
|
121
|
+
symbol,
|
|
122
|
+
side,
|
|
123
|
+
type,
|
|
124
|
+
amount: check.roundedAmount,
|
|
125
|
+
price: submitPrice,
|
|
126
|
+
reduceOnly: options?.reduceOnly,
|
|
127
|
+
cloid,
|
|
128
|
+
});
|
|
129
|
+
if (!order)
|
|
130
|
+
throw new Error(`Hyperliquid order submission returned no order for ${symbol}`);
|
|
131
|
+
return order;
|
|
132
|
+
}
|
|
133
|
+
/** Emergency-safe: never pre-gated, never readiness-gated. */
|
|
134
|
+
async cancelOrder(orderId, symbol) {
|
|
135
|
+
if (!symbol)
|
|
136
|
+
throw new Error('Hyperliquid cancelOrder requires a symbol');
|
|
137
|
+
const res = await this.api.cancelOrder(orderId, symbol);
|
|
138
|
+
if (!res)
|
|
139
|
+
throw new Error(`Cancel returned no result for ${orderId}`);
|
|
140
|
+
return res;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Cancel WORKING orders — protective legs are PRESERVED.
|
|
144
|
+
*
|
|
145
|
+
* This is the plugin-side half of the kill-preserve rule (the skill has its own,
|
|
146
|
+
* broader layer). A Kill must never strip the stop off a live position.
|
|
147
|
+
*/
|
|
148
|
+
async cancelAllOrders(symbol) {
|
|
149
|
+
const open = await this.api.fetchOpenOrders(symbol);
|
|
150
|
+
if (open === null) {
|
|
151
|
+
// Unknown ≠ nothing. Refuse to "cancel all" against an unknown book.
|
|
152
|
+
throw new Error('cancelAllOrders: could not read open orders (fetch failed) — refusing to act on an unknown order book');
|
|
153
|
+
}
|
|
154
|
+
const cancelled = [];
|
|
155
|
+
for (const o of open) {
|
|
156
|
+
const cloid = (o.clientOrderId ?? '');
|
|
157
|
+
// Preserve OUR bracket legs…
|
|
158
|
+
if (cloid && parseHlBracketCloid(cloid))
|
|
159
|
+
continue;
|
|
160
|
+
// …and any other protective order (externally placed stops included).
|
|
161
|
+
const raw = o.info ?? {};
|
|
162
|
+
if (raw.reduceOnly === true ||
|
|
163
|
+
raw.isTrigger === true ||
|
|
164
|
+
raw.isPositionTpsl === true) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const res = await this.api.cancelOrder(String(o.id), o.symbol ?? symbol ?? '');
|
|
169
|
+
if (res)
|
|
170
|
+
cancelled.push(res);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
logger.warn(TAG, `cancelAllOrders: cancel ${o.id} failed: ${msg(err)}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return cancelled;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Close a position at market (reduce-only IOC).
|
|
180
|
+
*
|
|
181
|
+
* ★ null ≠ empty: a FAILED position fetch throws (state unknown — closing
|
|
182
|
+
* against an unknown book could open a NEW position in the opposite direction);
|
|
183
|
+
* a CONFIRMED-flat account is a benign no-op.
|
|
184
|
+
*/
|
|
185
|
+
async closePosition(symbol, _closeReason) {
|
|
186
|
+
const positions = await this.api.fetchPositions(symbol);
|
|
187
|
+
if (positions === null) {
|
|
188
|
+
throw new Error(`closePosition(${symbol}): position state UNKNOWN (fetch failed) — refusing to submit a ` +
|
|
189
|
+
'close that could open an opposite position. Retry once the exchange responds.');
|
|
190
|
+
}
|
|
191
|
+
const pos = positions.find((p) => p.symbol?.startsWith(symbol.split(':')[0]));
|
|
192
|
+
if (!pos || Math.abs(Number(pos.contracts ?? 0)) === 0) {
|
|
193
|
+
throw new Error(`closePosition(${symbol}): no open position (exchange confirmed flat)`);
|
|
194
|
+
}
|
|
195
|
+
const size = Math.abs(Number(pos.contracts));
|
|
196
|
+
const side = pos.side === 'long' ? 'sell' : 'buy';
|
|
197
|
+
const mark = await this.publicApi.fetchMarkPrice(symbol);
|
|
198
|
+
if (mark === null || !(mark > 0)) {
|
|
199
|
+
throw new Error(`closePosition(${symbol}): mark price unavailable — cannot bound the IOC close`);
|
|
200
|
+
}
|
|
201
|
+
const price = this.infoCache.marketPrice({
|
|
202
|
+
symbol,
|
|
203
|
+
side,
|
|
204
|
+
referencePrice: mark,
|
|
205
|
+
// A CLOSE is not the place to be precious about slippage — use a wider
|
|
206
|
+
// bound than an entry so an exit does not silently fail to fill.
|
|
207
|
+
slippagePct: Math.min(this.slippagePct * 2, 0.02),
|
|
208
|
+
});
|
|
209
|
+
const order = await this.api.submitOrder({
|
|
210
|
+
symbol,
|
|
211
|
+
side,
|
|
212
|
+
type: 'market',
|
|
213
|
+
amount: this.infoCache.roundSize(symbol, size),
|
|
214
|
+
price,
|
|
215
|
+
reduceOnly: true,
|
|
216
|
+
cloid: buildHlOrderCloid(),
|
|
217
|
+
});
|
|
218
|
+
if (!order)
|
|
219
|
+
throw new Error(`closePosition(${symbol}) returned no order`);
|
|
220
|
+
return order;
|
|
221
|
+
}
|
|
222
|
+
// ---- State queries ----
|
|
223
|
+
async getBalance() {
|
|
224
|
+
const state = await this.api.rawInfo({
|
|
225
|
+
type: 'clearinghouseState',
|
|
226
|
+
user: this.opts.credentials.walletAddress,
|
|
227
|
+
}).catch(() => null);
|
|
228
|
+
const nav = deriveHlNav(state);
|
|
229
|
+
if (!nav) {
|
|
230
|
+
throw new Error('getBalance: clearinghouseState unreadable — balance UNKNOWN (never reported as $0)');
|
|
231
|
+
}
|
|
232
|
+
return toCcxtBalance(nav);
|
|
233
|
+
}
|
|
234
|
+
/** Display contract (`?? []`) — the 20+ KPI/display callers. */
|
|
235
|
+
async getPositions(symbol) {
|
|
236
|
+
return (await this.api.fetchPositions(symbol)) ?? [];
|
|
237
|
+
}
|
|
238
|
+
/** ★ Decision contract — null means UNKNOWN, and destructive paths must not act. */
|
|
239
|
+
async getPositionsOrNull(symbol) {
|
|
240
|
+
return this.api.fetchPositions(symbol);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* ★ THROWS on a failed fetch — never returns [].
|
|
244
|
+
*
|
|
245
|
+
* An empty list is positive information ("the exchange has no orders"); a failed
|
|
246
|
+
* fetch is not. Collapsing the two is what drove the ATOM naked-bracket loop on
|
|
247
|
+
* Binance. A sticky cooldown then prevents a retry loop from pinning the budget.
|
|
248
|
+
*/
|
|
249
|
+
async getOpenOrders(symbol) {
|
|
250
|
+
const now = Date.now();
|
|
251
|
+
if (now < this.openOrdersUnavailableUntil) {
|
|
252
|
+
const waitS = Math.ceil((this.openOrdersUnavailableUntil - now) / 1000);
|
|
253
|
+
throw new Error(`getOpenOrders: Hyperliquid unavailable — sticky cooldown active (~${waitS}s left, no call made). ` +
|
|
254
|
+
'Caller must NOT proceed as if the exchange has zero orders.');
|
|
255
|
+
}
|
|
256
|
+
const orders = await this.api.fetchOpenOrders(symbol);
|
|
257
|
+
if (orders === null) {
|
|
258
|
+
this.openOrdersUnavailableUntil = Date.now() + OPEN_ORDERS_COOLDOWN_MS;
|
|
259
|
+
throw new Error('getOpenOrders: fetch FAILED (rate limit / transient) — order state UNKNOWN. ' +
|
|
260
|
+
'This is NOT "zero orders": a caller that treats it as empty would re-attach or cancel ' +
|
|
261
|
+
'against real protective orders.');
|
|
262
|
+
}
|
|
263
|
+
this.openOrdersUnavailableUntil = 0;
|
|
264
|
+
return orders;
|
|
265
|
+
}
|
|
266
|
+
async fetchOrder(orderId, symbol) {
|
|
267
|
+
return this.api.fetchOrder(orderId, symbol);
|
|
268
|
+
}
|
|
269
|
+
async getLastPrice(symbol) {
|
|
270
|
+
return this.publicApi.fetchMarkPrice(symbol);
|
|
271
|
+
}
|
|
272
|
+
// ---- Brackets (the safety floor) ----
|
|
273
|
+
/** Attach protective legs to a filled position — ONE signed action for both. */
|
|
274
|
+
async attachBrackets(args) {
|
|
275
|
+
const plan = planBracket({
|
|
276
|
+
...args,
|
|
277
|
+
positionSize: this.infoCache.roundSize(args.symbol, args.positionSize),
|
|
278
|
+
// Round each trigger to the PROTECTIVE side: a long's stop rounds DOWN
|
|
279
|
+
// (never tighter than the agent asked), its target rounds UP.
|
|
280
|
+
stopPrice: args.stopPrice === undefined
|
|
281
|
+
? undefined
|
|
282
|
+
: this.infoCache.roundPrice(args.symbol, args.stopPrice, args.positionSide === 'long' ? 'down' : 'up'),
|
|
283
|
+
targetPrice: args.targetPrice === undefined
|
|
284
|
+
? undefined
|
|
285
|
+
: this.infoCache.roundPrice(args.symbol, args.targetPrice, args.positionSide === 'long' ? 'up' : 'down'),
|
|
286
|
+
});
|
|
287
|
+
const orders = await this.api.submitOrders(buildBracketOrders(plan));
|
|
288
|
+
if (!orders)
|
|
289
|
+
throw new Error(`attachBrackets(${args.symbol}): submission returned nothing`);
|
|
290
|
+
return orders;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* ★ T-2 REQUIREMENT: resize protective legs after a scale-in.
|
|
294
|
+
*
|
|
295
|
+
* HL legs are FIXED SIZE and do not follow the position (measured). Without this
|
|
296
|
+
* the added contracts are NAKED. Submit-then-cancel: a moment of over-protection
|
|
297
|
+
* (harmless — reduce-only cannot flip a position) beats a naked window.
|
|
298
|
+
*/
|
|
299
|
+
async resizeBrackets(args) {
|
|
300
|
+
const live = await this.readLiveLegs(args.symbol);
|
|
301
|
+
const plan = planResize({
|
|
302
|
+
...args,
|
|
303
|
+
positionSize: this.infoCache.roundSize(args.symbol, args.positionSize),
|
|
304
|
+
liveLegs: live,
|
|
305
|
+
});
|
|
306
|
+
if (plan.noop)
|
|
307
|
+
return { resized: false };
|
|
308
|
+
await this.api.submitOrders(plan.submit); // protect FIRST
|
|
309
|
+
for (const cloid of plan.cancelCloids) {
|
|
310
|
+
try {
|
|
311
|
+
await this.api.cancelOrderByCloid(cloid, args.symbol);
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
// A stale leg that refuses to cancel is over-protection, not risk.
|
|
315
|
+
logger.warn(TAG, `resizeBrackets: stale leg ${cloid} cancel failed: ${msg(err)}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
logger.info(TAG, `resized brackets on ${args.symbol} to ${args.positionSize} (T-2: HL legs do not auto-resize)`);
|
|
319
|
+
return { resized: true };
|
|
320
|
+
}
|
|
321
|
+
/** Coverage audit — `covered:false` means part of the position is NAKED. */
|
|
322
|
+
async auditBracketCoverage(symbol, positionSize) {
|
|
323
|
+
const live = await this.readLiveLegs(symbol);
|
|
324
|
+
return bracketCoversPosition({ positionSize, liveLegs: live });
|
|
325
|
+
}
|
|
326
|
+
/** Our protective legs currently on the exchange. Throws on unknown (never []). */
|
|
327
|
+
async readLiveLegs(symbol) {
|
|
328
|
+
const orders = await this.getOpenOrders(symbol); // throws on failed fetch
|
|
329
|
+
const legs = [];
|
|
330
|
+
for (const o of orders) {
|
|
331
|
+
const cloid = (o.clientOrderId ?? '');
|
|
332
|
+
const parsed = cloid ? parseHlBracketCloid(cloid) : null;
|
|
333
|
+
if (!parsed || parsed.role === 'entry')
|
|
334
|
+
continue;
|
|
335
|
+
const raw = o.info ?? {};
|
|
336
|
+
legs.push({
|
|
337
|
+
role: parsed.role,
|
|
338
|
+
cloid,
|
|
339
|
+
size: Number(o.remaining ?? o.amount ?? raw.sz ?? 0),
|
|
340
|
+
triggerPrice: Number(raw.triggerPx ?? o.price ?? 0),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return legs;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function clampSlippage(pct) {
|
|
347
|
+
if (!Number.isFinite(pct) || pct <= 0)
|
|
348
|
+
return DEFAULT_MARKET_SLIPPAGE;
|
|
349
|
+
return Math.min(pct, 0.02); // hard ceiling 2%
|
|
350
|
+
}
|
|
351
|
+
function msg(err) {
|
|
352
|
+
return err instanceof Error ? err.message : String(err);
|
|
353
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** HL minimum order value in USDC. Observed live 2026-07-12 (a $6 order was
|
|
2
|
+
* rejected: "Order must have minimum value of $10"). */
|
|
3
|
+
export declare const HL_MIN_NOTIONAL_USD = 10;
|
|
4
|
+
export type RoundDirection = 'down' | 'up';
|
|
5
|
+
/** Count significant figures of a decimal string (leading zeros are not
|
|
6
|
+
* significant; trailing zeros AFTER a decimal point are, but we always emit
|
|
7
|
+
* trimmed strings so they never appear). */
|
|
8
|
+
export declare function countSignificantFigures(value: number): number;
|
|
9
|
+
/** Decimal places of a value (0 for integers). */
|
|
10
|
+
export declare function decimalPlaces(value: number): number;
|
|
11
|
+
export interface HlAssetRules {
|
|
12
|
+
/** Size decimals from `meta.universe[].szDecimals`. */
|
|
13
|
+
szDecimals: number;
|
|
14
|
+
/** Asset index — the `a` field of an order. NEVER hardcode: mainnet and
|
|
15
|
+
* testnet indices differ ([asset-ids]). */
|
|
16
|
+
assetIndex: number;
|
|
17
|
+
maxLeverage?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Is this price acceptable to Hyperliquid as-is? (The exact documented rule —
|
|
20
|
+
* used by tests and as a post-round assertion, never as a substitute for
|
|
21
|
+
* rounding.) */
|
|
22
|
+
export declare function isValidHlPrice(price: number, szDecimals: number): boolean;
|
|
23
|
+
/** Round a price to the nearest HL-legal value in the requested direction.
|
|
24
|
+
*
|
|
25
|
+
* Applies BOTH constraints (≤5 sig figs AND ≤ MAX_DECIMALS−szDecimals decimals),
|
|
26
|
+
* tightest-wins. An integer result is always legal, so the decimal cap can never
|
|
27
|
+
* push a price to an illegal value. */
|
|
28
|
+
export declare function roundHlPrice(price: number, szDecimals: number, dir: RoundDirection): number;
|
|
29
|
+
/** Round an order size DOWN to szDecimals. Always DOWN: rounding a size up can
|
|
30
|
+
* overshoot the position (a reduce-only leg sized above the position is rejected)
|
|
31
|
+
* or exceed the intended risk. */
|
|
32
|
+
export declare function roundHlSize(size: number, szDecimals: number): number;
|
|
33
|
+
export interface HlValidationResult {
|
|
34
|
+
valid: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
roundedAmount: number;
|
|
37
|
+
roundedPrice?: number;
|
|
38
|
+
}
|
|
39
|
+
/** Pre-flight validation mirroring `ExchangeInfoCache.validate`'s contract
|
|
40
|
+
* (same shape, same "round then check" order, same dollar-hint error copy) so
|
|
41
|
+
* the adapter's call sites are venue-symmetric.
|
|
42
|
+
*
|
|
43
|
+
* @param side drives passive rounding of a limit price (buy → down, sell → up).
|
|
44
|
+
* @param referencePrice mark/mid — used for the notional check when no limit
|
|
45
|
+
* price is given (market orders). */
|
|
46
|
+
export declare function validateHlOrder(args: {
|
|
47
|
+
amount: number;
|
|
48
|
+
price?: number;
|
|
49
|
+
referencePrice?: number;
|
|
50
|
+
rules: HlAssetRules;
|
|
51
|
+
side: 'buy' | 'sell';
|
|
52
|
+
}): HlValidationResult;
|
|
53
|
+
/** The IOC price bound that emulates a market order (HL has no native market
|
|
54
|
+
* order — plan §3.4). Explicit slippage ALWAYS — ccxt's `defaultSlippage: 0.05`
|
|
55
|
+
* (5%!) is never relied upon. */
|
|
56
|
+
export declare function marketIocPrice(args: {
|
|
57
|
+
referencePrice: number;
|
|
58
|
+
side: 'buy' | 'sell';
|
|
59
|
+
slippagePct: number;
|
|
60
|
+
szDecimals: number;
|
|
61
|
+
}): number;
|