openbroker 1.9.6 → 1.10.0
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/CHANGELOG.md +19 -0
- package/SKILL.md +13 -4
- package/dist/core/client.d.ts +60 -6
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +190 -26
- package/dist/core/utils.d.ts +29 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +27 -0
- package/dist/lib.d.ts +2 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -1
- package/dist/operations/advanced-orders.test.js +302 -13
- package/dist/operations/bracket.d.ts +35 -5
- package/dist/operations/bracket.d.ts.map +1 -1
- package/dist/operations/bracket.js +238 -64
- package/dist/operations/chase.d.ts +1 -1
- package/dist/operations/chase.d.ts.map +1 -1
- package/dist/operations/chase.js +58 -23
- package/dist/operations/scale.d.ts.map +1 -1
- package/dist/operations/scale.js +10 -1
- package/dist/operations/set-tpsl.js +69 -57
- package/dist/operations/trigger-order.js +18 -6
- package/dist/operations/twap.js +13 -1
- package/package.json +1 -1
- package/scripts/core/client.ts +249 -30
- package/scripts/core/utils.ts +37 -0
- package/scripts/lib.ts +3 -0
- package/scripts/operations/advanced-orders.test.ts +347 -14
- package/scripts/operations/bracket.ts +252 -73
- package/scripts/operations/chase.ts +57 -24
- package/scripts/operations/scale.ts +10 -1
- package/scripts/operations/set-tpsl.ts +62 -57
- package/scripts/operations/trigger-order.ts +20 -6
- package/scripts/operations/twap.ts +14 -1
package/scripts/core/client.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
OutcomeQuestion,
|
|
20
20
|
} from './types.js';
|
|
21
21
|
import { loadConfig, isMainnet } from './config.js';
|
|
22
|
-
import { roundPrice, roundSize } from './utils.js';
|
|
22
|
+
import { MIN_ORDER_NOTIONAL_USD, roundPrice, roundSize } from './utils.js';
|
|
23
23
|
|
|
24
24
|
export interface RealtimeBookSnapshot {
|
|
25
25
|
coin: string;
|
|
@@ -2596,6 +2596,8 @@ export class HyperliquidClient {
|
|
|
2596
2596
|
* @param limitPrice - Limit price for the order (use triggerPrice for market-like execution)
|
|
2597
2597
|
* @param tpsl - 'tp' for take profit, 'sl' for stop loss
|
|
2598
2598
|
* @param reduceOnly - Whether order is reduce-only (should be true for TP/SL)
|
|
2599
|
+
* @param isMarket - Execute as a market trigger on fire; limitPrice then only
|
|
2600
|
+
* caps the fill (slippage band) instead of resting on the book
|
|
2599
2601
|
*/
|
|
2600
2602
|
async triggerOrder(
|
|
2601
2603
|
coin: string,
|
|
@@ -2605,7 +2607,8 @@ export class HyperliquidClient {
|
|
|
2605
2607
|
limitPrice: number,
|
|
2606
2608
|
tpsl: 'tp' | 'sl',
|
|
2607
2609
|
reduceOnly: boolean = true,
|
|
2608
|
-
leverage?: number
|
|
2610
|
+
leverage?: number,
|
|
2611
|
+
isMarket: boolean = false
|
|
2609
2612
|
): Promise<OrderResponse> {
|
|
2610
2613
|
await this.requireTrading();
|
|
2611
2614
|
await this.getMetaAndAssetCtxs();
|
|
@@ -2622,9 +2625,8 @@ export class HyperliquidClient {
|
|
|
2622
2625
|
const assetIndex = this.getAssetIndex(coin);
|
|
2623
2626
|
const szDecimals = this.getSzDecimals(coin);
|
|
2624
2627
|
|
|
2625
|
-
//
|
|
2626
|
-
// isMarket: false
|
|
2627
|
-
// For stop loss, we typically want some slippage protection
|
|
2628
|
+
// isMarket: true fires as a market order capped by limitPrice (slippage band);
|
|
2629
|
+
// isMarket: false rests as a limit order at limitPrice once triggered.
|
|
2628
2630
|
const orderWire = {
|
|
2629
2631
|
a: assetIndex,
|
|
2630
2632
|
b: isBuy,
|
|
@@ -2634,7 +2636,7 @@ export class HyperliquidClient {
|
|
|
2634
2636
|
t: {
|
|
2635
2637
|
trigger: {
|
|
2636
2638
|
triggerPx: roundPrice(triggerPrice, szDecimals),
|
|
2637
|
-
isMarket
|
|
2639
|
+
isMarket,
|
|
2638
2640
|
tpsl,
|
|
2639
2641
|
},
|
|
2640
2642
|
},
|
|
@@ -2671,23 +2673,30 @@ export class HyperliquidClient {
|
|
|
2671
2673
|
}
|
|
2672
2674
|
|
|
2673
2675
|
/**
|
|
2674
|
-
* Place a stop loss order
|
|
2676
|
+
* Place a stop loss order.
|
|
2677
|
+
*
|
|
2678
|
+
* Executes as a market trigger by default: in a gap move that jumps past the
|
|
2679
|
+
* trigger, a stop-limit's band can be skipped entirely and the position sits
|
|
2680
|
+
* unprotected — the exact scenario an SL exists for. The limit price still
|
|
2681
|
+
* caps the fill at `slippageBps` past the trigger. Pass `isMarket: false`
|
|
2682
|
+
* for the stop-limit variant (rests at the band price once triggered).
|
|
2675
2683
|
*/
|
|
2676
2684
|
async stopLoss(
|
|
2677
2685
|
coin: string,
|
|
2678
2686
|
isBuy: boolean,
|
|
2679
2687
|
size: number,
|
|
2680
2688
|
triggerPrice: number,
|
|
2681
|
-
slippageBps: number = 100 // 1% slippage for SL execution
|
|
2689
|
+
slippageBps: number = 100, // 1% slippage cap for SL execution
|
|
2690
|
+
isMarket: boolean = true
|
|
2682
2691
|
): Promise<OrderResponse> {
|
|
2683
|
-
//
|
|
2692
|
+
// Limit price sits worse than trigger to ensure fill
|
|
2684
2693
|
// Buy SL: limit above trigger, Sell SL: limit below trigger
|
|
2685
2694
|
const slippageMult = slippageBps / 10000;
|
|
2686
2695
|
const limitPrice = isBuy
|
|
2687
2696
|
? triggerPrice * (1 + slippageMult)
|
|
2688
2697
|
: triggerPrice * (1 - slippageMult);
|
|
2689
2698
|
|
|
2690
|
-
return this.triggerOrder(coin, isBuy, size, triggerPrice, limitPrice, 'sl', true);
|
|
2699
|
+
return this.triggerOrder(coin, isBuy, size, triggerPrice, limitPrice, 'sl', true, undefined, isMarket);
|
|
2691
2700
|
}
|
|
2692
2701
|
|
|
2693
2702
|
/**
|
|
@@ -2704,19 +2713,42 @@ export class HyperliquidClient {
|
|
|
2704
2713
|
}
|
|
2705
2714
|
|
|
2706
2715
|
/**
|
|
2707
|
-
* Place
|
|
2708
|
-
*
|
|
2709
|
-
*
|
|
2716
|
+
* Place TP and/or SL triggers for an open position in one batch.
|
|
2717
|
+
*
|
|
2718
|
+
* Defaults to Hyperliquid's `positionTpsl` grouping: the triggers are tied
|
|
2719
|
+
* to the open position (cancelled when it closes, OCO between themselves)
|
|
2720
|
+
* — the same mechanism the Hyperliquid frontend uses for position TP/SL.
|
|
2721
|
+
* `normalTpsl` is available for a standalone OCO pair not bound to the
|
|
2722
|
+
* position. SL executes as a market trigger by default with the limit price
|
|
2723
|
+
* capping the fill `slSlippageBps` past the trigger (see `stopLoss`).
|
|
2724
|
+
*
|
|
2725
|
+
* `isBuy` is the EXIT side (opposite of the position direction). Statuses
|
|
2726
|
+
* in the response align with the orders sent: TP first (when present),
|
|
2727
|
+
* then SL.
|
|
2710
2728
|
*/
|
|
2711
|
-
async
|
|
2729
|
+
async tpslOrders(
|
|
2712
2730
|
coin: string,
|
|
2713
2731
|
isBuy: boolean,
|
|
2714
2732
|
size: number,
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2733
|
+
opts: {
|
|
2734
|
+
takeProfitPrice?: number;
|
|
2735
|
+
stopLossPrice?: number;
|
|
2736
|
+
stopLossSlippageBps?: number;
|
|
2737
|
+
/** SL fires as a market trigger (default true); false = stop-limit. */
|
|
2738
|
+
stopLossIsMarket?: boolean;
|
|
2739
|
+
grouping?: 'positionTpsl' | 'normalTpsl';
|
|
2740
|
+
leverage?: number;
|
|
2741
|
+
} = {}
|
|
2719
2742
|
): Promise<OrderResponse> {
|
|
2743
|
+
const { takeProfitPrice, stopLossPrice, leverage } = opts;
|
|
2744
|
+
const stopLossSlippageBps = opts.stopLossSlippageBps ?? 100;
|
|
2745
|
+
const stopLossIsMarket = opts.stopLossIsMarket ?? true;
|
|
2746
|
+
const grouping = opts.grouping ?? 'positionTpsl';
|
|
2747
|
+
|
|
2748
|
+
if (takeProfitPrice === undefined && stopLossPrice === undefined) {
|
|
2749
|
+
throw new Error('tpslOrders requires takeProfitPrice, stopLossPrice, or both');
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2720
2752
|
await this.requireTrading();
|
|
2721
2753
|
await this.getMetaAndAssetCtxs();
|
|
2722
2754
|
|
|
@@ -2726,18 +2758,22 @@ export class HyperliquidClient {
|
|
|
2726
2758
|
}
|
|
2727
2759
|
|
|
2728
2760
|
const slippageMult = stopLossSlippageBps / 10000;
|
|
2729
|
-
const stopLossLimitPrice =
|
|
2730
|
-
?
|
|
2731
|
-
:
|
|
2761
|
+
const stopLossLimitPrice = stopLossPrice === undefined
|
|
2762
|
+
? undefined
|
|
2763
|
+
: isBuy
|
|
2764
|
+
? stopLossPrice * (1 + slippageMult)
|
|
2765
|
+
: stopLossPrice * (1 - slippageMult);
|
|
2732
2766
|
|
|
2733
|
-
|
|
2767
|
+
const worstPrice = Math.max(takeProfitPrice ?? 0, stopLossLimitPrice ?? 0);
|
|
2768
|
+
await this.ensureHip3Ready(coin, size * worstPrice, leverage);
|
|
2734
2769
|
|
|
2735
2770
|
const assetIndex = this.getAssetIndex(coin);
|
|
2736
2771
|
const szDecimals = this.getSzDecimals(coin);
|
|
2737
2772
|
const roundedSize = roundSize(size, szDecimals);
|
|
2738
2773
|
|
|
2739
|
-
const orderWires = [
|
|
2740
|
-
|
|
2774
|
+
const orderWires = [];
|
|
2775
|
+
if (takeProfitPrice !== undefined) {
|
|
2776
|
+
orderWires.push({
|
|
2741
2777
|
a: assetIndex,
|
|
2742
2778
|
b: isBuy,
|
|
2743
2779
|
p: roundPrice(takeProfitPrice, szDecimals),
|
|
@@ -2750,22 +2786,182 @@ export class HyperliquidClient {
|
|
|
2750
2786
|
tpsl: 'tp' as const,
|
|
2751
2787
|
},
|
|
2752
2788
|
},
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
if (stopLossPrice !== undefined) {
|
|
2792
|
+
orderWires.push({
|
|
2755
2793
|
a: assetIndex,
|
|
2756
2794
|
b: isBuy,
|
|
2757
|
-
p: roundPrice(stopLossLimitPrice
|
|
2795
|
+
p: roundPrice(stopLossLimitPrice!, szDecimals),
|
|
2758
2796
|
s: roundedSize,
|
|
2759
2797
|
r: true,
|
|
2760
2798
|
t: {
|
|
2761
2799
|
trigger: {
|
|
2762
2800
|
triggerPx: roundPrice(stopLossPrice, szDecimals),
|
|
2763
|
-
isMarket:
|
|
2801
|
+
isMarket: stopLossIsMarket,
|
|
2764
2802
|
tpsl: 'sl' as const,
|
|
2765
2803
|
},
|
|
2766
2804
|
},
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
const orderRequest: {
|
|
2809
|
+
orders: typeof orderWires;
|
|
2810
|
+
grouping: 'positionTpsl' | 'normalTpsl';
|
|
2811
|
+
builder?: BuilderInfo;
|
|
2812
|
+
} = {
|
|
2813
|
+
orders: orderWires,
|
|
2814
|
+
grouping,
|
|
2815
|
+
};
|
|
2816
|
+
|
|
2817
|
+
if (!this.isTestnet && this.config.builderAddress !== '0x0000000000000000000000000000000000000000') {
|
|
2818
|
+
orderRequest.builder = this.builderInfo;
|
|
2819
|
+
this.log('Including builder fee:', this.builderInfo);
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
try {
|
|
2823
|
+
const response = await this.exchange.order(orderRequest, this.vaultParam);
|
|
2824
|
+
this.log('TP/SL orders response:', JSON.stringify(response, null, 2));
|
|
2825
|
+
return response as unknown as OrderResponse;
|
|
2826
|
+
} catch (error) {
|
|
2827
|
+
this.log('TP/SL orders error:', error);
|
|
2828
|
+
return {
|
|
2829
|
+
status: 'err',
|
|
2830
|
+
response: error instanceof Error ? error.message : String(error),
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
/**
|
|
2836
|
+
* Place a paired TP/SL trigger set for an open position.
|
|
2837
|
+
*
|
|
2838
|
+
* Back-compat wrapper over `tpslOrders`. Since v1.10.0 this uses the
|
|
2839
|
+
* `positionTpsl` grouping (triggers track the open position and OCO-cancel)
|
|
2840
|
+
* instead of a standalone `normalTpsl` pair, and the SL fires as a market
|
|
2841
|
+
* trigger capped by the slippage band instead of a stop-limit.
|
|
2842
|
+
*/
|
|
2843
|
+
async tpslPair(
|
|
2844
|
+
coin: string,
|
|
2845
|
+
isBuy: boolean,
|
|
2846
|
+
size: number,
|
|
2847
|
+
takeProfitPrice: number,
|
|
2848
|
+
stopLossPrice: number,
|
|
2849
|
+
stopLossSlippageBps: number = 100,
|
|
2850
|
+
leverage?: number
|
|
2851
|
+
): Promise<OrderResponse> {
|
|
2852
|
+
return this.tpslOrders(coin, isBuy, size, {
|
|
2853
|
+
takeProfitPrice,
|
|
2854
|
+
stopLossPrice,
|
|
2855
|
+
stopLossSlippageBps,
|
|
2856
|
+
leverage,
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
/**
|
|
2861
|
+
* Atomic bracket: a limit entry with TP/SL children in one `normalTpsl`
|
|
2862
|
+
* batch — exactly how the Hyperliquid frontend does order-attached TP/SL.
|
|
2863
|
+
* The children arm only when the entry fills and are sized to it; their
|
|
2864
|
+
* statuses come back as the plain strings "waitingForFill" /
|
|
2865
|
+
* "waitingForTrigger" (parse with `parseOrderStatus`).
|
|
2866
|
+
*
|
|
2867
|
+
* `isBuy` is the ENTRY side; exits are placed on the opposite side,
|
|
2868
|
+
* reduce-only. Statuses align with [entry, tp?, sl?].
|
|
2869
|
+
*/
|
|
2870
|
+
async bracketOrder(
|
|
2871
|
+
coin: string,
|
|
2872
|
+
isBuy: boolean,
|
|
2873
|
+
size: number,
|
|
2874
|
+
entryPrice: number,
|
|
2875
|
+
opts: {
|
|
2876
|
+
entryTif?: 'Gtc' | 'Alo';
|
|
2877
|
+
takeProfitPrice?: number;
|
|
2878
|
+
stopLossPrice?: number;
|
|
2879
|
+
stopLossSlippageBps?: number;
|
|
2880
|
+
/** SL fires as a market trigger (default true); false = stop-limit. */
|
|
2881
|
+
stopLossIsMarket?: boolean;
|
|
2882
|
+
leverage?: number;
|
|
2883
|
+
} = {}
|
|
2884
|
+
): Promise<OrderResponse> {
|
|
2885
|
+
const { takeProfitPrice, stopLossPrice, leverage } = opts;
|
|
2886
|
+
const entryTif = opts.entryTif ?? 'Gtc';
|
|
2887
|
+
const stopLossSlippageBps = opts.stopLossSlippageBps ?? 100;
|
|
2888
|
+
const stopLossIsMarket = opts.stopLossIsMarket ?? true;
|
|
2889
|
+
|
|
2890
|
+
if (takeProfitPrice === undefined && stopLossPrice === undefined) {
|
|
2891
|
+
throw new Error('bracketOrder requires takeProfitPrice, stopLossPrice, or both');
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
await this.requireTrading();
|
|
2895
|
+
await this.getMetaAndAssetCtxs();
|
|
2896
|
+
|
|
2897
|
+
if (leverage && !this.isHip3(coin)) {
|
|
2898
|
+
this.log(`Setting leverage for ${coin} to ${leverage}x cross`);
|
|
2899
|
+
await this.updateLeverage(coin, leverage, true);
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
await this.ensureHip3Ready(coin, size * entryPrice, leverage);
|
|
2903
|
+
|
|
2904
|
+
const assetIndex = this.getAssetIndex(coin);
|
|
2905
|
+
const szDecimals = this.getSzDecimals(coin);
|
|
2906
|
+
const roundedSize = roundSize(size, szDecimals);
|
|
2907
|
+
const exitBuy = !isBuy;
|
|
2908
|
+
|
|
2909
|
+
const slippageMult = stopLossSlippageBps / 10000;
|
|
2910
|
+
const stopLossLimitPrice = stopLossPrice === undefined
|
|
2911
|
+
? undefined
|
|
2912
|
+
: exitBuy
|
|
2913
|
+
? stopLossPrice * (1 + slippageMult)
|
|
2914
|
+
: stopLossPrice * (1 - slippageMult);
|
|
2915
|
+
|
|
2916
|
+
const orderWires: Array<{
|
|
2917
|
+
a: number;
|
|
2918
|
+
b: boolean;
|
|
2919
|
+
p: string;
|
|
2920
|
+
s: string;
|
|
2921
|
+
r: boolean;
|
|
2922
|
+
t: { limit: { tif: 'Gtc' | 'Alo' } } | { trigger: { triggerPx: string; isMarket: boolean; tpsl: 'tp' | 'sl' } };
|
|
2923
|
+
}> = [
|
|
2924
|
+
{
|
|
2925
|
+
a: assetIndex,
|
|
2926
|
+
b: isBuy,
|
|
2927
|
+
p: roundPrice(entryPrice, szDecimals),
|
|
2928
|
+
s: roundedSize,
|
|
2929
|
+
r: false,
|
|
2930
|
+
t: { limit: { tif: entryTif } },
|
|
2767
2931
|
},
|
|
2768
2932
|
];
|
|
2933
|
+
if (takeProfitPrice !== undefined) {
|
|
2934
|
+
orderWires.push({
|
|
2935
|
+
a: assetIndex,
|
|
2936
|
+
b: exitBuy,
|
|
2937
|
+
p: roundPrice(takeProfitPrice, szDecimals),
|
|
2938
|
+
s: roundedSize,
|
|
2939
|
+
r: true,
|
|
2940
|
+
t: {
|
|
2941
|
+
trigger: {
|
|
2942
|
+
triggerPx: roundPrice(takeProfitPrice, szDecimals),
|
|
2943
|
+
isMarket: false,
|
|
2944
|
+
tpsl: 'tp',
|
|
2945
|
+
},
|
|
2946
|
+
},
|
|
2947
|
+
});
|
|
2948
|
+
}
|
|
2949
|
+
if (stopLossPrice !== undefined) {
|
|
2950
|
+
orderWires.push({
|
|
2951
|
+
a: assetIndex,
|
|
2952
|
+
b: exitBuy,
|
|
2953
|
+
p: roundPrice(stopLossLimitPrice!, szDecimals),
|
|
2954
|
+
s: roundedSize,
|
|
2955
|
+
r: true,
|
|
2956
|
+
t: {
|
|
2957
|
+
trigger: {
|
|
2958
|
+
triggerPx: roundPrice(stopLossPrice, szDecimals),
|
|
2959
|
+
isMarket: stopLossIsMarket,
|
|
2960
|
+
tpsl: 'sl',
|
|
2961
|
+
},
|
|
2962
|
+
},
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2769
2965
|
|
|
2770
2966
|
const orderRequest: {
|
|
2771
2967
|
orders: typeof orderWires;
|
|
@@ -2783,10 +2979,10 @@ export class HyperliquidClient {
|
|
|
2783
2979
|
|
|
2784
2980
|
try {
|
|
2785
2981
|
const response = await this.exchange.order(orderRequest, this.vaultParam);
|
|
2786
|
-
this.log('
|
|
2982
|
+
this.log('Bracket order response:', JSON.stringify(response, null, 2));
|
|
2787
2983
|
return response as unknown as OrderResponse;
|
|
2788
2984
|
} catch (error) {
|
|
2789
|
-
this.log('
|
|
2985
|
+
this.log('Bracket order error:', error);
|
|
2790
2986
|
return {
|
|
2791
2987
|
status: 'err',
|
|
2792
2988
|
response: error instanceof Error ? error.message : String(error),
|
|
@@ -3247,6 +3443,29 @@ export class HyperliquidClient {
|
|
|
3247
3443
|
) {
|
|
3248
3444
|
await this.getMetaAndAssetCtxs();
|
|
3249
3445
|
|
|
3446
|
+
if (!Number.isFinite(durationMinutes) || durationMinutes < 5 || durationMinutes > 1440) {
|
|
3447
|
+
throw new Error('TWAP duration must be between 5 and 1440 minutes');
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
// Native TWAP fires a sub-order every 30s; each must clear the exchange
|
|
3451
|
+
// minimum notional or the venue silently skips slices.
|
|
3452
|
+
if (!reduceOnly) {
|
|
3453
|
+
try {
|
|
3454
|
+
const mid = parseFloat((await this.getAllMids())[coin]);
|
|
3455
|
+
if (Number.isFinite(mid) && mid > 0) {
|
|
3456
|
+
const perSlice = (size * mid) / Math.max(1, Math.round(durationMinutes) * 2);
|
|
3457
|
+
if (perSlice < MIN_ORDER_NOTIONAL_USD) {
|
|
3458
|
+
throw new Error(
|
|
3459
|
+
`TWAP slices of ~$${perSlice.toFixed(2)} fall below the $${MIN_ORDER_NOTIONAL_USD} exchange minimum — shorten the duration or increase the size`
|
|
3460
|
+
);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
} catch (error) {
|
|
3464
|
+
if (error instanceof Error && error.message.includes('exchange minimum')) throw error;
|
|
3465
|
+
this.log('TWAP min-notional pre-check skipped (no mid available):', error);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3250
3469
|
if (leverage) {
|
|
3251
3470
|
await this.updateLeverage(coin, leverage);
|
|
3252
3471
|
}
|
package/scripts/core/utils.ts
CHANGED
|
@@ -138,6 +138,43 @@ export function sleep(ms: number): Promise<void> {
|
|
|
138
138
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/** Hyperliquid rejects orders below this notional (waived for reduce-only). */
|
|
142
|
+
export const MIN_ORDER_NOTIONAL_USD = 10;
|
|
143
|
+
|
|
144
|
+
export type ParsedOrderStatus =
|
|
145
|
+
| { kind: 'resting'; oid: number }
|
|
146
|
+
| { kind: 'filled'; totalSz: number; avgPx: number; oid: number }
|
|
147
|
+
/** normalTpsl children behind an unfilled parent — success states, not errors. */
|
|
148
|
+
| { kind: 'waiting'; state: 'waitingForFill' | 'waitingForTrigger' }
|
|
149
|
+
| { kind: 'error'; error: string }
|
|
150
|
+
| { kind: 'unknown'; raw: unknown };
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Parse a single entry of an order response's `statuses` array. Handles the
|
|
154
|
+
* plain-string success states ("waitingForFill" / "waitingForTrigger") that
|
|
155
|
+
* Hyperliquid returns for normalTpsl children armed behind an unfilled parent,
|
|
156
|
+
* alongside the usual resting/filled/error objects.
|
|
157
|
+
*/
|
|
158
|
+
export function parseOrderStatus(status: unknown): ParsedOrderStatus {
|
|
159
|
+
if (status === 'waitingForFill' || status === 'waitingForTrigger') {
|
|
160
|
+
return { kind: 'waiting', state: status };
|
|
161
|
+
}
|
|
162
|
+
if (status && typeof status === 'object') {
|
|
163
|
+
const data = status as Record<string, unknown>;
|
|
164
|
+
if (data.resting) {
|
|
165
|
+
return { kind: 'resting', oid: (data.resting as { oid: number }).oid };
|
|
166
|
+
}
|
|
167
|
+
if (data.filled) {
|
|
168
|
+
const f = data.filled as { totalSz: string; avgPx: string; oid: number };
|
|
169
|
+
return { kind: 'filled', totalSz: parseFloat(f.totalSz), avgPx: parseFloat(f.avgPx), oid: f.oid };
|
|
170
|
+
}
|
|
171
|
+
if (data.error) {
|
|
172
|
+
return { kind: 'error', error: String(data.error) };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { kind: 'unknown', raw: status };
|
|
176
|
+
}
|
|
177
|
+
|
|
141
178
|
/**
|
|
142
179
|
* Generate a random client order ID
|
|
143
180
|
*/
|
package/scripts/lib.ts
CHANGED
|
@@ -47,7 +47,10 @@ export {
|
|
|
47
47
|
generateCloid,
|
|
48
48
|
orderToWire,
|
|
49
49
|
checkBuilderFeeApproval,
|
|
50
|
+
MIN_ORDER_NOTIONAL_USD,
|
|
51
|
+
parseOrderStatus,
|
|
50
52
|
} from './core/utils.js';
|
|
53
|
+
export type { ParsedOrderStatus } from './core/utils.js';
|
|
51
54
|
|
|
52
55
|
export type * from './core/types.js';
|
|
53
56
|
|