@metaflux/fluxaction 0.1.3
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/dist/core/RiskManager.d.ts +22 -0
- package/dist/core/RiskManager.js +179 -0
- package/dist/core/exchange.d.ts +76 -0
- package/dist/core/exchange.js +1 -0
- package/dist/core/logicalOrders.d.ts +23 -0
- package/dist/core/logicalOrders.js +1 -0
- package/dist/core/results.d.ts +18 -0
- package/dist/core/results.js +1 -0
- package/dist/exchanges/GateSpotAdapter.d.ts +33 -0
- package/dist/exchanges/GateSpotAdapter.js +146 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +70 -0
- package/examples/main.ts +77 -0
- package/package.json +34 -0
- package/src/core/RiskManager.ts +205 -0
- package/src/core/exchange.ts +87 -0
- package/src/core/logicalOrders.ts +31 -0
- package/src/core/results.ts +20 -0
- package/src/exchanges/GateSpotAdapter.ts +193 -0
- package/src/index.ts +138 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ExchangeAdapter } from './exchange';
|
|
2
|
+
import type { LogicalOrder, TakeProfitOrder, StopLossOrder, TrailingStopOrder } from './logicalOrders';
|
|
3
|
+
export interface RiskManagerConfig {
|
|
4
|
+
exchanges: Record<string, ExchangeAdapter>;
|
|
5
|
+
pollIntervalMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class RiskManager {
|
|
8
|
+
private cfg;
|
|
9
|
+
private orders;
|
|
10
|
+
private started;
|
|
11
|
+
constructor(cfg: RiskManagerConfig);
|
|
12
|
+
registerExchange(ex: ExchangeAdapter): void;
|
|
13
|
+
placeTakeProfit(o: TakeProfitOrder): Promise<void>;
|
|
14
|
+
placeStopLoss(o: StopLossOrder): Promise<void>;
|
|
15
|
+
placeTrailingStop(o: TrailingStopOrder): Promise<void>;
|
|
16
|
+
cancel(id: string): Promise<void>;
|
|
17
|
+
getActiveOrders(): LogicalOrder[];
|
|
18
|
+
private startPolling;
|
|
19
|
+
private tick;
|
|
20
|
+
private processForSymbol;
|
|
21
|
+
private executeAndClear;
|
|
22
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
export class RiskManager {
|
|
2
|
+
constructor(cfg) {
|
|
3
|
+
this.orders = new Map();
|
|
4
|
+
this.started = false;
|
|
5
|
+
this.cfg = cfg;
|
|
6
|
+
}
|
|
7
|
+
registerExchange(ex) {
|
|
8
|
+
this.cfg.exchanges[ex.id] = ex;
|
|
9
|
+
}
|
|
10
|
+
async placeTakeProfit(o) {
|
|
11
|
+
this.orders.set(o.id, o);
|
|
12
|
+
const ex = this.cfg.exchanges[o.exchangeId];
|
|
13
|
+
if (!ex)
|
|
14
|
+
throw new Error(`no exchange ${o.exchangeId}`);
|
|
15
|
+
if (ex.capabilities.spot.nativeTakeProfit) {
|
|
16
|
+
// TODO
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.startPolling();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async placeStopLoss(o) {
|
|
23
|
+
this.orders.set(o.id, o);
|
|
24
|
+
const ex = this.cfg.exchanges[o.exchangeId];
|
|
25
|
+
if (!ex)
|
|
26
|
+
throw new Error(`no exchange ${o.exchangeId}`);
|
|
27
|
+
if (ex.capabilities.spot.nativeStopLoss) {
|
|
28
|
+
// TODO
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
this.startPolling();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async placeTrailingStop(o) {
|
|
35
|
+
this.orders.set(o.id, o);
|
|
36
|
+
const ex = this.cfg.exchanges[o.exchangeId];
|
|
37
|
+
if (!ex)
|
|
38
|
+
throw new Error(`no exchange ${o.exchangeId}`);
|
|
39
|
+
if (ex.capabilities.spot.nativeTrailingStop) {
|
|
40
|
+
// TODO
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.startPolling();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async cancel(id) {
|
|
47
|
+
this.orders.delete(id);
|
|
48
|
+
}
|
|
49
|
+
getActiveOrders() {
|
|
50
|
+
return [...this.orders.values()];
|
|
51
|
+
}
|
|
52
|
+
startPolling() {
|
|
53
|
+
if (this.started)
|
|
54
|
+
return;
|
|
55
|
+
this.started = true;
|
|
56
|
+
const interval = this.cfg.pollIntervalMs ?? 3000;
|
|
57
|
+
setInterval(() => {
|
|
58
|
+
this.tick().catch((e) => {
|
|
59
|
+
console.error('[RiskManager] tick error', e);
|
|
60
|
+
});
|
|
61
|
+
}, interval);
|
|
62
|
+
}
|
|
63
|
+
async tick() {
|
|
64
|
+
const grouped = {};
|
|
65
|
+
for (const o of this.orders.values()) {
|
|
66
|
+
if (!grouped[o.exchangeId])
|
|
67
|
+
grouped[o.exchangeId] = {};
|
|
68
|
+
if (!grouped[o.exchangeId][o.symbol])
|
|
69
|
+
grouped[o.exchangeId][o.symbol] = [];
|
|
70
|
+
grouped[o.exchangeId][o.symbol].push(o);
|
|
71
|
+
}
|
|
72
|
+
for (const [exId, bySymbol] of Object.entries(grouped)) {
|
|
73
|
+
const ex = this.cfg.exchanges[exId];
|
|
74
|
+
if (!ex)
|
|
75
|
+
continue;
|
|
76
|
+
for (const [symbol, orders] of Object.entries(bySymbol)) {
|
|
77
|
+
await this.processForSymbol(ex, symbol, orders);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async processForSymbol(ex, symbol, orders) {
|
|
82
|
+
const ticker = await ex.fetchTicker(symbol);
|
|
83
|
+
const price = ticker.last;
|
|
84
|
+
if (!price || price <= 0)
|
|
85
|
+
return;
|
|
86
|
+
for (const o of orders) {
|
|
87
|
+
if (o.kind === 'take-profit') {
|
|
88
|
+
const tp = o;
|
|
89
|
+
if (o.side === 'sell' && price >= tp.triggerPrice) {
|
|
90
|
+
console.log('[RiskManager] TP condition hit', {
|
|
91
|
+
id: o.id,
|
|
92
|
+
price,
|
|
93
|
+
triggerPrice: tp.triggerPrice,
|
|
94
|
+
});
|
|
95
|
+
await this.executeAndClear(ex, o, price);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (o.kind === 'stop-loss') {
|
|
99
|
+
const sl = o;
|
|
100
|
+
if (o.side === 'sell' && price <= sl.triggerPrice) {
|
|
101
|
+
console.log('[RiskManager] SL condition hit', {
|
|
102
|
+
id: o.id,
|
|
103
|
+
price,
|
|
104
|
+
triggerPrice: sl.triggerPrice,
|
|
105
|
+
});
|
|
106
|
+
await this.executeAndClear(ex, o, price);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else if (o.kind === 'trailing-stop') {
|
|
110
|
+
const ts = o;
|
|
111
|
+
if (!ts.highWatermark || price > ts.highWatermark) {
|
|
112
|
+
ts.highWatermark = price;
|
|
113
|
+
this.orders.set(o.id, ts);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const drop = (ts.highWatermark - price) / ts.highWatermark;
|
|
117
|
+
if (drop >= ts.callbackRate) {
|
|
118
|
+
console.log('[RiskManager] TRAILING condition hit', {
|
|
119
|
+
id: o.id,
|
|
120
|
+
price,
|
|
121
|
+
highWatermark: ts.highWatermark,
|
|
122
|
+
drop,
|
|
123
|
+
});
|
|
124
|
+
await this.executeAndClear(ex, o, price);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async executeAndClear(ex, o, price) {
|
|
130
|
+
console.log('[RiskManager] EXIT', {
|
|
131
|
+
exchangeId: ex.id,
|
|
132
|
+
symbol: o.symbol,
|
|
133
|
+
kind: o.kind,
|
|
134
|
+
side: o.side,
|
|
135
|
+
qty: o.qty,
|
|
136
|
+
price,
|
|
137
|
+
});
|
|
138
|
+
this.orders.delete(o.id);
|
|
139
|
+
if (o.side === 'sell') {
|
|
140
|
+
for (const [id, ord] of this.orders.entries()) {
|
|
141
|
+
if (ord.exchangeId === o.exchangeId && ord.symbol === o.symbol && ord.side === 'sell') {
|
|
142
|
+
this.orders.delete(id);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const bal = await ex.fetchBalance();
|
|
147
|
+
const base = o.symbol.split('/')[0];
|
|
148
|
+
const free = bal[base]?.free ?? 0;
|
|
149
|
+
const rawQty = Math.min(o.qty, free * 0.995);
|
|
150
|
+
if (rawQty <= 0) {
|
|
151
|
+
console.warn('[RiskManager] skip exit: qty <= 0', { symbol: o.symbol, base, free, requested: o.qty });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (o.side === 'sell') {
|
|
155
|
+
try {
|
|
156
|
+
await ex.marketSellSpot(o.symbol, rawQty);
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
const msg = String(e.message || '');
|
|
160
|
+
if (msg.includes('too small') && msg.includes('Your order size')) {
|
|
161
|
+
console.warn('[RiskManager] sell failed: below exchange min size', {
|
|
162
|
+
symbol: o.symbol,
|
|
163
|
+
base,
|
|
164
|
+
free,
|
|
165
|
+
requested: o.qty,
|
|
166
|
+
rawQty,
|
|
167
|
+
error: msg,
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
throw e;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const quoteCost = price * rawQty;
|
|
176
|
+
await ex.marketBuySpot(o.symbol, quoteCost);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type MarketType = 'spot' | 'futures';
|
|
2
|
+
export type Side = 'buy' | 'sell';
|
|
3
|
+
export type PositionSide = 'long' | 'short';
|
|
4
|
+
export interface MarketInfo {
|
|
5
|
+
symbol: string;
|
|
6
|
+
base: string;
|
|
7
|
+
quote: string;
|
|
8
|
+
type: MarketType;
|
|
9
|
+
precisionAmount?: number;
|
|
10
|
+
precisionPrice?: number;
|
|
11
|
+
minCost?: number;
|
|
12
|
+
minAmount?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface OrderInfo {
|
|
15
|
+
id: string;
|
|
16
|
+
symbol: string;
|
|
17
|
+
side: Side;
|
|
18
|
+
type: 'market' | 'limit';
|
|
19
|
+
price?: number;
|
|
20
|
+
amount: number;
|
|
21
|
+
status: string;
|
|
22
|
+
raw: any;
|
|
23
|
+
}
|
|
24
|
+
export interface PositionInfo {
|
|
25
|
+
symbol: string;
|
|
26
|
+
side: PositionSide;
|
|
27
|
+
size: number;
|
|
28
|
+
entryPrice: number;
|
|
29
|
+
raw: any;
|
|
30
|
+
}
|
|
31
|
+
export interface ExchangeCapabilities {
|
|
32
|
+
spot: {
|
|
33
|
+
nativeTakeProfit: boolean;
|
|
34
|
+
nativeStopLoss: boolean;
|
|
35
|
+
nativeTrailingStop: boolean;
|
|
36
|
+
};
|
|
37
|
+
futures: {
|
|
38
|
+
nativeTakeProfit: boolean;
|
|
39
|
+
nativeStopLoss: boolean;
|
|
40
|
+
nativeTrailingStop: boolean;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface ExchangeAdapter {
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly label: string;
|
|
46
|
+
readonly capabilities: ExchangeCapabilities;
|
|
47
|
+
loadMarkets(): Promise<void>;
|
|
48
|
+
hasSpotMarket(symbol: string): boolean;
|
|
49
|
+
hasFuturesMarket(symbol: string): boolean;
|
|
50
|
+
getMarket(symbol: string): MarketInfo | undefined;
|
|
51
|
+
fetchTicker(symbol: string): Promise<{
|
|
52
|
+
last: number;
|
|
53
|
+
}>;
|
|
54
|
+
fetchBalance(): Promise<Record<string, {
|
|
55
|
+
free: number;
|
|
56
|
+
used: number;
|
|
57
|
+
total: number;
|
|
58
|
+
}>>;
|
|
59
|
+
fetchOpenOrders(symbol?: string): Promise<OrderInfo[]>;
|
|
60
|
+
cancelOrder(symbol: string, orderId: string): Promise<void>;
|
|
61
|
+
marketBuySpot(symbol: string, quoteCost: number): Promise<OrderInfo>;
|
|
62
|
+
marketSellSpot(symbol: string, baseAmount: number): Promise<OrderInfo>;
|
|
63
|
+
limitOrderSpot(symbol: string, side: Side, baseAmount: number, price: number): Promise<OrderInfo>;
|
|
64
|
+
openFutures(params: {
|
|
65
|
+
symbol: string;
|
|
66
|
+
side: PositionSide;
|
|
67
|
+
costUSDT: number;
|
|
68
|
+
leverage?: number;
|
|
69
|
+
}): Promise<OrderInfo>;
|
|
70
|
+
closeFutures(params: {
|
|
71
|
+
symbol: string;
|
|
72
|
+
side: PositionSide;
|
|
73
|
+
costUSDT?: number;
|
|
74
|
+
}): Promise<OrderInfo>;
|
|
75
|
+
fetchPositions?(symbol?: string): Promise<PositionInfo[]>;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type LogicalOrderKind = 'take-profit' | 'stop-loss' | 'trailing-stop';
|
|
2
|
+
export interface LogicalOrderBase {
|
|
3
|
+
id: string;
|
|
4
|
+
exchangeId: string;
|
|
5
|
+
symbol: string;
|
|
6
|
+
side: 'buy' | 'sell';
|
|
7
|
+
qty: number;
|
|
8
|
+
}
|
|
9
|
+
export interface TakeProfitOrder extends LogicalOrderBase {
|
|
10
|
+
kind: 'take-profit';
|
|
11
|
+
triggerPrice: number;
|
|
12
|
+
}
|
|
13
|
+
export interface StopLossOrder extends LogicalOrderBase {
|
|
14
|
+
kind: 'stop-loss';
|
|
15
|
+
triggerPrice: number;
|
|
16
|
+
}
|
|
17
|
+
export interface TrailingStopOrder extends LogicalOrderBase {
|
|
18
|
+
kind: 'trailing-stop';
|
|
19
|
+
callbackRate: number;
|
|
20
|
+
activationPrice?: number;
|
|
21
|
+
highWatermark?: number;
|
|
22
|
+
}
|
|
23
|
+
export type LogicalOrder = TakeProfitOrder | StopLossOrder | TrailingStopOrder;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SpotListingExecutionStep {
|
|
2
|
+
at: number;
|
|
3
|
+
level: 'info' | 'warn' | 'error';
|
|
4
|
+
message: string;
|
|
5
|
+
context?: any;
|
|
6
|
+
}
|
|
7
|
+
export interface SpotListingExecutionLog {
|
|
8
|
+
exchangeId: string;
|
|
9
|
+
symbol: string;
|
|
10
|
+
steps: SpotListingExecutionStep[];
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SpotListingResult {
|
|
14
|
+
ticker: string;
|
|
15
|
+
normalizedSymbol: string;
|
|
16
|
+
perExchangeBudget: number;
|
|
17
|
+
executions: SpotListingExecutionLog[];
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ExchangeAdapter, type ExchangeCapabilities, type MarketInfo, type OrderInfo, type PositionInfo } from '../core/exchange';
|
|
2
|
+
export interface GateSpotAdapterConfig {
|
|
3
|
+
id: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
secret: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class GateSpotAdapter implements ExchangeAdapter {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
readonly capabilities: ExchangeCapabilities;
|
|
12
|
+
private ex;
|
|
13
|
+
constructor(cfg: GateSpotAdapterConfig);
|
|
14
|
+
loadMarkets(): Promise<void>;
|
|
15
|
+
hasSpotMarket(symbol: string): boolean;
|
|
16
|
+
hasFuturesMarket(): boolean;
|
|
17
|
+
getMarket(symbol: string): MarketInfo | undefined;
|
|
18
|
+
private roundAmount;
|
|
19
|
+
private roundPrice;
|
|
20
|
+
private mapOrder;
|
|
21
|
+
fetchTicker(symbol: string): Promise<{
|
|
22
|
+
last: number;
|
|
23
|
+
}>;
|
|
24
|
+
fetchBalance(): Promise<any>;
|
|
25
|
+
fetchOpenOrders(symbol?: string): Promise<OrderInfo[]>;
|
|
26
|
+
cancelOrder(symbol: string, orderId: string): Promise<void>;
|
|
27
|
+
marketBuySpot(symbol: string, quoteCost: number): Promise<OrderInfo>;
|
|
28
|
+
marketSellSpot(symbol: string, baseAmount: number): Promise<OrderInfo>;
|
|
29
|
+
limitOrderSpot(symbol: string, side: 'buy' | 'sell', baseAmount: number, price: number): Promise<OrderInfo>;
|
|
30
|
+
openFutures(): Promise<OrderInfo>;
|
|
31
|
+
closeFutures(): Promise<OrderInfo>;
|
|
32
|
+
fetchPositions(): Promise<PositionInfo[]>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import ccxt from 'ccxt';
|
|
2
|
+
export class GateSpotAdapter {
|
|
3
|
+
constructor(cfg) {
|
|
4
|
+
this.id = cfg.id;
|
|
5
|
+
this.label = cfg.label ?? 'gate-spot';
|
|
6
|
+
this.ex = new ccxt.gateio({
|
|
7
|
+
apiKey: cfg.apiKey,
|
|
8
|
+
secret: cfg.secret,
|
|
9
|
+
enableRateLimit: true,
|
|
10
|
+
options: {
|
|
11
|
+
defaultType: 'spot',
|
|
12
|
+
createMarketBuyOrderRequiresPrice: false,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
this.capabilities = {
|
|
16
|
+
spot: {
|
|
17
|
+
nativeTakeProfit: false,
|
|
18
|
+
nativeStopLoss: false,
|
|
19
|
+
nativeTrailingStop: false,
|
|
20
|
+
},
|
|
21
|
+
futures: {
|
|
22
|
+
nativeTakeProfit: false,
|
|
23
|
+
nativeStopLoss: false,
|
|
24
|
+
nativeTrailingStop: false,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async loadMarkets() {
|
|
29
|
+
await this.ex.loadMarkets();
|
|
30
|
+
}
|
|
31
|
+
hasSpotMarket(symbol) {
|
|
32
|
+
const m = this.ex.markets?.[symbol];
|
|
33
|
+
return !!m && m.type === 'spot';
|
|
34
|
+
}
|
|
35
|
+
hasFuturesMarket() {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
getMarket(symbol) {
|
|
39
|
+
const m = this.ex.markets?.[symbol];
|
|
40
|
+
if (!m)
|
|
41
|
+
return undefined;
|
|
42
|
+
return {
|
|
43
|
+
symbol: m.symbol,
|
|
44
|
+
base: m.base,
|
|
45
|
+
quote: m.quote,
|
|
46
|
+
type: m.type,
|
|
47
|
+
precisionAmount: m.precision?.amount,
|
|
48
|
+
precisionPrice: m.precision?.price,
|
|
49
|
+
minCost: m.limits?.cost?.min,
|
|
50
|
+
minAmount: m.limits?.amount?.min,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
roundAmount(symbol, amount) {
|
|
54
|
+
const m = this.ex.markets?.[symbol];
|
|
55
|
+
if (!m)
|
|
56
|
+
return amount;
|
|
57
|
+
return Number(this.ex.amountToPrecision(symbol, amount));
|
|
58
|
+
}
|
|
59
|
+
roundPrice(symbol, price) {
|
|
60
|
+
const m = this.ex.markets?.[symbol];
|
|
61
|
+
if (!m)
|
|
62
|
+
return price;
|
|
63
|
+
return Number(this.ex.priceToPrecision(symbol, price));
|
|
64
|
+
}
|
|
65
|
+
mapOrder(symbol, o) {
|
|
66
|
+
const price = o.price ?? o.average ?? o.avgPrice;
|
|
67
|
+
let baseAmount;
|
|
68
|
+
if (price && o.cost) {
|
|
69
|
+
baseAmount = o.cost / price;
|
|
70
|
+
}
|
|
71
|
+
else if (price && o.amount) {
|
|
72
|
+
baseAmount = o.amount / price;
|
|
73
|
+
}
|
|
74
|
+
else if (typeof o.amount === 'number' && o.amount > 0) {
|
|
75
|
+
baseAmount = o.amount;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
baseAmount = 0;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
id: o.id,
|
|
82
|
+
symbol,
|
|
83
|
+
side: o.side,
|
|
84
|
+
type: o.type,
|
|
85
|
+
price,
|
|
86
|
+
amount: baseAmount,
|
|
87
|
+
status: o.status,
|
|
88
|
+
raw: o,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async fetchTicker(symbol) {
|
|
92
|
+
const t = await this.ex.fetchTicker(symbol);
|
|
93
|
+
return { last: t.last ?? 0 };
|
|
94
|
+
}
|
|
95
|
+
async fetchBalance() {
|
|
96
|
+
const bal = await this.ex.fetchBalance();
|
|
97
|
+
return bal;
|
|
98
|
+
}
|
|
99
|
+
async fetchOpenOrders(symbol) {
|
|
100
|
+
const orders = await this.ex.fetchOpenOrders(symbol);
|
|
101
|
+
return orders.map((o) => this.mapOrder(o.symbol, o));
|
|
102
|
+
}
|
|
103
|
+
async cancelOrder(symbol, orderId) {
|
|
104
|
+
await this.ex.cancelOrder(orderId, symbol);
|
|
105
|
+
}
|
|
106
|
+
async marketBuySpot(symbol, quoteCost) {
|
|
107
|
+
const ticker = await this.fetchTicker(symbol);
|
|
108
|
+
if (!ticker.last || ticker.last <= 0) {
|
|
109
|
+
throw new Error(`gate-spot invalid price for ${symbol}: ${ticker.last}`);
|
|
110
|
+
}
|
|
111
|
+
const m = this.getMarket(symbol);
|
|
112
|
+
if (m?.minCost && quoteCost < m.minCost) {
|
|
113
|
+
throw new Error(`gate-spot buy violates minCost: ${quoteCost} < ${m.minCost} ${m.quote}`);
|
|
114
|
+
}
|
|
115
|
+
const order = await this.ex.createMarketBuyOrderWithCost(symbol, quoteCost, {
|
|
116
|
+
createMarketBuyOrderRequiresPrice: false,
|
|
117
|
+
});
|
|
118
|
+
return this.mapOrder(symbol, order);
|
|
119
|
+
}
|
|
120
|
+
async marketSellSpot(symbol, baseAmount) {
|
|
121
|
+
const amount = this.roundAmount(symbol, baseAmount);
|
|
122
|
+
if (!amount || amount <= 0) {
|
|
123
|
+
throw new Error(`gate-spot sell amount too small for ${symbol}: ${amount}`);
|
|
124
|
+
}
|
|
125
|
+
const order = await this.ex.createOrder(symbol, 'market', 'sell', amount);
|
|
126
|
+
return this.mapOrder(symbol, order);
|
|
127
|
+
}
|
|
128
|
+
async limitOrderSpot(symbol, side, baseAmount, price) {
|
|
129
|
+
const amount = this.roundAmount(symbol, baseAmount);
|
|
130
|
+
const p = this.roundPrice(symbol, price);
|
|
131
|
+
if (!amount || amount <= 0) {
|
|
132
|
+
throw new Error(`gate-spot limit amount too small for ${symbol}: ${amount}`);
|
|
133
|
+
}
|
|
134
|
+
const order = await this.ex.createOrder(symbol, 'limit', side, amount, p);
|
|
135
|
+
return this.mapOrder(symbol, order);
|
|
136
|
+
}
|
|
137
|
+
async openFutures() {
|
|
138
|
+
throw new Error('gate-spot: futures not supported');
|
|
139
|
+
}
|
|
140
|
+
async closeFutures() {
|
|
141
|
+
throw new Error('gate-spot: futures not supported');
|
|
142
|
+
}
|
|
143
|
+
async fetchPositions() {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ExchangeAdapter } from './core/exchange';
|
|
2
|
+
import { type TakeProfitOrder, type StopLossOrder, type TrailingStopOrder } from './core/logicalOrders';
|
|
3
|
+
export interface FluxActionConfig {
|
|
4
|
+
okx?: {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
secret: string;
|
|
7
|
+
passphrase: string;
|
|
8
|
+
isDemo?: boolean;
|
|
9
|
+
};
|
|
10
|
+
gate?: {
|
|
11
|
+
apiKey: string;
|
|
12
|
+
secret: string;
|
|
13
|
+
};
|
|
14
|
+
risk?: {
|
|
15
|
+
pollIntervalMs?: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare class FluxAction {
|
|
19
|
+
private adapters;
|
|
20
|
+
private risk;
|
|
21
|
+
constructor(cfg: FluxActionConfig);
|
|
22
|
+
init(): Promise<void>;
|
|
23
|
+
getExchange(id: string): ExchangeAdapter | undefined;
|
|
24
|
+
openSpotPosition(params: {
|
|
25
|
+
exchangeId: string;
|
|
26
|
+
symbol: string;
|
|
27
|
+
quoteCost: number;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
orderId: string;
|
|
30
|
+
filledBase: number;
|
|
31
|
+
avgPrice: number;
|
|
32
|
+
}>;
|
|
33
|
+
closeSpotMarket(params: {
|
|
34
|
+
exchangeId: string;
|
|
35
|
+
symbol: string;
|
|
36
|
+
baseQty: number;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
orderId: string;
|
|
39
|
+
}>;
|
|
40
|
+
placeSpotLimit(params: {
|
|
41
|
+
exchangeId: string;
|
|
42
|
+
symbol: string;
|
|
43
|
+
side: 'buy' | 'sell';
|
|
44
|
+
baseQty: number;
|
|
45
|
+
price: number;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
orderId: string;
|
|
48
|
+
}>;
|
|
49
|
+
placeTakeProfit(o: TakeProfitOrder): Promise<{
|
|
50
|
+
id: string;
|
|
51
|
+
}>;
|
|
52
|
+
placeStopLoss(o: StopLossOrder): Promise<{
|
|
53
|
+
id: string;
|
|
54
|
+
}>;
|
|
55
|
+
placeTrailingStop(o: TrailingStopOrder): Promise<{
|
|
56
|
+
id: string;
|
|
57
|
+
}>;
|
|
58
|
+
cancelLogical(id: string): Promise<void>;
|
|
59
|
+
getActiveLogicalOrders(): import("./core/logicalOrders").LogicalOrder[];
|
|
60
|
+
private requireExchange;
|
|
61
|
+
}
|
|
62
|
+
export { RiskManager, type RiskManagerConfig } from './core/RiskManager';
|
|
63
|
+
export type { ExchangeAdapter, ExchangeCapabilities, MarketInfo, OrderInfo, PositionInfo, } from './core/exchange';
|
|
64
|
+
export { GateSpotAdapter, type GateSpotAdapterConfig, } from './exchanges/GateSpotAdapter';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { RiskManager } from './core/RiskManager';
|
|
2
|
+
import { GateSpotAdapter } from './exchanges/GateSpotAdapter';
|
|
3
|
+
export class FluxAction {
|
|
4
|
+
constructor(cfg) {
|
|
5
|
+
this.adapters = {};
|
|
6
|
+
if (cfg.gate) {
|
|
7
|
+
const gate = new GateSpotAdapter({
|
|
8
|
+
id: 'gate-spot',
|
|
9
|
+
apiKey: cfg.gate.apiKey,
|
|
10
|
+
secret: cfg.gate.secret,
|
|
11
|
+
});
|
|
12
|
+
this.adapters[gate.id] = gate;
|
|
13
|
+
}
|
|
14
|
+
this.risk = new RiskManager({
|
|
15
|
+
exchanges: this.adapters,
|
|
16
|
+
pollIntervalMs: cfg.risk?.pollIntervalMs,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async init() {
|
|
20
|
+
await Promise.all(Object.values(this.adapters).map((a) => a.loadMarkets()));
|
|
21
|
+
}
|
|
22
|
+
getExchange(id) {
|
|
23
|
+
return this.adapters[id];
|
|
24
|
+
}
|
|
25
|
+
async openSpotPosition(params) {
|
|
26
|
+
const ex = this.requireExchange(params.exchangeId);
|
|
27
|
+
const order = await ex.marketBuySpot(params.symbol, params.quoteCost);
|
|
28
|
+
return {
|
|
29
|
+
orderId: order.id,
|
|
30
|
+
filledBase: order.amount,
|
|
31
|
+
avgPrice: order.price ?? 0,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async closeSpotMarket(params) {
|
|
35
|
+
const ex = this.requireExchange(params.exchangeId);
|
|
36
|
+
const order = await ex.marketSellSpot(params.symbol, params.baseQty);
|
|
37
|
+
return { orderId: order.id };
|
|
38
|
+
}
|
|
39
|
+
async placeSpotLimit(params) {
|
|
40
|
+
const ex = this.requireExchange(params.exchangeId);
|
|
41
|
+
const order = await ex.limitOrderSpot(params.symbol, params.side, params.baseQty, params.price);
|
|
42
|
+
return { orderId: order.id };
|
|
43
|
+
}
|
|
44
|
+
async placeTakeProfit(o) {
|
|
45
|
+
await this.risk.placeTakeProfit(o);
|
|
46
|
+
return { id: o.id };
|
|
47
|
+
}
|
|
48
|
+
async placeStopLoss(o) {
|
|
49
|
+
await this.risk.placeStopLoss(o);
|
|
50
|
+
return { id: o.id };
|
|
51
|
+
}
|
|
52
|
+
async placeTrailingStop(o) {
|
|
53
|
+
await this.risk.placeTrailingStop(o);
|
|
54
|
+
return { id: o.id };
|
|
55
|
+
}
|
|
56
|
+
async cancelLogical(id) {
|
|
57
|
+
await this.risk.cancel(id);
|
|
58
|
+
}
|
|
59
|
+
getActiveLogicalOrders() {
|
|
60
|
+
return this.risk.getActiveOrders();
|
|
61
|
+
}
|
|
62
|
+
requireExchange(id) {
|
|
63
|
+
const ex = this.adapters[id];
|
|
64
|
+
if (!ex)
|
|
65
|
+
throw new Error(`Exchange ${id} is not configured`);
|
|
66
|
+
return ex;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export { RiskManager } from './core/RiskManager';
|
|
70
|
+
export { GateSpotAdapter, } from './exchanges/GateSpotAdapter';
|