@metaflux/fluxaction 0.1.3 → 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.
@@ -1,11 +1,28 @@
1
1
  export type LogicalOrderKind = 'take-profit' | 'stop-loss' | 'trailing-stop'
2
+ export type MarketScope = 'spot' | 'futures'
3
+ export type FuturesPositionSide = 'long' | 'short'
4
+
5
+ export type ExitMode = 'all' // MVP: закрываем всю позицию по symbol
2
6
 
3
7
  export interface LogicalOrderBase {
4
8
  id: string
5
9
  exchangeId: string
10
+ market: MarketScope
6
11
  symbol: string
7
- side: 'buy' | 'sell'
8
- qty: number
12
+
13
+ kind: LogicalOrderKind
14
+
15
+ // trigger side semantics:
16
+ // - spot: side 'sell' обычно означает выход из long spot позиции
17
+ // - futures: мы используем positionSide + exitMode
18
+ side?: 'buy' | 'sell'
19
+
20
+ // spot
21
+ qty?: number // base qty
22
+
23
+ // futures
24
+ positionSide?: FuturesPositionSide
25
+ exitMode?: ExitMode
9
26
  }
10
27
 
11
28
  export interface TakeProfitOrder extends LogicalOrderBase {
@@ -20,12 +37,9 @@ export interface StopLossOrder extends LogicalOrderBase {
20
37
 
21
38
  export interface TrailingStopOrder extends LogicalOrderBase {
22
39
  kind: 'trailing-stop'
23
- callbackRate: number
40
+ callbackRate: number
24
41
  activationPrice?: number
25
42
  highWatermark?: number
26
43
  }
27
44
 
28
- export type LogicalOrder =
29
- | TakeProfitOrder
30
- | StopLossOrder
31
- | TrailingStopOrder
45
+ export type LogicalOrder = TakeProfitOrder | StopLossOrder | TrailingStopOrder
@@ -0,0 +1,7 @@
1
+ export type OrderKind = 'market' | 'limit'
2
+
3
+ export type MarketOrLimit =
4
+ | { kind: 'market' }
5
+ | { kind: 'limit'; price: number }
6
+
7
+ export type SpotSide = 'buy' | 'sell'
@@ -0,0 +1,301 @@
1
+ import ccxt, { type gateio as GateCcxt } from 'ccxt'
2
+ import type {
3
+ ExchangeAdapter,
4
+ ExchangeCapabilities,
5
+ MarketInfo,
6
+ OrderInfo,
7
+ PositionInfo,
8
+ PositionSide,
9
+ } from '../../../core/exchange'
10
+ import { mapCcxtOrder } from '../spot/gate.mapOrder'
11
+
12
+ export interface GateFuturesAdapterConfig {
13
+ id: string
14
+ label?: string
15
+ apiKey: string
16
+ secret: string
17
+ settle?: 'usdt' | 'btc'
18
+ defaultLeverage?: number
19
+ }
20
+
21
+ export class GateFuturesAdapter implements ExchangeAdapter {
22
+ readonly id: string
23
+ readonly label: string
24
+ readonly capabilities: ExchangeCapabilities
25
+
26
+ private ex: GateCcxt
27
+ private settle: 'usdt' | 'btc'
28
+ private defaultLeverage?: number
29
+
30
+ constructor(cfg: GateFuturesAdapterConfig) {
31
+ this.id = cfg.id
32
+ this.label = cfg.label ?? 'gate-futures'
33
+ this.settle = cfg.settle ?? 'usdt'
34
+ this.defaultLeverage = cfg.defaultLeverage
35
+
36
+ this.ex = new ccxt.gateio({
37
+ apiKey: cfg.apiKey,
38
+ secret: cfg.secret,
39
+ enableRateLimit: true,
40
+ options: { defaultType: 'swap' },
41
+ }) as GateCcxt
42
+
43
+ this.capabilities = {
44
+ spot: { nativeTakeProfit: false, nativeStopLoss: false, nativeTrailingStop: false },
45
+ futures: { nativeTakeProfit: false, nativeStopLoss: false, nativeTrailingStop: false },
46
+ }
47
+ }
48
+
49
+ async loadMarkets(): Promise<void> {
50
+ await this.ex.loadMarkets()
51
+ }
52
+
53
+ hasSpotMarket(_symbol: string): boolean {
54
+ return false
55
+ }
56
+
57
+ hasFuturesMarket(symbol: string): boolean {
58
+ const m: any = (this.ex as any).markets?.[symbol]
59
+ return !!m && (m.type === 'swap' || m.swap === true)
60
+ }
61
+
62
+ getMarket(symbol: string): MarketInfo | undefined {
63
+ const m: any = (this.ex as any).markets?.[symbol]
64
+ if (!m) return undefined
65
+ return {
66
+ symbol: m.symbol,
67
+ base: m.base,
68
+ quote: m.quote,
69
+ type: 'futures',
70
+ precisionAmount: m.precision?.amount,
71
+ precisionPrice: m.precision?.price,
72
+ minCost: m.limits?.cost?.min,
73
+ minAmount: m.limits?.amount?.min,
74
+ }
75
+ }
76
+
77
+ private roundAmount(symbol: string, amount: number): number {
78
+ const m: any = (this.ex as any).markets?.[symbol]
79
+ if (!m) return amount
80
+ return Number((this.ex as any).amountToPrecision(symbol, amount))
81
+ }
82
+
83
+ private roundPrice(symbol: string, price: number): number {
84
+ const m: any = (this.ex as any).markets?.[symbol]
85
+ if (!m) return price
86
+ return Number((this.ex as any).priceToPrecision(symbol, price))
87
+ }
88
+
89
+ async fetchTicker(symbol: string): Promise<{ last: number }> {
90
+ const t: any = await (this.ex as any).fetchTicker(symbol)
91
+ return { last: Number(t?.last ?? 0) }
92
+ }
93
+
94
+ async fetchBalance(): Promise<Record<string, { free: number; used: number; total: number }>> {
95
+ const bal: any = await (this.ex as any).fetchBalance()
96
+ return bal as any
97
+ }
98
+
99
+ async fetchOpenOrders(symbol?: string): Promise<OrderInfo[]> {
100
+ const orders: any[] = await (this.ex as any).fetchOpenOrders(symbol)
101
+ return orders.map((o: any) => mapCcxtOrder(o.symbol, o))
102
+ }
103
+
104
+ async cancelOrder(symbol: string, orderId: string): Promise<void> {
105
+ await (this.ex as any).cancelOrder(orderId, symbol)
106
+ }
107
+
108
+ // ===== helpers =====
109
+ private getMarketRaw(symbol: string): any {
110
+ const m: any = (this.ex as any).markets?.[symbol]
111
+ if (!m) throw new Error(`gate-futures: unknown market ${symbol}`)
112
+ return m
113
+ }
114
+
115
+ private getContractSize(symbol: string): number {
116
+ const m = this.getMarketRaw(symbol)
117
+ const cs = Number(m.contractSize ?? 1)
118
+ return cs > 0 ? cs : 1
119
+ }
120
+
121
+ /**
122
+ * Конвертирует USDT стоимость в дробное количество контрактов
123
+ * Gate API поддерживает дробные размеры через X-Gate-Size-Decimal: 1
124
+ */
125
+ private costUSDTToContracts(symbol: string, costUSDT: number, price: number): number {
126
+ const contractSize = this.getContractSize(symbol)
127
+ const notionalPerContract = price * contractSize
128
+
129
+ // Дробное количество контрактов (может быть 0.17, 1.71 и т.д.)
130
+ const contractsFloat = costUSDT / notionalPerContract
131
+
132
+ if (contractsFloat < 0.0001) {
133
+ throw new Error(
134
+ `gate-futures: costUSDT too small. ` +
135
+ `Minimum notional per contract: ${notionalPerContract.toFixed(2)} USDT, got ${costUSDT}`,
136
+ )
137
+ }
138
+
139
+ return contractsFloat
140
+ }
141
+
142
+ async quoteToBaseQty(params: { symbol: string; quoteCost: number; price?: number }): Promise<number> {
143
+ const { symbol, quoteCost } = params
144
+ if (!quoteCost || quoteCost <= 0) throw new Error(`gate-futures quoteCost must be > 0, got ${quoteCost}`)
145
+
146
+ const p = params.price ?? (await this.fetchTicker(symbol)).last
147
+ if (!p || p <= 0) throw new Error(`gate-futures invalid price for ${symbol}: ${p}`)
148
+
149
+ // Возвращаем дробное количество контрактов
150
+ const contracts = this.costUSDTToContracts(symbol, quoteCost, p)
151
+ return contracts
152
+ }
153
+
154
+ async baseQtyToQuote(params: { symbol: string; baseQty: number; price?: number }): Promise<number> {
155
+ // baseQty здесь = contracts (может быть дробное)
156
+ if (!params.baseQty || params.baseQty <= 0) return 0
157
+
158
+ const p = params.price ?? (await this.fetchTicker(params.symbol)).last
159
+ if (!p || p <= 0) throw new Error(`gate-futures invalid price for ${params.symbol}: ${p}`)
160
+
161
+ const contractSize = this.getContractSize(params.symbol)
162
+ // contracts * contractSize * price = notional in USDT
163
+ return params.baseQty * contractSize * p
164
+ }
165
+
166
+ // ===== spot methods not supported =====
167
+ async marketBuySpot(): Promise<OrderInfo> {
168
+ throw new Error('gate-futures: spot not supported')
169
+ }
170
+ async marketSellSpot(): Promise<OrderInfo> {
171
+ throw new Error('gate-futures: spot not supported')
172
+ }
173
+ async limitOrderSpot(): Promise<OrderInfo> {
174
+ throw new Error('gate-futures: spot not supported')
175
+ }
176
+
177
+ // ===== futures =====
178
+ async openFutures(params: {
179
+ symbol: string
180
+ side: PositionSide
181
+ costUSDT: number
182
+ leverage?: number
183
+ price?: number | null
184
+ }): Promise<OrderInfo> {
185
+ const symbol = params.symbol
186
+
187
+ // Set leverage
188
+ const leverage = params.leverage ?? this.defaultLeverage
189
+ if (leverage) {
190
+ try {
191
+ await (this.ex as any).setLeverage(leverage, symbol)
192
+ } catch {
193
+ // ignore best-effort
194
+ }
195
+ }
196
+
197
+ // Конвертируем costUSDT -> contracts (дробное)
198
+ const last = (await this.fetchTicker(symbol)).last
199
+ const contracts = await this.quoteToBaseQty({ symbol, quoteCost: params.costUSDT, price: last })
200
+ const actualCostUSDT = await this.baseQtyToQuote({ symbol, baseQty: contracts, price: last })
201
+
202
+ console.log(
203
+ `[${this.id}] openFutures: ` +
204
+ `requested ${params.costUSDT} USDT -> ` +
205
+ `calculated ${contracts.toFixed(8)} contracts -> ` +
206
+ `notional ${actualCostUSDT.toFixed(2)} USDT`,
207
+ )
208
+
209
+ const side = params.side === 'long' ? 'buy' : 'sell'
210
+ const type: 'market' | 'limit' = typeof params.price === 'number' ? 'limit' : 'market'
211
+ const price = typeof params.price === 'number' ? this.roundPrice(symbol, params.price) : undefined
212
+
213
+ // DEBUG: выводим то, что передаём в createOrder
214
+ console.log(`[${this.id}] createOrder args: symbol=${symbol}, type=${type}, side=${side}, amount=${contracts}, price=${price}`)
215
+
216
+ // Создаём ордер с дробным количеством контрактов
217
+ const order: any = await (this.ex as any).createOrder(symbol, type, side, contracts, price, {
218
+ settle: this.settle,
219
+ })
220
+
221
+ console.log(`[${this.id}] createOrder response: id=${order.id}, filled=${order.filled}, cost=${order.cost}, status=${order.status}`)
222
+
223
+ // fetchOrder чтобы получить точные filled values
224
+ const filledOrder: any = await (this.ex as any).fetchOrder(order.id, symbol).catch(() => order)
225
+
226
+ console.log(
227
+ `[${this.id}] fetchOrder response: id=${filledOrder.id}, filled=${filledOrder.filled}, cost=${filledOrder.cost}, average=${filledOrder.average}`,
228
+ )
229
+
230
+ return mapCcxtOrder(symbol, filledOrder)
231
+ }
232
+
233
+ async closeFutures(params: {
234
+ symbol: string
235
+ side: PositionSide
236
+ closeMode: 'all' | 'cost'
237
+ costUSDT?: number
238
+ }): Promise<OrderInfo> {
239
+ const symbol = params.symbol
240
+ const closeSide = params.side === 'long' ? 'sell' : 'buy'
241
+
242
+ if (params.closeMode === 'cost') {
243
+ if (!params.costUSDT) throw new Error('gate-futures closeMode=cost requires costUSDT')
244
+
245
+ const last = (await this.fetchTicker(symbol)).last
246
+ const contracts = await this.quoteToBaseQty({
247
+ symbol,
248
+ quoteCost: params.costUSDT,
249
+ price: last,
250
+ })
251
+
252
+ const order: any = await (this.ex as any).createOrder(symbol, 'market', closeSide, contracts, undefined, {
253
+ settle: this.settle,
254
+ reduceOnly: true,
255
+ })
256
+
257
+ const filledOrder: any = await (this.ex as any).fetchOrder(order.id, symbol).catch(() => order)
258
+ return mapCcxtOrder(symbol, filledOrder)
259
+ }
260
+
261
+ // closeMode === 'all': close entire position
262
+ const positions: any[] = await (this.ex as any).fetchPositions?.([symbol]).catch(() => []) ?? []
263
+ const p0: any = positions.find((p) => p?.symbol === symbol) ?? positions[0]
264
+ const rawSize = Math.abs(Number(p0?.contracts ?? p0?.size ?? p0?.positionAmt ?? 0))
265
+
266
+ if (!rawSize || rawSize <= 0) {
267
+ throw new Error(`gate-futures closeMode=all: no open position for ${symbol}`)
268
+ }
269
+
270
+ const order: any = await (this.ex as any).createOrder(symbol, 'market', closeSide, rawSize, undefined, {
271
+ settle: this.settle,
272
+ reduceOnly: true,
273
+ })
274
+
275
+ const filledOrder: any = await (this.ex as any).fetchOrder(order.id, symbol).catch(() => order)
276
+ return mapCcxtOrder(symbol, filledOrder)
277
+ }
278
+
279
+ async fetchPositions(symbol?: string): Promise<PositionInfo[]> {
280
+ const positions: any[] = await (this.ex as any).fetchPositions?.(symbol ? [symbol] : undefined).catch(() => []) ?? []
281
+ if (!Array.isArray(positions)) return []
282
+
283
+ return positions.map((p: any) => {
284
+ const rawSize = Number(p?.contracts ?? p?.size ?? p?.positionAmt ?? 0)
285
+ const side: PositionSide =
286
+ p?.side === 'long' || p?.side === 'short'
287
+ ? p.side
288
+ : rawSize >= 0
289
+ ? 'long'
290
+ : 'short'
291
+
292
+ return {
293
+ symbol: p?.symbol ?? symbol ?? '',
294
+ side,
295
+ size: Math.abs(rawSize),
296
+ entryPrice: Number(p?.entryPrice ?? p?.entry ?? 0),
297
+ raw: p,
298
+ }
299
+ })
300
+ }
301
+ }
@@ -0,0 +1,2 @@
1
+ export { GateSpotAdapter, type GateSpotAdapterConfig } from './spot/GateSpotAdapter'
2
+ export { GateFuturesAdapter, type GateFuturesAdapterConfig } from './futures/GateFuturesAdapter'
@@ -0,0 +1,208 @@
1
+ import ccxt, { type gateio as GateCcxt } from 'ccxt'
2
+ import type {
3
+ ExchangeAdapter,
4
+ ExchangeCapabilities,
5
+ MarketInfo,
6
+ OrderInfo,
7
+ PositionInfo,
8
+ } from '../../../core/exchange'
9
+ import { mapCcxtOrder } from './gate.mapOrder'
10
+
11
+ export interface GateSpotAdapterConfig {
12
+ id: string
13
+ label?: string
14
+ apiKey: string
15
+ secret: string
16
+ }
17
+
18
+ export class GateSpotAdapter implements ExchangeAdapter {
19
+ readonly id: string
20
+ readonly label: string
21
+ readonly capabilities: ExchangeCapabilities
22
+ private ex: GateCcxt
23
+
24
+ constructor(cfg: GateSpotAdapterConfig) {
25
+ this.id = cfg.id
26
+ this.label = cfg.label ?? 'gate-spot'
27
+
28
+ this.ex = new ccxt.gateio({
29
+ apiKey: cfg.apiKey,
30
+ secret: cfg.secret,
31
+ enableRateLimit: true,
32
+ options: {
33
+ defaultType: 'spot',
34
+ createMarketBuyOrderRequiresPrice: false,
35
+ },
36
+ }) as GateCcxt
37
+
38
+ this.capabilities = {
39
+ spot: { nativeTakeProfit: false, nativeStopLoss: false, nativeTrailingStop: false },
40
+ futures: { nativeTakeProfit: false, nativeStopLoss: false, nativeTrailingStop: false },
41
+ }
42
+ }
43
+
44
+ async loadMarkets(): Promise<void> {
45
+ await this.ex.loadMarkets()
46
+ }
47
+
48
+ hasSpotMarket(symbol: string): boolean {
49
+ const m: any = (this.ex as any).markets?.[symbol]
50
+ return !!m && m.type === 'spot'
51
+ }
52
+
53
+ hasFuturesMarket(_symbol: string): boolean {
54
+ return false
55
+ }
56
+
57
+ getMarket(symbol: string): MarketInfo | undefined {
58
+ const m: any = (this.ex as any).markets?.[symbol]
59
+ if (!m) return undefined
60
+ return {
61
+ symbol: m.symbol,
62
+ base: m.base,
63
+ quote: m.quote,
64
+ type: 'spot',
65
+ precisionAmount: m.precision?.amount,
66
+ precisionPrice: m.precision?.price,
67
+ minCost: m.limits?.cost?.min,
68
+ minAmount: m.limits?.amount?.min,
69
+ }
70
+ }
71
+
72
+ private roundAmount(symbol: string, amount: number): number {
73
+ const m: any = (this.ex as any).markets?.[symbol]
74
+ if (!m) return amount
75
+ return Number((this.ex as any).amountToPrecision(symbol, amount))
76
+ }
77
+
78
+ private roundPrice(symbol: string, price: number): number {
79
+ const m: any = (this.ex as any).markets?.[symbol]
80
+ if (!m) return price
81
+ return Number((this.ex as any).priceToPrecision(symbol, price))
82
+ }
83
+
84
+ async fetchTicker(symbol: string): Promise<{ last: number }> {
85
+ const t: any = await (this.ex as any).fetchTicker(symbol)
86
+ return { last: Number(t?.last ?? 0) }
87
+ }
88
+
89
+ async fetchBalance(): Promise<Record<string, { free: number; used: number; total: number }>> {
90
+ const bal: any = await (this.ex as any).fetchBalance()
91
+ return bal as any
92
+ }
93
+
94
+ async fetchOpenOrders(symbol?: string): Promise<OrderInfo[]> {
95
+ const orders: any[] = await (this.ex as any).fetchOpenOrders(symbol)
96
+ return orders.map((o: any) => mapCcxtOrder(o.symbol, o))
97
+ }
98
+
99
+ async cancelOrder(symbol: string, orderId: string): Promise<void> {
100
+ await (this.ex as any).cancelOrder(orderId, symbol)
101
+ }
102
+
103
+ async quoteToBaseQty(params: { symbol: string; quoteCost: number; price?: number }): Promise<number> {
104
+ const { symbol, quoteCost } = params
105
+ if (!quoteCost || quoteCost <= 0) throw new Error(`gate-spot quoteCost must be > 0, got ${quoteCost}`)
106
+
107
+ const p = params.price ?? (await this.fetchTicker(symbol)).last
108
+ if (!p || p <= 0) throw new Error(`gate-spot invalid price for ${symbol}: ${p}`)
109
+
110
+ const m = this.getMarket(symbol)
111
+
112
+ // 1) первичное значение и округление вниз (как обычно делает ccxt)
113
+ let baseQty = this.roundAmount(symbol, quoteCost / p)
114
+
115
+ // 2) minAmount
116
+ if (m?.minAmount && baseQty < m.minAmount) {
117
+ baseQty = this.roundAmount(symbol, m.minAmount)
118
+ }
119
+
120
+ // 3) minCost (ключевой фикс): если из-за округления ниже minCost — увеличиваем на один шаг
121
+ if (m?.minCost) {
122
+ const cost = baseQty * p
123
+ if (cost + 1e-12 < m.minCost) {
124
+ // вычисляем 1 шаг по precision.amount
125
+ const prec = m.precisionAmount
126
+ if (typeof prec === 'number') {
127
+ const step = Math.pow(10, -prec)
128
+ baseQty = this.roundAmount(symbol, baseQty + step)
129
+ } else {
130
+ // fallback: небольшой буфер по quoteCost
131
+ baseQty = this.roundAmount(symbol, (quoteCost * 1.002) / p)
132
+ }
133
+ }
134
+ if (baseQty * p + 1e-12 < m.minCost) {
135
+ throw new Error(`gate-spot violates minCost: ${baseQty * p} < ${m.minCost} ${m.quote}`)
136
+ }
137
+ }
138
+
139
+ return baseQty
140
+ }
141
+
142
+ async baseQtyToQuote(params: { symbol: string; baseQty: number; price?: number }): Promise<number> {
143
+ const qty = this.roundAmount(params.symbol, params.baseQty)
144
+ if (!qty || qty <= 0) return 0
145
+ const p = params.price ?? (await this.fetchTicker(params.symbol)).last
146
+ if (!p || p <= 0) throw new Error(`gate-spot invalid price for ${params.symbol}: ${p}`)
147
+ return qty * p
148
+ }
149
+
150
+ // spot trading
151
+ async marketBuySpot(symbol: string, quoteCost: number): Promise<OrderInfo> {
152
+ const m = this.getMarket(symbol)
153
+ if (m?.minCost && quoteCost < m.minCost) {
154
+ throw new Error(`gate-spot buy violates minCost: ${quoteCost} < ${m.minCost} ${m.quote}`)
155
+ }
156
+
157
+ const order: any = await (this.ex as any).createMarketBuyOrderWithCost(symbol, quoteCost, {
158
+ createMarketBuyOrderRequiresPrice: false,
159
+ })
160
+
161
+ return mapCcxtOrder(symbol, order)
162
+ }
163
+
164
+ async marketSellSpot(symbol: string, baseAmount: number): Promise<OrderInfo> {
165
+ const amount = this.roundAmount(symbol, baseAmount)
166
+ if (!amount || amount <= 0) throw new Error(`gate-spot sell amount too small: ${amount}`)
167
+
168
+ const m = this.getMarket(symbol)
169
+ if (m?.minAmount && amount < m.minAmount) {
170
+ throw new Error(`gate-spot sell violates minAmount: ${amount} < ${m.minAmount} ${m.base}`)
171
+ }
172
+
173
+ const order: any = await (this.ex as any).createOrder(symbol, 'market', 'sell', amount)
174
+ return mapCcxtOrder(symbol, order)
175
+ }
176
+
177
+ async limitOrderSpot(symbol: string, side: 'buy' | 'sell', baseAmount: number, price: number): Promise<OrderInfo> {
178
+ const amount = this.roundAmount(symbol, baseAmount)
179
+ const p = this.roundPrice(symbol, price)
180
+
181
+ if (!amount || amount <= 0) throw new Error(`gate-spot limit amount too small: ${amount}`)
182
+ if (!p || p <= 0) throw new Error(`gate-spot limit price invalid: ${p}`)
183
+
184
+ const m = this.getMarket(symbol)
185
+ if (m?.minAmount && amount < m.minAmount) {
186
+ throw new Error(`gate-spot limit violates minAmount: ${amount} < ${m.minAmount} ${m.base}`)
187
+ }
188
+ if (m?.minCost && amount * p < m.minCost) {
189
+ throw new Error(`gate-spot limit violates minCost: ${amount * p} < ${m.minCost} ${m.quote}`)
190
+ }
191
+
192
+ const order: any = await (this.ex as any).createOrder(symbol, 'limit', side, amount, p)
193
+ return mapCcxtOrder(symbol, order)
194
+ }
195
+
196
+ // futures not supported
197
+ async openFutures(): Promise<OrderInfo> {
198
+ throw new Error('gate-spot: futures not supported')
199
+ }
200
+ async closeFutures(): Promise<OrderInfo> {
201
+ throw new Error('gate-spot: futures not supported')
202
+ }
203
+
204
+ // positions for spot: empty (НЕ используем ccxt Position typing)
205
+ async fetchPositions(): Promise<PositionInfo[]> {
206
+ return []
207
+ }
208
+ }
@@ -0,0 +1,22 @@
1
+ import type { OrderInfo } from '../../../core/exchange'
2
+
3
+ export function mapCcxtOrder(symbol: string, o: any): OrderInfo {
4
+ const price = o.price ?? o.average ?? o.avgPrice
5
+
6
+ // ВАЖНО: в ccxt amount обычно base, filled тоже base, cost = quote.
7
+ const amount =
8
+ typeof o.filled === 'number' && o.filled > 0 ? o.filled :
9
+ typeof o.amount === 'number' && o.amount > 0 ? o.amount :
10
+ 0
11
+
12
+ return {
13
+ id: String(o.id),
14
+ symbol,
15
+ side: o.side,
16
+ type: o.type,
17
+ price,
18
+ amount,
19
+ status: o.status,
20
+ raw: o,
21
+ }
22
+ }