@raintree-technology/perps 0.1.2 → 0.1.4
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 -1
- package/README.md +42 -4
- package/dist/adapters/aevo.d.ts +1 -6
- package/dist/adapters/aevo.js +0 -21
- package/dist/adapters/certification.js +55 -9
- package/dist/adapters/decibel/order-manager.d.ts +41 -0
- package/dist/adapters/decibel/order-manager.js +216 -0
- package/dist/adapters/decibel/rest-client.d.ts +21 -0
- package/dist/adapters/decibel/rest-client.js +15 -8
- package/dist/adapters/decibel/ws-feed.js +19 -4
- package/dist/adapters/decibel.d.ts +15 -10
- package/dist/adapters/decibel.js +371 -50
- package/dist/adapters/hyperliquid.d.ts +1 -6
- package/dist/adapters/hyperliquid.js +0 -18
- package/dist/adapters/interface.d.ts +15 -14
- package/dist/adapters/orderly.d.ts +1 -9
- package/dist/adapters/orderly.js +0 -33
- package/dist/adapters/paradex.d.ts +1 -9
- package/dist/adapters/paradex.js +1 -34
- package/dist/cli/experience.js +0 -1
- package/dist/cli/program.js +28 -5
- package/dist/commands/arb/alert.d.ts +0 -4
- package/dist/commands/arb/alert.js +4 -14
- package/dist/commands/markets/index.js +2 -0
- package/dist/commands/markets/read-simple.d.ts +2 -0
- package/dist/commands/markets/read-simple.js +130 -0
- package/dist/commands/order/advanced-simple.d.ts +2 -0
- package/dist/commands/order/advanced-simple.js +820 -0
- package/dist/commands/order/index.js +2 -0
- package/dist/index.js +35 -1
- package/dist/lib/exit-codes.js +5 -1
- package/dist/lib/prompts.d.ts +0 -18
- package/dist/lib/prompts.js +1 -22
- package/dist/lib/schema.d.ts +8 -8
- package/dist/lib/schema.js +47 -8
- package/package.json +5 -2
|
@@ -192,6 +192,7 @@ export interface ExchangeCredentials {
|
|
|
192
192
|
accountAddress?: string;
|
|
193
193
|
subaccountAddress?: string;
|
|
194
194
|
packageAddress?: string;
|
|
195
|
+
usdcAddress?: string;
|
|
195
196
|
restUrl?: string;
|
|
196
197
|
wsUrl?: string;
|
|
197
198
|
fullnodeUrl?: string;
|
|
@@ -286,20 +287,20 @@ export interface PerpDEXAdapter {
|
|
|
286
287
|
cancelAllOrders(market?: string): Promise<number>;
|
|
287
288
|
setLeverage(market: string, leverage: number): Promise<void>;
|
|
288
289
|
setMarginType(market: string, type: MarginType): Promise<void>;
|
|
289
|
-
modifyOrder(params: ModifyOrderParams)
|
|
290
|
-
batchPlaceOrders(paramsList: OrderParams[])
|
|
291
|
-
batchCancelOrders(paramsList: CancelOrderParams[])
|
|
292
|
-
cancelAllAfter(timeoutMs: number)
|
|
293
|
-
getOrderHistory(market?: string, limit?: number)
|
|
294
|
-
getFundingHistory(market?: string, limit?: number)
|
|
295
|
-
getPublicTrades(market: string, limit?: number)
|
|
296
|
-
setMMP(config: MMPConfig)
|
|
297
|
-
getMMP(market: string)
|
|
298
|
-
resetMMP(market: string)
|
|
299
|
-
placeTWAP(params: TWAPParams)
|
|
300
|
-
cancelTWAP(twapId: string)
|
|
301
|
-
getTWAPStatus(twapId: string)
|
|
302
|
-
updateIsolatedMargin(market: string, amount: string)
|
|
290
|
+
modifyOrder?: (params: ModifyOrderParams) => Promise<Order>;
|
|
291
|
+
batchPlaceOrders?: (paramsList: OrderParams[]) => Promise<Order[]>;
|
|
292
|
+
batchCancelOrders?: (paramsList: CancelOrderParams[]) => Promise<boolean[]>;
|
|
293
|
+
cancelAllAfter?: (timeoutMs: number) => Promise<void>;
|
|
294
|
+
getOrderHistory?: (market?: string, limit?: number) => Promise<Order[]>;
|
|
295
|
+
getFundingHistory?: (market?: string, limit?: number) => Promise<FundingPayment[]>;
|
|
296
|
+
getPublicTrades?: (market: string, limit?: number) => Promise<PublicTrade[]>;
|
|
297
|
+
setMMP?: (config: MMPConfig) => Promise<void>;
|
|
298
|
+
getMMP?: (market: string) => Promise<MMPStatus>;
|
|
299
|
+
resetMMP?: (market: string) => Promise<void>;
|
|
300
|
+
placeTWAP?: (params: TWAPParams) => Promise<TWAPStatus>;
|
|
301
|
+
cancelTWAP?: (twapId: string) => Promise<boolean>;
|
|
302
|
+
getTWAPStatus?: (twapId: string) => Promise<TWAPStatus | null>;
|
|
303
|
+
updateIsolatedMargin?: (market: string, amount: string) => Promise<void>;
|
|
303
304
|
subscribe(callbacks: SubscriptionCallbacks): Unsubscribe;
|
|
304
305
|
subscribeOrderBook(market: string, callback: (book: OrderBook) => void): Unsubscribe;
|
|
305
306
|
subscribeTicker(market: string, callback: (ticker: Ticker) => void): Unsubscribe;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* API Docs: https://orderly.network/docs
|
|
6
6
|
* Base URL: https://api-evm.orderly.org/v1
|
|
7
7
|
*/
|
|
8
|
-
import { type PerpDEXAdapter, type ExchangeInfo, type ExchangeConfig, type Market, type Ticker, type OrderBook, type FundingRate, type Position, type Order, type Balance, type Trade, type OrderParams, type CancelOrderParams, type
|
|
8
|
+
import { type PerpDEXAdapter, type ExchangeInfo, type ExchangeConfig, type Market, type Ticker, type OrderBook, type FundingRate, type Position, type Order, type Balance, type Trade, type OrderParams, type CancelOrderParams, type FundingPayment, type PublicTrade, type SubscriptionCallbacks, type Unsubscribe, type MarginType } from "./interface.js";
|
|
9
9
|
export declare class OrderlyAdapter implements PerpDEXAdapter {
|
|
10
10
|
readonly info: ExchangeInfo;
|
|
11
11
|
private baseUrl;
|
|
@@ -43,20 +43,12 @@ export declare class OrderlyAdapter implements PerpDEXAdapter {
|
|
|
43
43
|
cancelAllOrders(market?: string): Promise<number>;
|
|
44
44
|
setLeverage(_market: string, leverage: number): Promise<void>;
|
|
45
45
|
setMarginType(_market: string, type: MarginType): Promise<void>;
|
|
46
|
-
modifyOrder(_params: ModifyOrderParams): Promise<Order>;
|
|
47
46
|
batchPlaceOrders(paramsList: OrderParams[]): Promise<Order[]>;
|
|
48
47
|
batchCancelOrders(paramsList: CancelOrderParams[]): Promise<boolean[]>;
|
|
49
48
|
cancelAllAfter(timeoutMs: number): Promise<void>;
|
|
50
49
|
getOrderHistory(market?: string, limit?: number): Promise<Order[]>;
|
|
51
50
|
getFundingHistory(market?: string, limit?: number): Promise<FundingPayment[]>;
|
|
52
51
|
getPublicTrades(market: string, limit?: number): Promise<PublicTrade[]>;
|
|
53
|
-
setMMP(_config: MMPConfig): Promise<void>;
|
|
54
|
-
getMMP(_market: string): Promise<MMPStatus>;
|
|
55
|
-
resetMMP(_market: string): Promise<void>;
|
|
56
|
-
placeTWAP(_params: TWAPParams): Promise<TWAPStatus>;
|
|
57
|
-
cancelTWAP(_twapId: string): Promise<boolean>;
|
|
58
|
-
getTWAPStatus(_twapId: string): Promise<TWAPStatus | null>;
|
|
59
|
-
updateIsolatedMargin(_market: string, _amount: string): Promise<void>;
|
|
60
52
|
subscribe(callbacks: SubscriptionCallbacks): Unsubscribe;
|
|
61
53
|
subscribeOrderBook(market: string, callback: (book: OrderBook) => void): Unsubscribe;
|
|
62
54
|
subscribeTicker(market: string, callback: (ticker: Ticker) => void): Unsubscribe;
|
package/dist/adapters/orderly.js
CHANGED
|
@@ -660,9 +660,6 @@ export class OrderlyAdapter {
|
|
|
660
660
|
// --------------------------------------------------------------------------
|
|
661
661
|
// Advanced Trading
|
|
662
662
|
// --------------------------------------------------------------------------
|
|
663
|
-
async modifyOrder(_params) {
|
|
664
|
-
throw new Error("Orderly does not support order modification. Cancel and replace instead.");
|
|
665
|
-
}
|
|
666
663
|
async batchPlaceOrders(paramsList) {
|
|
667
664
|
// Orderly has batch create order endpoint
|
|
668
665
|
const results = [];
|
|
@@ -741,36 +738,6 @@ export class OrderlyAdapter {
|
|
|
741
738
|
}));
|
|
742
739
|
}
|
|
743
740
|
// --------------------------------------------------------------------------
|
|
744
|
-
// Market Maker Protection
|
|
745
|
-
// --------------------------------------------------------------------------
|
|
746
|
-
async setMMP(_config) {
|
|
747
|
-
throw new Error("Orderly does not support Market Maker Protection (MMP)");
|
|
748
|
-
}
|
|
749
|
-
async getMMP(_market) {
|
|
750
|
-
throw new Error("Orderly does not support Market Maker Protection (MMP)");
|
|
751
|
-
}
|
|
752
|
-
async resetMMP(_market) {
|
|
753
|
-
throw new Error("Orderly does not support Market Maker Protection (MMP)");
|
|
754
|
-
}
|
|
755
|
-
// --------------------------------------------------------------------------
|
|
756
|
-
// TWAP Orders
|
|
757
|
-
// --------------------------------------------------------------------------
|
|
758
|
-
async placeTWAP(_params) {
|
|
759
|
-
throw new Error("Orderly does not support TWAP orders");
|
|
760
|
-
}
|
|
761
|
-
async cancelTWAP(_twapId) {
|
|
762
|
-
throw new Error("Orderly does not support TWAP orders");
|
|
763
|
-
}
|
|
764
|
-
async getTWAPStatus(_twapId) {
|
|
765
|
-
throw new Error("Orderly does not support TWAP orders");
|
|
766
|
-
}
|
|
767
|
-
// --------------------------------------------------------------------------
|
|
768
|
-
// Margin Management
|
|
769
|
-
// --------------------------------------------------------------------------
|
|
770
|
-
async updateIsolatedMargin(_market, _amount) {
|
|
771
|
-
throw new Error("Orderly does not support isolated margin adjustment");
|
|
772
|
-
}
|
|
773
|
-
// --------------------------------------------------------------------------
|
|
774
741
|
// Subscriptions (polling)
|
|
775
742
|
// --------------------------------------------------------------------------
|
|
776
743
|
subscribe(callbacks) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* API Docs: https://docs.paradex.trade/
|
|
6
6
|
* Base URL: https://api.prod.paradex.trade/v1
|
|
7
7
|
*/
|
|
8
|
-
import { type PerpDEXAdapter, type ExchangeInfo, type ExchangeConfig, type Market, type Ticker, type OrderBook, type FundingRate, type Position, type Order, type Balance, type Trade, type OrderParams, type CancelOrderParams, type SubscriptionCallbacks, type Unsubscribe, type MarginType, type ModifyOrderParams, type FundingPayment, type PublicTrade
|
|
8
|
+
import { type PerpDEXAdapter, type ExchangeInfo, type ExchangeConfig, type Market, type Ticker, type OrderBook, type FundingRate, type Position, type Order, type Balance, type Trade, type OrderParams, type CancelOrderParams, type SubscriptionCallbacks, type Unsubscribe, type MarginType, type ModifyOrderParams, type FundingPayment, type PublicTrade } from "./interface.js";
|
|
9
9
|
export declare class ParadexAdapter implements PerpDEXAdapter {
|
|
10
10
|
readonly info: ExchangeInfo;
|
|
11
11
|
private baseUrl;
|
|
@@ -46,17 +46,9 @@ export declare class ParadexAdapter implements PerpDEXAdapter {
|
|
|
46
46
|
modifyOrder(params: ModifyOrderParams): Promise<Order>;
|
|
47
47
|
batchPlaceOrders(paramsList: OrderParams[]): Promise<Order[]>;
|
|
48
48
|
batchCancelOrders(paramsList: CancelOrderParams[]): Promise<boolean[]>;
|
|
49
|
-
cancelAllAfter(_timeoutMs: number): Promise<void>;
|
|
50
49
|
getOrderHistory(market?: string, limit?: number): Promise<Order[]>;
|
|
51
50
|
getFundingHistory(market?: string, limit?: number): Promise<FundingPayment[]>;
|
|
52
51
|
getPublicTrades(market: string, limit?: number): Promise<PublicTrade[]>;
|
|
53
|
-
setMMP(_config: MMPConfig): Promise<void>;
|
|
54
|
-
getMMP(_market: string): Promise<MMPStatus>;
|
|
55
|
-
resetMMP(_market: string): Promise<void>;
|
|
56
|
-
placeTWAP(_params: TWAPParams): Promise<TWAPStatus>;
|
|
57
|
-
cancelTWAP(_twapId: string): Promise<boolean>;
|
|
58
|
-
getTWAPStatus(_twapId: string): Promise<TWAPStatus | null>;
|
|
59
|
-
updateIsolatedMargin(_market: string, _amount: string): Promise<void>;
|
|
60
52
|
subscribe(callbacks: SubscriptionCallbacks): Unsubscribe;
|
|
61
53
|
subscribeOrderBook(market: string, callback: (book: OrderBook) => void): Unsubscribe;
|
|
62
54
|
subscribeTicker(market: string, callback: (ticker: Ticker) => void): Unsubscribe;
|
package/dist/adapters/paradex.js
CHANGED
|
@@ -24,7 +24,7 @@ export class ParadexAdapter {
|
|
|
24
24
|
perp: true,
|
|
25
25
|
margin: true,
|
|
26
26
|
crossMargin: true,
|
|
27
|
-
isolatedMargin:
|
|
27
|
+
isolatedMargin: false,
|
|
28
28
|
stopOrders: true,
|
|
29
29
|
takeProfitOrders: true,
|
|
30
30
|
postOnly: true,
|
|
@@ -635,9 +635,6 @@ export class ParadexAdapter {
|
|
|
635
635
|
}
|
|
636
636
|
return results;
|
|
637
637
|
}
|
|
638
|
-
async cancelAllAfter(_timeoutMs) {
|
|
639
|
-
throw new Error("Paradex does not support cancelAllAfter (dead man's switch)");
|
|
640
|
-
}
|
|
641
638
|
async getOrderHistory(market, limit = 100) {
|
|
642
639
|
const params = new URLSearchParams();
|
|
643
640
|
if (market)
|
|
@@ -674,36 +671,6 @@ export class ParadexAdapter {
|
|
|
674
671
|
}));
|
|
675
672
|
}
|
|
676
673
|
// --------------------------------------------------------------------------
|
|
677
|
-
// Market Maker Protection
|
|
678
|
-
// --------------------------------------------------------------------------
|
|
679
|
-
async setMMP(_config) {
|
|
680
|
-
throw new Error("Paradex does not support Market Maker Protection (MMP)");
|
|
681
|
-
}
|
|
682
|
-
async getMMP(_market) {
|
|
683
|
-
throw new Error("Paradex does not support Market Maker Protection (MMP)");
|
|
684
|
-
}
|
|
685
|
-
async resetMMP(_market) {
|
|
686
|
-
throw new Error("Paradex does not support Market Maker Protection (MMP)");
|
|
687
|
-
}
|
|
688
|
-
// --------------------------------------------------------------------------
|
|
689
|
-
// TWAP Orders
|
|
690
|
-
// --------------------------------------------------------------------------
|
|
691
|
-
async placeTWAP(_params) {
|
|
692
|
-
throw new Error("Paradex does not support TWAP orders");
|
|
693
|
-
}
|
|
694
|
-
async cancelTWAP(_twapId) {
|
|
695
|
-
throw new Error("Paradex does not support TWAP orders");
|
|
696
|
-
}
|
|
697
|
-
async getTWAPStatus(_twapId) {
|
|
698
|
-
throw new Error("Paradex does not support TWAP orders");
|
|
699
|
-
}
|
|
700
|
-
// --------------------------------------------------------------------------
|
|
701
|
-
// Margin Management
|
|
702
|
-
// --------------------------------------------------------------------------
|
|
703
|
-
async updateIsolatedMargin(_market, _amount) {
|
|
704
|
-
throw new Error("Paradex does not support isolated margin adjustment");
|
|
705
|
-
}
|
|
706
|
-
// --------------------------------------------------------------------------
|
|
707
674
|
// Subscriptions (polling)
|
|
708
675
|
// --------------------------------------------------------------------------
|
|
709
676
|
subscribe(callbacks) {
|
package/dist/cli/experience.js
CHANGED
|
@@ -107,7 +107,6 @@ export function printSessionBanner(args) {
|
|
|
107
107
|
for (const line of getPerpsAsciiMark()) {
|
|
108
108
|
console.log(highlighter.brand(line));
|
|
109
109
|
}
|
|
110
|
-
console.log(highlighter.dim("by Raintree Technology"));
|
|
111
110
|
console.log("");
|
|
112
111
|
console.log(`${highlighter.dim("mode")} ${networkTag} ${highlighter.dim("exchange")} ${exchangeTag}`);
|
|
113
112
|
console.log(`${highlighter.dim("command")} ${commandTag} ${highlighter.dim("version")} ${highlighter.dim(`v${args.version}`)}`);
|
package/dist/cli/program.js
CHANGED
|
@@ -8,6 +8,8 @@ import { createContext } from "./context.js";
|
|
|
8
8
|
import { highlighter, printCommandCompletion, printSessionBanner } from "./experience.js";
|
|
9
9
|
import { commandPathFromCommand, resolveTestnetMode } from "./network-defaults.js";
|
|
10
10
|
import { outputError } from "./output.js";
|
|
11
|
+
import { inferExitCode } from "../lib/exit-codes.js";
|
|
12
|
+
import { withJsonContract } from "../lib/contracts.js";
|
|
11
13
|
const require = createRequire(import.meta.url);
|
|
12
14
|
const pkg = require("../../package.json");
|
|
13
15
|
const commandMeta = new WeakMap();
|
|
@@ -30,6 +32,7 @@ export function createProgram() {
|
|
|
30
32
|
.description("Universal CLI for perpetual DEXes")
|
|
31
33
|
.showSuggestionAfterError(true)
|
|
32
34
|
.showHelpAfterError()
|
|
35
|
+
.exitOverride()
|
|
33
36
|
.version(pkg.version)
|
|
34
37
|
.option("--json", "Output in JSON format", false)
|
|
35
38
|
.option("--human", "Enable human-first output UX (banner, timing, rich formatting)", false)
|
|
@@ -62,17 +65,37 @@ Examples:
|
|
|
62
65
|
setExchange(opts.exchange);
|
|
63
66
|
}
|
|
64
67
|
catch (err) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
69
|
+
const exitCode = inferExitCode(err);
|
|
70
|
+
if (opts.json) {
|
|
71
|
+
console.error(JSON.stringify(withJsonContract("perps.error", {
|
|
72
|
+
status: "error",
|
|
73
|
+
error: { message, exitCode },
|
|
74
|
+
}), null, 2));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
outputError(message);
|
|
78
|
+
console.error(highlighter.dim(`Available exchanges: ${getAvailableExchanges().join(", ")}`));
|
|
79
|
+
}
|
|
80
|
+
process.exit(exitCode);
|
|
68
81
|
}
|
|
69
82
|
let config;
|
|
70
83
|
try {
|
|
71
84
|
config = loadConfig(isTestnet, opts.exchange);
|
|
72
85
|
}
|
|
73
86
|
catch (err) {
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
const exitCode = inferExitCode(err);
|
|
89
|
+
if (opts.json) {
|
|
90
|
+
console.error(JSON.stringify(withJsonContract("perps.error", {
|
|
91
|
+
status: "error",
|
|
92
|
+
error: { message, exitCode },
|
|
93
|
+
}), null, 2));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
outputError(message);
|
|
97
|
+
}
|
|
98
|
+
process.exit(exitCode);
|
|
76
99
|
}
|
|
77
100
|
const context = createContext(config);
|
|
78
101
|
// Store metadata on the root command
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Arb Alert Command
|
|
3
|
-
* Set up alerts for funding rate arbitrage opportunities
|
|
4
|
-
*/
|
|
5
1
|
import { getContext } from "../../cli/program.js";
|
|
6
|
-
import {
|
|
2
|
+
import { COMMON_ASSETS, DEFAULT_ALERT_INTERVAL_MS, MIN_PROFITABLE_SPREAD_PCT, } from "../../lib/constants.js";
|
|
7
3
|
import { getAvailableExchanges } from "../../lib/exchange.js";
|
|
8
|
-
import {
|
|
9
|
-
import { parseAssetList,
|
|
4
|
+
import { pollFundingRates } from "../../lib/funding-tracker.js";
|
|
5
|
+
import { parseAssetList, parsePositiveInt, parsePositiveNumber } from "../../lib/validate.js";
|
|
10
6
|
export function registerArbAlertCommand(arb) {
|
|
11
7
|
arb
|
|
12
8
|
.command("alert")
|
|
@@ -31,19 +27,15 @@ export function registerArbAlertCommand(arb) {
|
|
|
31
27
|
console.log(` Exchanges: ${exchanges.join(", ")}`);
|
|
32
28
|
console.log("\n Monitoring... (Ctrl+C to stop)\n");
|
|
33
29
|
console.log(" " + "─".repeat(60));
|
|
34
|
-
// Check immediately
|
|
35
30
|
await checkAndAlert(config.assets, exchanges, config.threshold, isTestnet);
|
|
36
|
-
// Then check on interval
|
|
37
31
|
const interval = setInterval(async () => {
|
|
38
32
|
await checkAndAlert(config.assets, exchanges, config.threshold, isTestnet);
|
|
39
33
|
}, config.intervalMin * 60 * 1000);
|
|
40
|
-
// Handle Ctrl+C
|
|
41
34
|
process.once("SIGINT", () => {
|
|
42
35
|
clearInterval(interval);
|
|
43
36
|
console.log("\n\n Alert monitor stopped.\n");
|
|
44
37
|
process.exit(0);
|
|
45
38
|
});
|
|
46
|
-
// Keep running
|
|
47
39
|
await new Promise(() => { });
|
|
48
40
|
});
|
|
49
41
|
}
|
|
@@ -51,7 +43,6 @@ async function checkAndAlert(assets, exchanges, threshold, isTestnet) {
|
|
|
51
43
|
try {
|
|
52
44
|
const records = await pollFundingRates(assets, exchanges, undefined, isTestnet);
|
|
53
45
|
const now = new Date().toLocaleTimeString();
|
|
54
|
-
// Group by asset and find opportunities
|
|
55
46
|
const byAsset = new Map();
|
|
56
47
|
for (const r of records) {
|
|
57
48
|
const asset = r.market.replace("-PERP", "");
|
|
@@ -71,11 +62,10 @@ async function checkAndAlert(assets, exchanges, threshold, isTestnet) {
|
|
|
71
62
|
if (annualized >= threshold) {
|
|
72
63
|
foundAny = true;
|
|
73
64
|
const dailyOn10k = spread * 24 * 10000;
|
|
74
|
-
console.log(`\n \x1b[32m
|
|
65
|
+
console.log(`\n \x1b[32m${now} - ${asset} OPPORTUNITY\x1b[0m`);
|
|
75
66
|
console.log(` Short ${high.exchange} (${(high.rate * 100).toFixed(4)}%)`);
|
|
76
67
|
console.log(` Long ${low.exchange} (${(low.rate * 100).toFixed(4)}%)`);
|
|
77
68
|
console.log(` Spread: \x1b[32m${annualized.toFixed(1)}%\x1b[0m annualized (~$${dailyOn10k.toFixed(2)}/day on $10k)`);
|
|
78
|
-
// Could add external notifications here (Discord, Telegram, etc.)
|
|
79
69
|
}
|
|
80
70
|
}
|
|
81
71
|
if (!foundAny) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { registerMarketsLsSimpleCommand } from "./ls-simple.js";
|
|
2
|
+
import { registerMarketsReadSimpleCommands } from "./read-simple.js";
|
|
2
3
|
export function registerMarketsCommands(program) {
|
|
3
4
|
const markets = program.command("markets").description("Market information");
|
|
4
5
|
registerMarketsLsSimpleCommand(markets);
|
|
6
|
+
registerMarketsReadSimpleCommands(markets);
|
|
5
7
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getContext, getOutputOptions } from "../../cli/program.js";
|
|
2
|
+
import { output, outputError, outputSuccess } from "../../cli/output.js";
|
|
3
|
+
import { getExchangeAdapter } from "../../lib/exchange.js";
|
|
4
|
+
import { getExchangeCredentials } from "../../lib/config.js";
|
|
5
|
+
import { normalizeMarket } from "../order/shared.js";
|
|
6
|
+
function parseMarketSymbol(symbol) {
|
|
7
|
+
return normalizeMarket(symbol);
|
|
8
|
+
}
|
|
9
|
+
async function withConnectedMarketAdapter(command, action) {
|
|
10
|
+
const ctx = getContext(command);
|
|
11
|
+
const outputOpts = getOutputOptions(command);
|
|
12
|
+
const adapter = getExchangeAdapter();
|
|
13
|
+
let connected = false;
|
|
14
|
+
try {
|
|
15
|
+
const credentials = getExchangeCredentials(ctx.config, adapter.info.id);
|
|
16
|
+
await adapter.connect({
|
|
17
|
+
testnet: ctx.config.testnet,
|
|
18
|
+
rpcUrl: credentials.fullnodeUrl,
|
|
19
|
+
wsUrl: credentials.wsUrl,
|
|
20
|
+
credentials,
|
|
21
|
+
});
|
|
22
|
+
connected = true;
|
|
23
|
+
await action(adapter, outputOpts);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
outputError(err instanceof Error ? err.message : String(err));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
if (connected) {
|
|
31
|
+
await adapter.disconnect().catch(() => undefined);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function registerMarketsReadSimpleCommands(markets) {
|
|
36
|
+
markets
|
|
37
|
+
.command("get <symbol>")
|
|
38
|
+
.description("Show market details for one symbol")
|
|
39
|
+
.action(async function (symbol) {
|
|
40
|
+
const market = parseMarketSymbol(symbol);
|
|
41
|
+
await withConnectedMarketAdapter(this, async (adapter, outputOpts) => {
|
|
42
|
+
const row = await adapter.getMarket(market);
|
|
43
|
+
if (outputOpts.json) {
|
|
44
|
+
output({ market: row }, outputOpts);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!row) {
|
|
48
|
+
outputError(`Market ${market} not found`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
outputSuccess(`Market ${row.symbol}`);
|
|
52
|
+
console.log(` Base: ${row.baseAsset}`);
|
|
53
|
+
console.log(` Quote: ${row.quoteAsset}`);
|
|
54
|
+
console.log(` Type: ${row.type}`);
|
|
55
|
+
console.log(` MaxLeverage: ${row.maxLeverage}x`);
|
|
56
|
+
console.log(` TickSize: ${row.tickSize}`);
|
|
57
|
+
console.log("");
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
markets
|
|
61
|
+
.command("ticker <symbol>")
|
|
62
|
+
.description("Show ticker for one symbol")
|
|
63
|
+
.action(async function (symbol) {
|
|
64
|
+
const market = parseMarketSymbol(symbol);
|
|
65
|
+
await withConnectedMarketAdapter(this, async (adapter, outputOpts) => {
|
|
66
|
+
const ticker = await adapter.getTicker(market);
|
|
67
|
+
if (outputOpts.json) {
|
|
68
|
+
output({ ticker }, outputOpts);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
outputSuccess(`Ticker ${ticker.market}`);
|
|
72
|
+
console.log(` Last: ${ticker.lastPrice}`);
|
|
73
|
+
console.log(` Mark: ${ticker.markPrice}`);
|
|
74
|
+
console.log(` FundingRate: ${ticker.fundingRate}`);
|
|
75
|
+
console.log(` OpenInterest: ${ticker.openInterest}`);
|
|
76
|
+
console.log("");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
markets
|
|
80
|
+
.command("tickers")
|
|
81
|
+
.description("Show all tickers")
|
|
82
|
+
.action(async function () {
|
|
83
|
+
await withConnectedMarketAdapter(this, async (adapter, outputOpts) => {
|
|
84
|
+
const tickers = await adapter.getTickers();
|
|
85
|
+
if (outputOpts.json) {
|
|
86
|
+
output({ tickers }, outputOpts);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
outputSuccess(`Fetched ${tickers.length} tickers`);
|
|
90
|
+
for (const row of tickers) {
|
|
91
|
+
console.log(` ${row.market.padEnd(10)} last=${row.lastPrice} funding=${row.fundingRate} oi=${row.openInterest}`);
|
|
92
|
+
}
|
|
93
|
+
console.log("");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
markets
|
|
97
|
+
.command("funding-rate <symbol>")
|
|
98
|
+
.description("Show funding rate for one symbol")
|
|
99
|
+
.action(async function (symbol) {
|
|
100
|
+
const market = parseMarketSymbol(symbol);
|
|
101
|
+
await withConnectedMarketAdapter(this, async (adapter, outputOpts) => {
|
|
102
|
+
const rate = await adapter.getFundingRate(market);
|
|
103
|
+
if (outputOpts.json) {
|
|
104
|
+
output({ fundingRate: rate }, outputOpts);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
outputSuccess(`Funding rate ${rate.market}`);
|
|
108
|
+
console.log(` Rate: ${rate.rate}`);
|
|
109
|
+
console.log(` NextFunding: ${new Date(rate.nextFundingTime).toISOString()}`);
|
|
110
|
+
console.log("");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
markets
|
|
114
|
+
.command("funding-rates")
|
|
115
|
+
.description("Show funding rates for all markets")
|
|
116
|
+
.action(async function () {
|
|
117
|
+
await withConnectedMarketAdapter(this, async (adapter, outputOpts) => {
|
|
118
|
+
const rates = await adapter.getFundingRates();
|
|
119
|
+
if (outputOpts.json) {
|
|
120
|
+
output({ fundingRates: rates }, outputOpts);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
outputSuccess(`Fetched ${rates.length} funding rates`);
|
|
124
|
+
for (const row of rates) {
|
|
125
|
+
console.log(` ${row.market.padEnd(10)} rate=${row.rate}`);
|
|
126
|
+
}
|
|
127
|
+
console.log("");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|