perp-cli 0.15.0 → 0.16.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.
Files changed (154) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +129 -535
  4. package/README.zh.md +243 -0
  5. package/dist/api/public/lighter.d.ts +5 -0
  6. package/dist/api/public/lighter.js +5 -0
  7. package/dist/api/public/pacifica.js +2 -2
  8. package/dist/arb/sizing.js +4 -2
  9. package/dist/arb/state.d.ts +6 -0
  10. package/dist/balance-by-address.d.ts +39 -0
  11. package/dist/balance-by-address.js +44 -0
  12. package/dist/bot/quote-rules.js +1 -1
  13. package/dist/bot/strategies/funding-arb-strategy.js +10 -3
  14. package/dist/bot/strategies/funding-arb-utils.d.ts +14 -2
  15. package/dist/bot/strategies/funding-arb-utils.js +48 -15
  16. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  17. package/dist/bot/strategies/spot-perp-arb-strategy.js +4 -2
  18. package/dist/bot/strategies/spot-perp-executor.js +2 -2
  19. package/dist/cli-program.js +1 -1
  20. package/dist/commands/account.js +168 -24
  21. package/dist/commands/arb/index.js +19 -77
  22. package/dist/commands/arb-auto.d.ts +24 -0
  23. package/dist/commands/arb-auto.js +155 -81
  24. package/dist/commands/arb.d.ts +8 -0
  25. package/dist/commands/arb.js +49 -23
  26. package/dist/commands/backtest.js +23 -21
  27. package/dist/commands/market.d.ts +48 -0
  28. package/dist/commands/market.js +241 -78
  29. package/dist/commands/options.d.ts +5 -0
  30. package/dist/commands/options.js +20 -3
  31. package/dist/commands/pacifica-earn.js +8 -21
  32. package/dist/commands/portfolio.d.ts +16 -0
  33. package/dist/commands/portfolio.js +51 -8
  34. package/dist/commands/trade.js +4 -4
  35. package/dist/cross-chain-margin.d.ts +3 -1
  36. package/dist/cross-chain-margin.js +4 -2
  37. package/dist/dashboard/server.js +2 -2
  38. package/dist/dashboard/ui.js +9 -6
  39. package/dist/errors.d.ts +5 -0
  40. package/dist/errors.js +10 -0
  41. package/dist/exchanges/address.d.ts +8 -0
  42. package/dist/exchanges/address.js +47 -0
  43. package/dist/exchanges/arcus/adapter.d.ts +10 -6
  44. package/dist/exchanges/arcus/adapter.js +33 -6
  45. package/dist/exchanges/arcus/public.js +3 -2
  46. package/dist/exchanges/aster.d.ts +27 -7
  47. package/dist/exchanges/aster.js +124 -42
  48. package/dist/exchanges/canonical-symbols.d.ts +7 -0
  49. package/dist/exchanges/canonical-symbols.js +20 -0
  50. package/dist/exchanges/capabilities.d.ts +2 -0
  51. package/dist/exchanges/capabilities.js +2 -1
  52. package/dist/exchanges/derive/options.d.ts +5 -0
  53. package/dist/exchanges/derive/options.js +20 -1
  54. package/dist/exchanges/extended/adapter.d.ts +16 -2
  55. package/dist/exchanges/extended/adapter.js +96 -34
  56. package/dist/exchanges/extended/public.d.ts +3 -0
  57. package/dist/exchanges/extended/public.js +9 -2
  58. package/dist/exchanges/grvt/adapter.d.ts +16 -2
  59. package/dist/exchanges/grvt/adapter.js +98 -21
  60. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  61. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  62. package/dist/exchanges/hyperliquid-spot.d.ts +8 -0
  63. package/dist/exchanges/hyperliquid-spot.js +20 -12
  64. package/dist/exchanges/hyperliquid.d.ts +180 -13
  65. package/dist/exchanges/hyperliquid.js +487 -185
  66. package/dist/exchanges/interface.d.ts +64 -8
  67. package/dist/exchanges/interface.js +31 -0
  68. package/dist/exchanges/lighter-public.d.ts +66 -11
  69. package/dist/exchanges/lighter-public.js +249 -81
  70. package/dist/exchanges/lighter-spot.d.ts +7 -1
  71. package/dist/exchanges/lighter-spot.js +99 -90
  72. package/dist/exchanges/lighter.d.ts +70 -11
  73. package/dist/exchanges/lighter.js +285 -92
  74. package/dist/exchanges/ondo/adapter.d.ts +34 -8
  75. package/dist/exchanges/ondo/adapter.js +73 -16
  76. package/dist/exchanges/ondo/public.d.ts +27 -2
  77. package/dist/exchanges/ondo/public.js +63 -31
  78. package/dist/exchanges/outcome-interface.d.ts +4 -0
  79. package/dist/exchanges/pacifica-spot.d.ts +6 -0
  80. package/dist/exchanges/pacifica-spot.js +23 -15
  81. package/dist/exchanges/pacifica-vaults.d.ts +9 -0
  82. package/dist/exchanges/pacifica-vaults.js +14 -1
  83. package/dist/exchanges/pacifica.d.ts +77 -6
  84. package/dist/exchanges/pacifica.js +321 -130
  85. package/dist/exchanges/public-balances.d.ts +25 -0
  86. package/dist/exchanges/public-balances.js +158 -0
  87. package/dist/exchanges/public-positions.d.ts +43 -0
  88. package/dist/exchanges/public-positions.js +154 -0
  89. package/dist/exchanges/qfex/adapter.d.ts +52 -9
  90. package/dist/exchanges/qfex/adapter.js +74 -9
  91. package/dist/exchanges/qfex/public.d.ts +62 -9
  92. package/dist/exchanges/qfex/public.js +128 -22
  93. package/dist/exchanges/qfex/ws-transport.d.ts +1 -1
  94. package/dist/exchanges/risex/adapter.d.ts +19 -7
  95. package/dist/exchanges/risex/adapter.js +58 -12
  96. package/dist/exchanges/spot-factory.js +1 -1
  97. package/dist/exchanges/txflow/adapter.d.ts +14 -7
  98. package/dist/exchanges/txflow/adapter.js +51 -19
  99. package/dist/exchanges/txflow/client.js +3 -1
  100. package/dist/exchanges/txflow/public.d.ts +12 -1
  101. package/dist/exchanges/txflow/public.js +82 -11
  102. package/dist/exchanges/unknown-symbol.d.ts +6 -1
  103. package/dist/exchanges/unknown-symbol.js +7 -2
  104. package/dist/exchanges/variational/adapter.d.ts +2 -6
  105. package/dist/exchanges/venue-types.d.ts +46 -3
  106. package/dist/exchanges/venue-types.js +80 -9
  107. package/dist/exchanges/venue.d.ts +99 -3
  108. package/dist/exchanges/venue.js +72 -0
  109. package/dist/exchanges/venues/hyperliquid.js +9 -0
  110. package/dist/exchanges/venues/lighter-rh.js +13 -3
  111. package/dist/exchanges/venues/lighter.js +13 -3
  112. package/dist/exchanges/venues/pacifica.js +9 -0
  113. package/dist/exchanges/venues/risex.js +15 -0
  114. package/dist/funding/legacy-public.d.ts +5 -3
  115. package/dist/funding/legacy-public.js +33 -37
  116. package/dist/funding/rates.d.ts +54 -5
  117. package/dist/funding/rates.js +49 -13
  118. package/dist/liquidity.d.ts +3 -1
  119. package/dist/liquidity.js +8 -4
  120. package/dist/mcp-pacifica-earn.js +11 -5
  121. package/dist/mcp-server.js +104 -15
  122. package/dist/pacifica/client.d.ts +2 -1
  123. package/dist/pacifica/client.js +11 -0
  124. package/dist/positions-by-address.d.ts +34 -0
  125. package/dist/positions-by-address.js +46 -0
  126. package/dist/strategies/funding-arb.js +35 -62
  127. package/dist/utils.d.ts +11 -1
  128. package/dist/venue-health.js +1 -1
  129. package/dist/ws/arcus-market.d.ts +9 -0
  130. package/dist/ws/arcus-market.js +9 -0
  131. package/dist/ws/aster-market.d.ts +33 -0
  132. package/dist/ws/aster-market.js +30 -0
  133. package/dist/ws/index.d.ts +30 -9
  134. package/dist/ws/index.js +180 -15
  135. package/dist/ws/lighter-market.d.ts +14 -0
  136. package/dist/ws/lighter-market.js +13 -0
  137. package/dist/ws/node.d.ts +30 -9
  138. package/dist/ws/node.js +204 -16
  139. package/dist/ws/unclassified.d.ts +16 -0
  140. package/dist/ws/unclassified.js +43 -0
  141. package/package.json +15 -2
  142. package/skills/perp-cli/SKILL.md +97 -33
  143. package/skills/perp-cli/package.json +15 -2
  144. package/skills/perp-cli/references/agent-operations.md +15 -12
  145. package/skills/perp-cli/references/commands.md +67 -12
  146. package/skills/perp-cli/references/strategies.md +38 -3
  147. package/skills/perp-cli/references/ws-library.md +55 -24
  148. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  149. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  150. package/skills/perp-cli/scripts/preflight.sh +27 -19
  151. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  152. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  153. package/dist/cli-spec.d.ts +0 -46
  154. package/dist/cli-spec.js +0 -81
@@ -6,16 +6,25 @@
6
6
  * quote / margin asset, allowed order types + TIFs, market hours
7
7
  * MDS market_stats snapshot mark price (each entry with its own `time`), open interest,
8
8
  * 24h notional volume
9
- * GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads only)
9
+ * GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads);
10
+ * index_price (market rows, best effort)
10
11
  *
11
12
  * Coupling: order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
12
- * Markets need /refdata + the MDS snapshot. Only funding reads need /md/contracts — its failure
13
- * fails fetchFundingRates, never market listing or order entry.
13
+ * Markets need /refdata + the MDS snapshot; /md/contracts only adds indexPrice — its failure
14
+ * leaves indexPrice null (unknown) with a warning, never fails market listing or order entry.
15
+ * Funding reads need /md/contracts.
16
+ *
17
+ * indexPrice = /md/contracts `index_price` (docs contract-specifications: the Index Price is "the
18
+ * reference spot price for the asset"; the OpenAPI field has no description). Live 2026-10-19 it
19
+ * tracked the MDS `underlier` channel (AAPL 342.47 = 342.47, GOLD 4170.22 vs 4170.05, SAMSUNG-KRW
20
+ * 220092 vs 220082 KRW), moved on 122 of 222 markets within 45 s, and sat a median 0.015 % from
21
+ * the mark (max 5 %, CVNA pre-market). It is in the quote currency (`index_currency`), like the mark.
22
+ * /md/contracts lists ACTIVE markets only; a non-positive / missing value is null.
14
23
  *
15
24
  * Funding is UNVERIFIED (venue flag fundingVerified: false, Q4): the rate's unit is not
16
25
  * confirmed against settled payments and `funding_rate` vs `next_funding_rate` is undecided.
17
26
  * Markets therefore carry fundingRate null; fetchFundingRates returns the raw `funding_rate`
18
- * for the explicit, flagged reads only (`perp funding`, `arb scan --include-unverified`).
27
+ * for the explicit, flagged reads only (`arb scan --rates|--compare --include-unverified`).
19
28
  *
20
29
  * Symbols: "AAPL-USD" → CLI symbol "AAPL". Non-USD quotes keep the suffix ("SKHYNIX-KRW"),
21
30
  * have no canonical symbol and are NOT tradable through perp-cli (USD-assuming sizing / risk).
@@ -60,6 +69,14 @@ export interface QfexContractRow {
60
69
  funding_rate: string;
61
70
  next_funding_rate: string;
62
71
  next_funding_rate_timestamp: number;
72
+ /** Index (reference spot) price in the quote currency — see the module header. */
73
+ index_price?: string;
74
+ /**
75
+ * NOT USD on a non-USD market despite the name: live 2026-10-19 SAMSUNG-KRW 1,234,418 =
76
+ * open_interest 5.58 × index 221,118 → KRW (as is target_volume). Nothing reads it; never wire it
77
+ * up as a USD figure. OI is reported in base units (VenueMarket.openInterest, MDS open_interests).
78
+ */
79
+ open_interest_usd?: string;
63
80
  }
64
81
  /** One instrument, as order entry needs it — from /refdata only. */
65
82
  export interface QfexInstrument {
@@ -92,15 +109,40 @@ export declare function toQfexInstrument(raw: unknown): QfexInstrument;
92
109
  /** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE. */
93
110
  export declare function qfexTradable(i: Pick<QfexInstrument, "status" | "quoteAsset">): boolean;
94
111
  /**
95
- * Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours
96
- * (weekday + local time in its `tz`). null = the venue published no usable schedule.
97
- * This is the underlying's regular session only — it is NOT used to infer whether funding
98
- * accrues (the schedule and the documented funding hours do not map 1:1, Q9).
112
+ * Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours.
113
+ * null = the venue published no usable schedule. This is the underlying's regular session only;
114
+ * funding hours are qfexFundingWindowClosed's question.
99
115
  */
100
116
  export declare function qfexUnderlyingOpen(marketHours: Record<string, unknown> | null, nowMs: number): boolean | null;
117
+ /**
118
+ * Was the funding window labelled `windowStartMs` outside the market's funding hours? Docs
119
+ * (docs.qfex.com/qfex/funding): "Funding is only active during a symbol's configured
120
+ * market_hours"; outside them "funding rate is 0 and no funding payment is exchanged".
121
+ *
122
+ * The window labelled T covers the hour BEFORE T (live 2026-10-19, GET /funding, 9 symbols over
123
+ * 10 days: AAPL-USD prints its last non-zero rate at Sat 00:00Z = Fri 20:00 ET and its first at
124
+ * Mon 01:00Z = Sun 21:00 ET, i.e. funding Sun 20:00–Fri 20:00 ET as documented, only when each
125
+ * label is read as the end of its hour).
126
+ *
127
+ * market_hours alone does not say whether an unlisted gap funds (US equities fund overnight
128
+ * between post_market and pre_market; Korean equities do not fund outside "open"), so a window is
129
+ * closed when the venue printed exactly 0 AND no part of the hour was in a funding session
130
+ * (anything but "weekend" / "close"). A non-zero rate is never marked closed, and a 0 inside a
131
+ * session stays a real 0. No usable schedule → never closed.
132
+ *
133
+ * Known limits (review B2-4 / B2-5):
134
+ * - market holidays are not in market_hours: a holiday inside a configured weekday session
135
+ * (SAMSUNG-KRW on Hangul Day) prints the venue's 0 as a real-looking "0", marketClosed false;
136
+ * - US equities fund in the weekday overnight gap (post_market → pre_market, docs "Sunday 20:00 –
137
+ * Friday 20:00 ET"), so a gap is never closed by schedule alone — only an exact 0 there is, and
138
+ * a genuine 0 rate in such a gap is (rarely) reported as closed.
139
+ */
140
+ export declare function qfexFundingWindowClosed(marketHours: Record<string, unknown> | null, windowStartMs: number, rate: number): boolean;
101
141
  export declare function qfexPublicClient(net: Network): QfexClient;
102
142
  /** Every instrument of /refdata (all statuses). The only input of order-entry validation. */
103
143
  export declare function fetchQfexInstruments(client: QfexClient): Promise<QfexInstrument[]>;
144
+ /** venue symbol → /md/contracts `index_price` (positive finite only). */
145
+ export declare function qfexIndexPrices(contracts: readonly Partial<QfexContractRow>[]): Map<string, string>;
104
146
  export declare function buildQfexMarkets(instruments: readonly QfexInstrument[], snap: {
105
147
  marks: ReadonlyMap<string, {
106
148
  price: string;
@@ -108,10 +150,21 @@ export declare function buildQfexMarkets(instruments: readonly QfexInstrument[],
108
150
  }>;
109
151
  openInterest: ReadonlyMap<string, string>;
110
152
  volumeNotional: ReadonlyMap<string, string>;
111
- }, nowMs: number): {
153
+ }, nowMs: number, indexPrices?: ReadonlyMap<string, string>): {
112
154
  markets: VenueMarket[];
113
155
  activeWithoutMark: string[];
114
156
  };
157
+ /**
158
+ * Priced markets, the /refdata instruments they were built from (status / quote asset per row) and
159
+ * the 24h notional per venue symbol in its QUOTE currency (KRW for SAMSUNG-KRW).
160
+ */
161
+ export declare function fetchQfexMarketsWithInstruments(net: Network): Promise<{
162
+ markets: VenueMarket[];
163
+ instruments: QfexInstrument[];
164
+ volumeNotional: ReadonlyMap<string, string>;
165
+ }>;
166
+ /** "active" = ACTIVE and USD-quoted; "nonUsdQuote" = ACTIVE, quoted in KRW / JPY / … (readable, never ordered). */
167
+ export declare function qfexListedStatus(i: Pick<QfexInstrument, "status" | "quoteAsset" | "venueSymbol">): "active" | "nonUsdQuote";
115
168
  export declare function buildQfexFundingRates(instruments: readonly QfexInstrument[], contracts: readonly QfexContractRow[], marks: ReadonlyMap<string, {
116
169
  price: string;
117
170
  time: number;
@@ -6,16 +6,25 @@
6
6
  * quote / margin asset, allowed order types + TIFs, market hours
7
7
  * MDS market_stats snapshot mark price (each entry with its own `time`), open interest,
8
8
  * 24h notional volume
9
- * GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads only)
9
+ * GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads);
10
+ * index_price (market rows, best effort)
10
11
  *
11
12
  * Coupling: order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
12
- * Markets need /refdata + the MDS snapshot. Only funding reads need /md/contracts — its failure
13
- * fails fetchFundingRates, never market listing or order entry.
13
+ * Markets need /refdata + the MDS snapshot; /md/contracts only adds indexPrice — its failure
14
+ * leaves indexPrice null (unknown) with a warning, never fails market listing or order entry.
15
+ * Funding reads need /md/contracts.
16
+ *
17
+ * indexPrice = /md/contracts `index_price` (docs contract-specifications: the Index Price is "the
18
+ * reference spot price for the asset"; the OpenAPI field has no description). Live 2026-10-19 it
19
+ * tracked the MDS `underlier` channel (AAPL 342.47 = 342.47, GOLD 4170.22 vs 4170.05, SAMSUNG-KRW
20
+ * 220092 vs 220082 KRW), moved on 122 of 222 markets within 45 s, and sat a median 0.015 % from
21
+ * the mark (max 5 %, CVNA pre-market). It is in the quote currency (`index_currency`), like the mark.
22
+ * /md/contracts lists ACTIVE markets only; a non-positive / missing value is null.
14
23
  *
15
24
  * Funding is UNVERIFIED (venue flag fundingVerified: false, Q4): the rate's unit is not
16
25
  * confirmed against settled payments and `funding_rate` vs `next_funding_rate` is undecided.
17
26
  * Markets therefore carry fundingRate null; fetchFundingRates returns the raw `funding_rate`
18
- * for the explicit, flagged reads only (`perp funding`, `arb scan --include-unverified`).
27
+ * for the explicit, flagged reads only (`arb scan --rates|--compare --include-unverified`).
19
28
  *
20
29
  * Symbols: "AAPL-USD" → CLI symbol "AAPL". Non-USD quotes keep the suffix ("SKHYNIX-KRW"),
21
30
  * have no canonical symbol and are NOT tradable through perp-cli (USD-assuming sizing / risk).
@@ -110,19 +119,17 @@ export function qfexTradable(i) {
110
119
  // ── Market hours → session ──
111
120
  const WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
112
121
  /**
113
- * Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours
114
- * (weekday + local time in its `tz`). null = the venue published no usable schedule.
115
- * This is the underlying's regular session only — it is NOT used to infer whether funding
116
- * accrues (the schedule and the documented funding hours do not map 1:1, Q9).
122
+ * Session names of the instrument's market_hours that contain `ms` (weekday + local time in its
123
+ * `tz`); [] = no window listed at that time. null = the venue published no usable schedule.
117
124
  */
118
- export function qfexUnderlyingOpen(marketHours, nowMs) {
125
+ function qfexSessionsAt(marketHours, ms) {
119
126
  if (marketHours === null || typeof marketHours.tz !== "string")
120
127
  return null;
121
128
  let parts;
122
129
  try {
123
130
  parts = new Intl.DateTimeFormat("en-US", {
124
131
  timeZone: marketHours.tz, weekday: "long", hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23",
125
- }).formatToParts(new Date(nowMs));
132
+ }).formatToParts(new Date(ms));
126
133
  }
127
134
  catch {
128
135
  return null; // an unknown IANA zone name is "no usable schedule", not a closed market
@@ -135,19 +142,70 @@ export function qfexUnderlyingOpen(marketHours, nowMs) {
135
142
  const now = `${h}:${m}:${s}`;
136
143
  const day = marketHours[weekday];
137
144
  if (day === undefined)
138
- return false; // no window listed for this weekday
145
+ return []; // no window listed for this weekday
139
146
  if (!Array.isArray(day))
140
147
  return null;
148
+ const out = [];
141
149
  for (const w of day) {
142
150
  if (w === null || typeof w !== "object")
143
151
  return null;
144
152
  const win = w;
145
153
  if (typeof win.session !== "string" || typeof win.open !== "string" || typeof win.close !== "string")
146
154
  return null;
147
- if (win.session === "open" && now >= win.open && now <= win.close)
148
- return true;
155
+ if (now >= win.open && now <= win.close)
156
+ out.push(win.session);
149
157
  }
150
- return false;
158
+ return out;
159
+ }
160
+ /**
161
+ * Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours.
162
+ * null = the venue published no usable schedule. This is the underlying's regular session only;
163
+ * funding hours are qfexFundingWindowClosed's question.
164
+ */
165
+ export function qfexUnderlyingOpen(marketHours, nowMs) {
166
+ const sessions = qfexSessionsAt(marketHours, nowMs);
167
+ return sessions === null ? null : sessions.includes("open");
168
+ }
169
+ /** market_hours session names during which no funding accrues. */
170
+ const QFEX_NON_FUNDING_SESSIONS = new Set(["weekend", "close"]);
171
+ /**
172
+ * Was the funding window labelled `windowStartMs` outside the market's funding hours? Docs
173
+ * (docs.qfex.com/qfex/funding): "Funding is only active during a symbol's configured
174
+ * market_hours"; outside them "funding rate is 0 and no funding payment is exchanged".
175
+ *
176
+ * The window labelled T covers the hour BEFORE T (live 2026-10-19, GET /funding, 9 symbols over
177
+ * 10 days: AAPL-USD prints its last non-zero rate at Sat 00:00Z = Fri 20:00 ET and its first at
178
+ * Mon 01:00Z = Sun 21:00 ET, i.e. funding Sun 20:00–Fri 20:00 ET as documented, only when each
179
+ * label is read as the end of its hour).
180
+ *
181
+ * market_hours alone does not say whether an unlisted gap funds (US equities fund overnight
182
+ * between post_market and pre_market; Korean equities do not fund outside "open"), so a window is
183
+ * closed when the venue printed exactly 0 AND no part of the hour was in a funding session
184
+ * (anything but "weekend" / "close"). A non-zero rate is never marked closed, and a 0 inside a
185
+ * session stays a real 0. No usable schedule → never closed.
186
+ *
187
+ * Known limits (review B2-4 / B2-5):
188
+ * - market holidays are not in market_hours: a holiday inside a configured weekday session
189
+ * (SAMSUNG-KRW on Hangul Day) prints the venue's 0 as a real-looking "0", marketClosed false;
190
+ * - US equities fund in the weekday overnight gap (post_market → pre_market, docs "Sunday 20:00 –
191
+ * Friday 20:00 ET"), so a gap is never closed by schedule alone — only an exact 0 there is, and
192
+ * a genuine 0 rate in such a gap is (rarely) reported as closed.
193
+ */
194
+ export function qfexFundingWindowClosed(marketHours, windowStartMs, rate) {
195
+ if (rate !== 0)
196
+ return false;
197
+ const from = windowStartMs - 3_600_000;
198
+ // Sampled inside the hour, never on its edges: market_hours closes are inclusive and often on
199
+ // the hour (GOLD-USD Fri close "17:00:00"), so a sample AT `from` would put the hour after the
200
+ // close inside the session (GOLD printed 0 for Fri 17:00–18:00 ET on 2026-10-09 and 10-16).
201
+ for (const t of [from + 1_000, from + 900_000, from + 1_800_000, from + 2_700_000, windowStartMs - 1_000]) {
202
+ const sessions = qfexSessionsAt(marketHours, t);
203
+ if (sessions === null)
204
+ return false;
205
+ if (sessions.some(x => !QFEX_NON_FUNDING_SESSIONS.has(x)))
206
+ return false;
207
+ }
208
+ return true;
151
209
  }
152
210
  function sessionOf(i, nowMs) {
153
211
  const open = qfexUnderlyingOpen(i.marketHours, nowMs);
@@ -173,10 +231,24 @@ function canonicalOf(i) {
173
231
  return null;
174
232
  return canonicalFor("qfex", i.symbol, i.assetClass);
175
233
  }
176
- export function buildQfexMarkets(instruments, snap, nowMs) {
234
+ /** venue symbol → /md/contracts `index_price` (positive finite only). */
235
+ export function qfexIndexPrices(contracts) {
236
+ const out = new Map();
237
+ for (const c of contracts) {
238
+ const v = c.index_price;
239
+ if (typeof c.ticker_id === "string" && typeof v === "string" && Number.isFinite(Number(v)) && Number(v) > 0)
240
+ out.set(c.ticker_id, v);
241
+ }
242
+ return out;
243
+ }
244
+ export function buildQfexMarkets(instruments, snap, nowMs, indexPrices = new Map()) {
177
245
  const markets = [];
178
246
  const activeWithoutMark = [];
179
247
  for (const i of instruments) {
248
+ // Listing policy: a DELISTED / INACTIVE market is not listed (per-symbol reads refuse it); only
249
+ // ACTIVE markets are, with tradable false when perp-cli cannot trade them (non-USD quote).
250
+ if (i.status !== "ACTIVE")
251
+ continue;
180
252
  const mark = snap.marks.get(i.venueSymbol);
181
253
  if (mark === undefined) {
182
254
  if (i.status === "ACTIVE")
@@ -194,12 +266,16 @@ export function buildQfexMarkets(instruments, snap, nowMs) {
194
266
  settle: i.marginAsset,
195
267
  quoteAsset: i.quoteAsset,
196
268
  markPrice: mark.price,
197
- indexPrice: null,
269
+ indexPrice: indexPrices.get(i.venueSymbol) ?? null,
198
270
  fundingRate: null, // unverified (Q4) — see module header
199
271
  fundingRateBasis: QFEX_FUNDING_BASIS,
200
272
  settlementIntervalHours: QFEX_SETTLEMENT_HOURS,
201
273
  openInterest: oi === undefined ? null : oi,
202
- volume24h: vol === undefined ? null : vol,
274
+ // daily_volumes_notional is in the QUOTE currency (live 2026-10-19: SAMSUNG-KRW 9,827,530 =
275
+ // /md/contracts target_volume 9,763,491 KRW ≈ base_volume 45.16 × ~216,000 KRW, and the
276
+ // 24 hourly candles' usdVolume sum to 9,821,767). VenueMarket.volume24h is USD, and the venue
277
+ // publishes no USD-KRW / JPY / CNY / SEK / HKD rate → null for a non-USD quote.
278
+ volume24h: vol === undefined || i.quoteAsset !== "USD" ? null : vol,
203
279
  tickSize: i.tickSize,
204
280
  stepSize: i.lotSize,
205
281
  minSize: i.minQuantity,
@@ -212,14 +288,40 @@ export function buildQfexMarkets(instruments, snap, nowMs) {
212
288
  }
213
289
  return { markets, activeWithoutMark };
214
290
  }
215
- async function fetchMarkets(net) {
216
- const instruments = await fetchQfexInstruments(qfexPublicClient(net));
291
+ /**
292
+ * Priced markets, the /refdata instruments they were built from (status / quote asset per row) and
293
+ * the 24h notional per venue symbol in its QUOTE currency (KRW for SAMSUNG-KRW).
294
+ */
295
+ export async function fetchQfexMarketsWithInstruments(net) {
296
+ const client = qfexPublicClient(net);
297
+ const instruments = await fetchQfexInstruments(client);
217
298
  const snap = await fetchQfexMarketStats(QFEX_ENDPOINTS[net].mdsWs, "*", QFEX_MDS_TIMEOUT_MS);
218
- const { markets, activeWithoutMark } = buildQfexMarkets(instruments, snap, Date.now());
299
+ let indexPrices = new Map();
300
+ try {
301
+ const body = await client.publicGet("/md/contracts", null);
302
+ if (!Array.isArray(body.data))
303
+ throw new Error("response has no data list");
304
+ indexPrices = qfexIndexPrices(body.data);
305
+ }
306
+ catch (e) {
307
+ console.warn(`[qfex] index prices unavailable (/md/contracts: ${e instanceof Error ? e.message : String(e)}); indexPrice left null`);
308
+ }
309
+ const { markets, activeWithoutMark } = buildQfexMarkets(instruments, snap, Date.now(), indexPrices);
219
310
  if (activeWithoutMark.length > 0) {
220
311
  console.warn(`[qfex] excluded ${activeWithoutMark.length} active market(s) without a mark price: ${activeWithoutMark.join(", ")}`);
221
312
  }
222
- return markets;
313
+ return { markets, instruments, volumeNotional: snap.volumeNotional };
314
+ }
315
+ /** "active" = ACTIVE and USD-quoted; "nonUsdQuote" = ACTIVE, quoted in KRW / JPY / … (readable, never ordered). */
316
+ export function qfexListedStatus(i) {
317
+ if (i.status !== "ACTIVE")
318
+ throw new PerpError("EXCHANGE_ERROR", `QFEX ${i.venueSymbol} is ${i.status}; it is not listed`, { exchange: "qfex" });
319
+ return qfexTradable(i) ? "active" : "nonUsdQuote";
320
+ }
321
+ async function fetchMarkets(net) {
322
+ const { markets, instruments } = await fetchQfexMarketsWithInstruments(net);
323
+ const bySymbol = new Map(instruments.map(i => [i.venueSymbol, i]));
324
+ return markets.map(m => { const i = bySymbol.get(m.venueSymbol); return i === undefined ? m : { ...m, status: qfexListedStatus(i) }; });
223
325
  }
224
326
  export function buildQfexFundingRates(instruments, contracts, marks, nowMs) {
225
327
  const byVenueSymbol = new Map(instruments.map(i => [i.venueSymbol, i]));
@@ -238,12 +340,16 @@ export function buildQfexFundingRates(instruments, contracts, marks, nowMs) {
238
340
  }
239
341
  const mark = marks.get(c.ticker_id);
240
342
  const nextMs = typeof c.next_funding_rate_timestamp === "number" ? c.next_funding_rate_timestamp * 1000 : null;
343
+ // `funding_rate` is the implied rate of the window in progress, labelled by its END (the next
344
+ // top of the hour, = next_funding_rate_timestamp live) — the same window rule as history.
345
+ const marketClosed = qfexFundingWindowClosed(i.marketHours, Math.floor(nowMs / 3_600_000) * 3_600_000 + 3_600_000, Number(c.funding_rate));
241
346
  rates.push({
242
347
  venue: "qfex",
243
348
  venueSymbol: c.ticker_id,
244
349
  symbol: i.symbol,
245
350
  canonical: canonicalOf(i),
246
- rate: c.funding_rate,
351
+ rate: marketClosed ? null : c.funding_rate,
352
+ marketClosed,
247
353
  basis: QFEX_FUNDING_BASIS,
248
354
  markPrice: mark === undefined ? null : mark.price,
249
355
  // A timestamp in the past is not a "next" funding time (seen on closed markets).
@@ -277,7 +277,7 @@ export interface QfexMdsSnapshot {
277
277
  }>;
278
278
  /** Venue symbol → open interest (base units). */
279
279
  openInterest: Map<string, string>;
280
- /** Venue symbol → 24h volume, quote notional. */
280
+ /** Venue symbol → 24h volume, notional in the market's QUOTE currency (KRW for SAMSUNG-KRW — not USD). */
281
281
  volumeNotional: Map<string, string>;
282
282
  frame: QfexFrame;
283
283
  }
@@ -20,7 +20,7 @@
20
20
  import { PerpError } from "../../errors.js";
21
21
  import type { RisexAgentMeta } from "../../settings.js";
22
22
  import type { EvmSigner } from "../../signer/interface.js";
23
- import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade } from "../interface.js";
23
+ import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade, FundingHistoryRow } from "../interface.js";
24
24
  import type { ResolvedSlippage } from "../slippage.js";
25
25
  import { type TradingV2 } from "../trading-v2.js";
26
26
  import type { VenueCreateContext, VenueDefinition } from "../venue.js";
@@ -92,16 +92,19 @@ export declare class RisexAdapter implements ExchangeAdapter, TradingV2 {
92
92
  postOnlyMarket: boolean;
93
93
  tradable: boolean;
94
94
  }>>;
95
- getOrderbook(symbol: string): Promise<{
95
+ /** `depth` levels per side (API max 250, developer.rise.trade; was always 50). */
96
+ getOrderbook(symbol: string, depth?: number): Promise<{
96
97
  bids: [string, string][];
97
98
  asks: [string, string][];
98
99
  }>;
99
100
  getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
100
- getFundingHistory(symbol: string, limit?: number): Promise<{
101
- time: number;
102
- rate: string;
103
- price: string | null;
104
- }[]>;
101
+ /**
102
+ * Settled hourly prints, newest first. The API defaults `start_time` to now − 7 d (168 rows, any
103
+ * `limit`), so the window is sent explicitly (`limit` hours back, one spare) and pages are
104
+ * followed with `page` / `has_next_page` (max 1000 rows per page; developer.rise.trade
105
+ * getfundingratehistory; verified live 2026-10-19).
106
+ */
107
+ getFundingHistory(symbol: string, limit?: number): Promise<FundingHistoryRow[]>;
105
108
  getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
106
109
  private portfolio;
107
110
  getBalance(): Promise<ExchangeBalance>;
@@ -145,4 +148,13 @@ export declare class RisexAdapter implements ExchangeAdapter, TradingV2 {
145
148
  automationGate(): PerpError;
146
149
  }
147
150
  /** Candle intervals → ms (the venue takes the interval in ns). */
151
+ /**
152
+ * Candle intervals verified live (2026-10-19, market 1): each returns rows labelled with that
153
+ * interval. 3m / 30m / 2h / 8h / 12h / 3d are answered with 1m bars (no error), so they are not
154
+ * listed — getKlines refuses them with INVALID_PARAMS instead of returning 1m candles.
155
+ */
156
+ export declare const RISEX_BOOK_MAX = 250;
157
+ export declare const RISEX_TRADES_CAP = 1000;
158
+ /** funding-rate-history: max rows per page (docs) and pages followed before giving up. */
159
+ export declare const RISEX_FUNDING_PAGE_MAX = 1000;
148
160
  export declare const KLINE_INTERVAL_MS: Readonly<Record<string, number>>;
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import { PerpError, extractErrorMessage } from "../../errors.js";
21
21
  import { quarantineSymbol } from "../../bot/quarantine.js";
22
+ import { assertCandleInterval } from "../interface.js";
22
23
  import { alignProtectionPrice, exactUnits, mulDecimal, slippagePrice, subDecimal } from "../precision.js";
23
24
  import { newVenueClientOrderId, unsupported, validateLegacyTif, validateOrderRequest, } from "../trading-v2.js";
24
25
  import { networkConfig, requireCapabilities, roleUnsupported } from "../venue.js";
@@ -250,9 +251,13 @@ export class RisexAdapter {
250
251
  return { ...toLegacyMarketInfo(m), marketId: m.venueSymbol, reduceOnlyMarket: r.reduce_only, postOnlyMarket: r.post_only, tradable: m.tradable };
251
252
  });
252
253
  }
253
- async getOrderbook(symbol) {
254
+ /** `depth` levels per side (API max 250, developer.rise.trade; was always 50). */
255
+ async getOrderbook(symbol, depth = 50) {
256
+ if (!Number.isInteger(depth) || depth < 1 || depth > RISEX_BOOK_MAX) {
257
+ throw new PerpError("INVALID_PARAMS", `RISEx: book depth must be 1..${RISEX_BOOK_MAX} (got ${depth})`);
258
+ }
254
259
  const m = await this.market(symbol);
255
- const d = await this.client.get("/v1/orderbook", { market_id: m.market_id, limit: 50 });
260
+ const d = await this.client.get("/v1/orderbook", { market_id: m.market_id, limit: depth });
256
261
  const side = (rows, what) => {
257
262
  if (!Array.isArray(rows))
258
263
  throw new PerpError("EXCHANGE_ERROR", `RISEx /v1/orderbook: no ${what}`, { exchange: "risex" });
@@ -261,8 +266,12 @@ export class RisexAdapter {
261
266
  return { bids: side(d.bids, "bids"), asks: side(d.asks, "asks") };
262
267
  }
263
268
  async getRecentTrades(symbol, limit = 50) {
269
+ // Venue cap (live 2026-10-19: "page_size cannot exceed 1000"); refused, never clamped.
270
+ if (!Number.isInteger(limit) || limit < 1 || limit > RISEX_TRADES_CAP) {
271
+ throw new PerpError("INVALID_PARAMS", `RISEx: trades limit must be 1..${RISEX_TRADES_CAP} (got ${limit})`, { remediation: `RISEx serves at most ${RISEX_TRADES_CAP} trades per request` });
272
+ }
264
273
  const m = await this.market(symbol);
265
- const d = await this.client.get(`/v1/markets/id/${m.market_id}/trade-history`, { limit: Math.min(limit, 1000) });
274
+ const d = await this.client.get(`/v1/markets/id/${m.market_id}/trade-history`, { limit });
266
275
  if (!Array.isArray(d.trades))
267
276
  throw new PerpError("EXCHANGE_ERROR", "RISEx trade-history: no trades array", { exchange: "risex" });
268
277
  const sym = risexSymbolOf(m);
@@ -271,19 +280,38 @@ export class RisexAdapter {
271
280
  if (x.maker_side !== "BUY" && x.maker_side !== "SELL")
272
281
  throw new PerpError("EXCHANGE_ERROR", `RISEx trade maker_side ${String(x.maker_side)}`, { exchange: "risex" });
273
282
  // The tape side is the taker's: a SELL maker means a buy took it.
274
- // Public tape carries no fee; "0" follows the legacy ExchangeTrade convention for public trades.
283
+ // Public tape: no fee applies to the viewer → null, never "0".
275
284
  return { time: nsToMs(str(x.time, "trade time")), symbol: sym, side: x.maker_side === "SELL" ? "buy" : "sell", price: decReq(x.price, "trade price"), size: decReq(x.size, "trade size"), fee: null };
276
285
  });
277
286
  }
287
+ /**
288
+ * Settled hourly prints, newest first. The API defaults `start_time` to now − 7 d (168 rows, any
289
+ * `limit`), so the window is sent explicitly (`limit` hours back, one spare) and pages are
290
+ * followed with `page` / `has_next_page` (max 1000 rows per page; developer.rise.trade
291
+ * getfundingratehistory; verified live 2026-10-19).
292
+ */
278
293
  async getFundingHistory(symbol, limit = 100) {
294
+ if (!Number.isInteger(limit) || limit < 1)
295
+ throw new PerpError("INVALID_PARAMS", `RISEx: funding history limit must be a positive integer (got ${limit})`);
279
296
  const m = await this.market(symbol);
280
- const d = await this.client.get(`/v1/markets/id/${m.market_id}/funding-rate-history`, { limit: Math.min(limit, 1000) });
281
- if (!Array.isArray(d.records))
282
- throw new PerpError("EXCHANGE_ERROR", "RISEx funding-rate-history: no records", { exchange: "risex" });
283
- return d.records.map(r => {
284
- const x = r;
285
- return { time: nsToMs(str(x.end_time, "funding end_time")), rate: decReq(x.funding_rate, "funding_rate"), price: positiveOrNull(x.index_price) };
286
- });
297
+ const startNs = (BigInt(Date.now() - (limit + 1) * 3_600_000) * 1000000n).toString();
298
+ const perPage = Math.min(limit, RISEX_FUNDING_PAGE_MAX);
299
+ const out = [];
300
+ for (let page = 1; out.length < limit; page++) {
301
+ if (page > RISEX_FUNDING_MAX_PAGES)
302
+ throw new PerpError("EXCHANGE_ERROR", `RISEx funding-rate-history: still paging after ${RISEX_FUNDING_MAX_PAGES} pages`, { exchange: "risex" });
303
+ const d = await this.client.get(`/v1/markets/id/${m.market_id}/funding-rate-history`, { limit: perPage, page, start_time: startNs });
304
+ if (!Array.isArray(d.records))
305
+ throw new PerpError("EXCHANGE_ERROR", "RISEx funding-rate-history: no records", { exchange: "risex" });
306
+ for (const r of d.records) {
307
+ const x = r;
308
+ out.push({ time: nsToMs(str(x.end_time, "funding end_time")), rate: decReq(x.funding_rate, "funding_rate"), price: positiveOrNull(x.index_price), intervalHours: 1, rateKind: "lastSettled" });
309
+ }
310
+ if (d.has_next_page !== true || d.records.length === 0)
311
+ break;
312
+ }
313
+ const seen = new Set();
314
+ return out.filter(r => (seen.has(r.time) ? false : (seen.add(r.time), true))).sort((a, b) => b.time - a.time).slice(0, limit);
287
315
  }
288
316
  async getKlines(symbol, interval, startTime, endTime) {
289
317
  const ms = KLINE_INTERVAL_MS[interval];
@@ -300,6 +328,14 @@ export class RisexAdapter {
300
328
  throw new PerpError("EXCHANGE_ERROR", "RISEx trading-view-data: not an array", { exchange: "risex" });
301
329
  return d.map(c => {
302
330
  const x = c;
331
+ // The venue answers an interval it does not serve with 1m bars: a row of another interval
332
+ // is never returned as the requested one.
333
+ // OHLC are mapped by field name, unscaled. RISEx opens a bar at the PREVIOUS bar's close
334
+ // while high / low are the bar's own trades, so `open` can lie outside [low, high] (live
335
+ // 2026-10-19: 2555 of 7187 1m bars, 5 of 121 1h bars; daily bars none). That is the venue's
336
+ // data, passed through as published — never clamped or "fixed" (documented in the CHANGELOG).
337
+ // Required on every row here (the field is always sent; null/absent would skip the check).
338
+ assertCandleInterval("risex", interval, x.interval === undefined ? null : x.interval, `Use one of: ${Object.keys(KLINE_INTERVAL_MS).join(", ")}`);
303
339
  return {
304
340
  time: nsToMs(str(x.time, "candle time")),
305
341
  open: decReq(x.open, "open"), high: decReq(x.high, "high"), low: decReq(x.low, "low"), close: decReq(x.close, "close"),
@@ -791,6 +827,16 @@ function historyStatus(o) {
791
827
  }
792
828
  }
793
829
  /** Candle intervals → ms (the venue takes the interval in ns). */
830
+ /**
831
+ * Candle intervals verified live (2026-10-19, market 1): each returns rows labelled with that
832
+ * interval. 3m / 30m / 2h / 8h / 12h / 3d are answered with 1m bars (no error), so they are not
833
+ * listed — getKlines refuses them with INVALID_PARAMS instead of returning 1m candles.
834
+ */
835
+ export const RISEX_BOOK_MAX = 250;
836
+ export const RISEX_TRADES_CAP = 1000;
837
+ /** funding-rate-history: max rows per page (docs) and pages followed before giving up. */
838
+ export const RISEX_FUNDING_PAGE_MAX = 1000;
839
+ const RISEX_FUNDING_MAX_PAGES = 20;
794
840
  export const KLINE_INTERVAL_MS = {
795
- "1m": 60_000, "5m": 300_000, "15m": 900_000, "30m": 1_800_000, "1h": 3_600_000, "4h": 14_400_000, "1d": 86_400_000,
841
+ "1m": 60_000, "5m": 300_000, "15m": 900_000, "1h": 3_600_000, "4h": 14_400_000, "1d": 86_400_000, "1w": 604_800_000,
796
842
  };
@@ -427,5 +427,5 @@ export function matchSpotPerp(spotVenue, spotMarkets, perpVenue, perpMarkets, wa
427
427
  }
428
428
  /** The perp venue's public markets (canonical + asset class + mark) on `net` (default: process network). */
429
429
  export function fetchPerpMarketsForMatching(perpVenue, net = sharedApiNetwork()) {
430
- return getVenue(perpVenue).publicData.fetchMarkets(net);
430
+ return getVenue(perpVenue).publicData.fetchMarkets(net, { funding: false });
431
431
  }
@@ -5,11 +5,22 @@
5
5
  * readable (the unofficial API refuses it) and every account / trading method refuses with
6
6
  * CAPABILITY_UNSUPPORTED before any request — there is no TxFlow account or order code.
7
7
  */
8
- import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade } from "../interface.js";
8
+ import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade, FundingHistoryRow } from "../interface.js";
9
9
  import { type VenueDefinition } from "../venue.js";
10
10
  import { type Network, type VenueMarket } from "../venue-types.js";
11
- /** Candle intervals the TxFlow app requests (HL-style). */
11
+ /**
12
+ * Candle intervals that return bars (verified live 2026-10-19, coin 1). 3d / 1w / 1M are
13
+ * accepted by the API but answer [] for every window, so they are not offered.
14
+ */
15
+ export declare const TXFLOW_KLINE_MS: Readonly<Record<string, number>>;
12
16
  export declare const TXFLOW_KLINE_INTERVALS: ReadonlySet<string>;
17
+ /**
18
+ * candleSnapshot answers `200 []` (no error) when the window spans more than ~3000 bars
19
+ * (measured 2026-10-19: 3000 bars OK, 3050 → [] at 1h; same at 1m / 5m / 1d), so ranges are
20
+ * read in windows of at most 2900 bars and merged.
21
+ */
22
+ export declare const TXFLOW_KLINE_WINDOW_BARS = 2900;
23
+ export declare const TXFLOW_KLINE_MAX_WINDOWS = 50;
13
24
  /** Legacy market row plus the labels every TxFlow row carries in JSON output. */
14
25
  export type TxflowMarketInfo = ExchangeMarketInfo & {
15
26
  venueApi: "unofficial";
@@ -31,11 +42,7 @@ export declare class TxflowPublicAdapter implements ExchangeAdapter {
31
42
  asks: [string, string][];
32
43
  }>;
33
44
  getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
34
- getFundingHistory(): Promise<{
35
- time: number;
36
- rate: string;
37
- price: string | null;
38
- }[]>;
45
+ getFundingHistory(): Promise<FundingHistoryRow[]>;
39
46
  getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
40
47
  private refuse;
41
48
  getBalance(): Promise<ExchangeBalance>;