@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.
- package/dist/core/RiskManager.js +9 -3
- package/dist/core/exchange.js +2 -1
- package/dist/core/logicalOrders.js +2 -1
- package/dist/core/results.js +2 -1
- package/dist/exchanges/GateSpotAdapter.js +29 -15
- package/dist/index.js +17 -9
- package/package.json +5 -4
- package/scripts/futures.ts +80 -0
- package/scripts/spot.ts +84 -0
- package/src/FluxAction.ts +150 -0
- package/src/clients/GateClient.ts +52 -0
- package/src/core/exchange.ts +8 -0
- package/src/core/logicalOrders.ts +21 -7
- package/src/core/orders.ts +7 -0
- package/src/exchanges/gate/futures/GateFuturesAdapter.ts +301 -0
- package/src/exchanges/gate/index.ts +2 -0
- package/src/exchanges/gate/spot/GateSpotAdapter.ts +208 -0
- package/src/exchanges/gate/spot/gate.mapOrder.ts +22 -0
- package/src/index.ts +21 -126
- package/src/risk/ExitExecutor.ts +56 -0
- package/src/risk/OrderStore.ts +21 -0
- package/src/risk/RiskEngine.ts +101 -0
- package/src/risk/RiskScheduler.ts +23 -0
- package/src/services/ExchangeRegistry.ts +27 -0
- package/tsconfig.json +3 -4
- package/examples/main.ts +0 -77
- package/src/core/RiskManager.ts +0 -205
- package/src/core/results.ts +0 -20
- package/src/exchanges/GateSpotAdapter.ts +0 -193
package/dist/core/RiskManager.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RiskManager = void 0;
|
|
4
|
+
class RiskManager {
|
|
2
5
|
constructor(cfg) {
|
|
3
6
|
this.orders = new Map();
|
|
4
7
|
this.started = false;
|
|
@@ -50,10 +53,11 @@ export class RiskManager {
|
|
|
50
53
|
return [...this.orders.values()];
|
|
51
54
|
}
|
|
52
55
|
startPolling() {
|
|
56
|
+
var _a;
|
|
53
57
|
if (this.started)
|
|
54
58
|
return;
|
|
55
59
|
this.started = true;
|
|
56
|
-
const interval = this.cfg.pollIntervalMs
|
|
60
|
+
const interval = (_a = this.cfg.pollIntervalMs) !== null && _a !== void 0 ? _a : 3000;
|
|
57
61
|
setInterval(() => {
|
|
58
62
|
this.tick().catch((e) => {
|
|
59
63
|
console.error('[RiskManager] tick error', e);
|
|
@@ -127,6 +131,7 @@ export class RiskManager {
|
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
async executeAndClear(ex, o, price) {
|
|
134
|
+
var _a, _b;
|
|
130
135
|
console.log('[RiskManager] EXIT', {
|
|
131
136
|
exchangeId: ex.id,
|
|
132
137
|
symbol: o.symbol,
|
|
@@ -145,7 +150,7 @@ export class RiskManager {
|
|
|
145
150
|
}
|
|
146
151
|
const bal = await ex.fetchBalance();
|
|
147
152
|
const base = o.symbol.split('/')[0];
|
|
148
|
-
const free = bal[base]
|
|
153
|
+
const free = (_b = (_a = bal[base]) === null || _a === void 0 ? void 0 : _a.free) !== null && _b !== void 0 ? _b : 0;
|
|
149
154
|
const rawQty = Math.min(o.qty, free * 0.995);
|
|
150
155
|
if (rawQty <= 0) {
|
|
151
156
|
console.warn('[RiskManager] skip exit: qty <= 0', { symbol: o.symbol, base, free, requested: o.qty });
|
|
@@ -177,3 +182,4 @@ export class RiskManager {
|
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
}
|
|
185
|
+
exports.RiskManager = RiskManager;
|
package/dist/core/exchange.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/core/results.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GateSpotAdapter = void 0;
|
|
7
|
+
const ccxt_1 = __importDefault(require("ccxt"));
|
|
8
|
+
class GateSpotAdapter {
|
|
3
9
|
constructor(cfg) {
|
|
10
|
+
var _a;
|
|
4
11
|
this.id = cfg.id;
|
|
5
|
-
this.label = cfg.label
|
|
6
|
-
this.ex = new
|
|
12
|
+
this.label = (_a = cfg.label) !== null && _a !== void 0 ? _a : 'gate-spot';
|
|
13
|
+
this.ex = new ccxt_1.default.gateio({
|
|
7
14
|
apiKey: cfg.apiKey,
|
|
8
15
|
secret: cfg.secret,
|
|
9
16
|
enableRateLimit: true,
|
|
@@ -29,14 +36,16 @@ export class GateSpotAdapter {
|
|
|
29
36
|
await this.ex.loadMarkets();
|
|
30
37
|
}
|
|
31
38
|
hasSpotMarket(symbol) {
|
|
32
|
-
|
|
39
|
+
var _a;
|
|
40
|
+
const m = (_a = this.ex.markets) === null || _a === void 0 ? void 0 : _a[symbol];
|
|
33
41
|
return !!m && m.type === 'spot';
|
|
34
42
|
}
|
|
35
43
|
hasFuturesMarket() {
|
|
36
44
|
return false;
|
|
37
45
|
}
|
|
38
46
|
getMarket(symbol) {
|
|
39
|
-
|
|
47
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
48
|
+
const m = (_a = this.ex.markets) === null || _a === void 0 ? void 0 : _a[symbol];
|
|
40
49
|
if (!m)
|
|
41
50
|
return undefined;
|
|
42
51
|
return {
|
|
@@ -44,26 +53,29 @@ export class GateSpotAdapter {
|
|
|
44
53
|
base: m.base,
|
|
45
54
|
quote: m.quote,
|
|
46
55
|
type: m.type,
|
|
47
|
-
precisionAmount: m.precision
|
|
48
|
-
precisionPrice: m.precision
|
|
49
|
-
minCost: m.limits
|
|
50
|
-
minAmount: m.limits
|
|
56
|
+
precisionAmount: (_b = m.precision) === null || _b === void 0 ? void 0 : _b.amount,
|
|
57
|
+
precisionPrice: (_c = m.precision) === null || _c === void 0 ? void 0 : _c.price,
|
|
58
|
+
minCost: (_e = (_d = m.limits) === null || _d === void 0 ? void 0 : _d.cost) === null || _e === void 0 ? void 0 : _e.min,
|
|
59
|
+
minAmount: (_g = (_f = m.limits) === null || _f === void 0 ? void 0 : _f.amount) === null || _g === void 0 ? void 0 : _g.min,
|
|
51
60
|
};
|
|
52
61
|
}
|
|
53
62
|
roundAmount(symbol, amount) {
|
|
54
|
-
|
|
63
|
+
var _a;
|
|
64
|
+
const m = (_a = this.ex.markets) === null || _a === void 0 ? void 0 : _a[symbol];
|
|
55
65
|
if (!m)
|
|
56
66
|
return amount;
|
|
57
67
|
return Number(this.ex.amountToPrecision(symbol, amount));
|
|
58
68
|
}
|
|
59
69
|
roundPrice(symbol, price) {
|
|
60
|
-
|
|
70
|
+
var _a;
|
|
71
|
+
const m = (_a = this.ex.markets) === null || _a === void 0 ? void 0 : _a[symbol];
|
|
61
72
|
if (!m)
|
|
62
73
|
return price;
|
|
63
74
|
return Number(this.ex.priceToPrecision(symbol, price));
|
|
64
75
|
}
|
|
65
76
|
mapOrder(symbol, o) {
|
|
66
|
-
|
|
77
|
+
var _a, _b;
|
|
78
|
+
const price = (_b = (_a = o.price) !== null && _a !== void 0 ? _a : o.average) !== null && _b !== void 0 ? _b : o.avgPrice;
|
|
67
79
|
let baseAmount;
|
|
68
80
|
if (price && o.cost) {
|
|
69
81
|
baseAmount = o.cost / price;
|
|
@@ -89,8 +101,9 @@ export class GateSpotAdapter {
|
|
|
89
101
|
};
|
|
90
102
|
}
|
|
91
103
|
async fetchTicker(symbol) {
|
|
104
|
+
var _a;
|
|
92
105
|
const t = await this.ex.fetchTicker(symbol);
|
|
93
|
-
return { last: t.last
|
|
106
|
+
return { last: (_a = t.last) !== null && _a !== void 0 ? _a : 0 };
|
|
94
107
|
}
|
|
95
108
|
async fetchBalance() {
|
|
96
109
|
const bal = await this.ex.fetchBalance();
|
|
@@ -109,7 +122,7 @@ export class GateSpotAdapter {
|
|
|
109
122
|
throw new Error(`gate-spot invalid price for ${symbol}: ${ticker.last}`);
|
|
110
123
|
}
|
|
111
124
|
const m = this.getMarket(symbol);
|
|
112
|
-
if (m
|
|
125
|
+
if ((m === null || m === void 0 ? void 0 : m.minCost) && quoteCost < m.minCost) {
|
|
113
126
|
throw new Error(`gate-spot buy violates minCost: ${quoteCost} < ${m.minCost} ${m.quote}`);
|
|
114
127
|
}
|
|
115
128
|
const order = await this.ex.createMarketBuyOrderWithCost(symbol, quoteCost, {
|
|
@@ -144,3 +157,4 @@ export class GateSpotAdapter {
|
|
|
144
157
|
return [];
|
|
145
158
|
}
|
|
146
159
|
}
|
|
160
|
+
exports.GateSpotAdapter = GateSpotAdapter;
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GateSpotAdapter = exports.RiskManager = exports.FluxAction = void 0;
|
|
4
|
+
const RiskManager_1 = require("./core/RiskManager");
|
|
5
|
+
const GateSpotAdapter_1 = require("./exchanges/GateSpotAdapter");
|
|
6
|
+
class FluxAction {
|
|
4
7
|
constructor(cfg) {
|
|
8
|
+
var _a;
|
|
5
9
|
this.adapters = {};
|
|
6
10
|
if (cfg.gate) {
|
|
7
|
-
const gate = new GateSpotAdapter({
|
|
11
|
+
const gate = new GateSpotAdapter_1.GateSpotAdapter({
|
|
8
12
|
id: 'gate-spot',
|
|
9
13
|
apiKey: cfg.gate.apiKey,
|
|
10
14
|
secret: cfg.gate.secret,
|
|
11
15
|
});
|
|
12
16
|
this.adapters[gate.id] = gate;
|
|
13
17
|
}
|
|
14
|
-
this.risk = new RiskManager({
|
|
18
|
+
this.risk = new RiskManager_1.RiskManager({
|
|
15
19
|
exchanges: this.adapters,
|
|
16
|
-
pollIntervalMs: cfg.risk
|
|
20
|
+
pollIntervalMs: (_a = cfg.risk) === null || _a === void 0 ? void 0 : _a.pollIntervalMs,
|
|
17
21
|
});
|
|
18
22
|
}
|
|
19
23
|
async init() {
|
|
@@ -23,12 +27,13 @@ export class FluxAction {
|
|
|
23
27
|
return this.adapters[id];
|
|
24
28
|
}
|
|
25
29
|
async openSpotPosition(params) {
|
|
30
|
+
var _a;
|
|
26
31
|
const ex = this.requireExchange(params.exchangeId);
|
|
27
32
|
const order = await ex.marketBuySpot(params.symbol, params.quoteCost);
|
|
28
33
|
return {
|
|
29
34
|
orderId: order.id,
|
|
30
35
|
filledBase: order.amount,
|
|
31
|
-
avgPrice: order.price
|
|
36
|
+
avgPrice: (_a = order.price) !== null && _a !== void 0 ? _a : 0,
|
|
32
37
|
};
|
|
33
38
|
}
|
|
34
39
|
async closeSpotMarket(params) {
|
|
@@ -66,5 +71,8 @@ export class FluxAction {
|
|
|
66
71
|
return ex;
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
exports.FluxAction = FluxAction;
|
|
75
|
+
var RiskManager_2 = require("./core/RiskManager");
|
|
76
|
+
Object.defineProperty(exports, "RiskManager", { enumerable: true, get: function () { return RiskManager_2.RiskManager; } });
|
|
77
|
+
var GateSpotAdapter_2 = require("./exchanges/GateSpotAdapter");
|
|
78
|
+
Object.defineProperty(exports, "GateSpotAdapter", { enumerable: true, get: function () { return GateSpotAdapter_2.GateSpotAdapter; } });
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaflux/fluxaction",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Programmatic spot trading actions (TP/SL/trailing) for crypto exchanges",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"module": "dist/index.js",
|
|
7
6
|
"types": "dist/index.d.ts",
|
|
8
7
|
"publishConfig": {
|
|
9
8
|
"access": "public"
|
|
10
9
|
},
|
|
11
10
|
"scripts": {
|
|
12
11
|
"build": "tsc -p tsconfig.json",
|
|
13
|
-
"
|
|
12
|
+
"test:spot": "ts-node scripts/spot.ts",
|
|
13
|
+
"test:futures": "ts-node scripts/futures.ts"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"trading-bot",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"author": "wancareri",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"ccxt": "^4.4.0"
|
|
27
|
+
"ccxt": "^4.4.0",
|
|
28
|
+
"dotenv": "^17.2.3"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/node": "^22.0.0",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
try { require('dotenv').config() } catch {}
|
|
2
|
+
|
|
3
|
+
import { FluxAction } from '../src'
|
|
4
|
+
import type { TakeProfitOrder, StopLossOrder, TrailingStopOrder } from '../src/core/logicalOrders'
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const apiKey = process.env.GATE_API_KEY || ''
|
|
8
|
+
const secret = process.env.GATE_SECRET_KEY || ''
|
|
9
|
+
if (!apiKey || !secret) throw new Error('Missing GATE_API_KEY / GATE_SECRET_KEY')
|
|
10
|
+
|
|
11
|
+
const flux = new FluxAction({
|
|
12
|
+
gate: { apiKey, secret },
|
|
13
|
+
risk: { pollIntervalMs: 300 },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
await flux.init()
|
|
17
|
+
|
|
18
|
+
const exchangeId = 'gate-futures'
|
|
19
|
+
const symbol = 'BTC/USDT:USDT'
|
|
20
|
+
const costUSDT = 15
|
|
21
|
+
|
|
22
|
+
const open = await flux.openFuturesPosition({
|
|
23
|
+
exchangeId,
|
|
24
|
+
symbol,
|
|
25
|
+
side: 'short',
|
|
26
|
+
costUSDT,
|
|
27
|
+
leverage: 3,
|
|
28
|
+
price: null, // market
|
|
29
|
+
})
|
|
30
|
+
console.log('[futures] opened', open)
|
|
31
|
+
|
|
32
|
+
const baseId = `${exchangeId}:${symbol}:${Date.now()}`
|
|
33
|
+
|
|
34
|
+
const tp: TakeProfitOrder = {
|
|
35
|
+
id: baseId + ':tp',
|
|
36
|
+
exchangeId,
|
|
37
|
+
market: 'futures',
|
|
38
|
+
symbol,
|
|
39
|
+
kind: 'take-profit',
|
|
40
|
+
triggerPrice: 10_000_000,
|
|
41
|
+
positionSide: 'long',
|
|
42
|
+
exitMode: 'all',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const sl: StopLossOrder = {
|
|
46
|
+
id: baseId + ':sl',
|
|
47
|
+
exchangeId,
|
|
48
|
+
market: 'futures',
|
|
49
|
+
symbol,
|
|
50
|
+
kind: 'stop-loss',
|
|
51
|
+
triggerPrice: 1,
|
|
52
|
+
positionSide: 'long',
|
|
53
|
+
exitMode: 'all',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ts: TrailingStopOrder = {
|
|
57
|
+
id: baseId + ':ts',
|
|
58
|
+
exchangeId,
|
|
59
|
+
market: 'futures',
|
|
60
|
+
symbol,
|
|
61
|
+
kind: 'trailing-stop',
|
|
62
|
+
callbackRate: 0.03,
|
|
63
|
+
activationPrice: undefined,
|
|
64
|
+
highWatermark: undefined,
|
|
65
|
+
positionSide: 'long',
|
|
66
|
+
exitMode: 'all',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await flux.placeTakeProfit(tp)
|
|
70
|
+
await flux.placeStopLoss(sl)
|
|
71
|
+
await flux.placeTrailingStop(ts)
|
|
72
|
+
|
|
73
|
+
console.log('[futures] exits registered', flux.getActiveLogicalOrders().map((o) => o.id))
|
|
74
|
+
while (true) await new Promise((r) => setTimeout(r, 60_000))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch((e) => {
|
|
78
|
+
console.error(e)
|
|
79
|
+
process.exit(1)
|
|
80
|
+
})
|
package/scripts/spot.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
try { require('dotenv').config() } catch {}
|
|
2
|
+
|
|
3
|
+
import { FluxAction } from '../src'
|
|
4
|
+
import type { TakeProfitOrder, StopLossOrder, TrailingStopOrder } from '../src/core/logicalOrders'
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const apiKey = process.env.GATE_API_KEY || ''
|
|
8
|
+
const secret = process.env.GATE_SECRET_KEY || ''
|
|
9
|
+
if (!apiKey || !secret) throw new Error('Missing GATE_API_KEY / GATE_SECRET_KEY')
|
|
10
|
+
|
|
11
|
+
const flux = new FluxAction({
|
|
12
|
+
gate: { apiKey, secret },
|
|
13
|
+
risk: { pollIntervalMs: 300 },
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
await flux.init()
|
|
17
|
+
|
|
18
|
+
const exchangeId = 'gate-spot'
|
|
19
|
+
const symbol = 'DOGE/USDT'
|
|
20
|
+
const quoteCost = 15
|
|
21
|
+
|
|
22
|
+
const open = await flux.openSpotPosition({ exchangeId, symbol, quoteCost })
|
|
23
|
+
console.log('[spot] opened', open)
|
|
24
|
+
|
|
25
|
+
const { baseQty } = await flux.quoteToBaseQty({ exchangeId, symbol, quoteCost: 3, price: open.avgPrice || undefined })
|
|
26
|
+
const limitOrder = await flux.placeSpotLimit({
|
|
27
|
+
exchangeId,
|
|
28
|
+
symbol,
|
|
29
|
+
side: 'sell',
|
|
30
|
+
baseQty,
|
|
31
|
+
price: (open.avgPrice || 0) * 1.1,
|
|
32
|
+
})
|
|
33
|
+
console.log('[spot] limit sell placed', limitOrder)
|
|
34
|
+
|
|
35
|
+
const qty = open.filledBase * 0.7
|
|
36
|
+
const baseId = `${exchangeId}:${symbol}:${Date.now()}`
|
|
37
|
+
|
|
38
|
+
const tp: TakeProfitOrder = {
|
|
39
|
+
id: baseId + ':tp',
|
|
40
|
+
exchangeId,
|
|
41
|
+
market: 'spot',
|
|
42
|
+
symbol,
|
|
43
|
+
side: 'sell',
|
|
44
|
+
qty,
|
|
45
|
+
kind: 'take-profit',
|
|
46
|
+
triggerPrice: open.avgPrice * 1.02,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const sl: StopLossOrder = {
|
|
50
|
+
id: baseId + ':sl',
|
|
51
|
+
exchangeId,
|
|
52
|
+
market: 'spot',
|
|
53
|
+
symbol,
|
|
54
|
+
side: 'sell',
|
|
55
|
+
qty,
|
|
56
|
+
kind: 'stop-loss',
|
|
57
|
+
triggerPrice: open.avgPrice * 0.95,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const ts: TrailingStopOrder = {
|
|
61
|
+
id: baseId + ':ts',
|
|
62
|
+
exchangeId,
|
|
63
|
+
market: 'spot',
|
|
64
|
+
symbol,
|
|
65
|
+
side: 'sell',
|
|
66
|
+
qty,
|
|
67
|
+
kind: 'trailing-stop',
|
|
68
|
+
callbackRate: 0.02,
|
|
69
|
+
activationPrice: open.avgPrice,
|
|
70
|
+
highWatermark: open.avgPrice,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await flux.placeTakeProfit(tp)
|
|
74
|
+
await flux.placeStopLoss(sl)
|
|
75
|
+
await flux.placeTrailingStop(ts)
|
|
76
|
+
|
|
77
|
+
console.log('[spot] exits registered', flux.getActiveLogicalOrders().map((o) => o.id))
|
|
78
|
+
while (true) await new Promise((r) => setTimeout(r, 60_000))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main().catch((e) => {
|
|
82
|
+
console.error(e)
|
|
83
|
+
process.exit(1)
|
|
84
|
+
})
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { ExchangeAdapter, PositionSide } from './core/exchange'
|
|
2
|
+
import { ExchangeRegistry } from './services/ExchangeRegistry'
|
|
3
|
+
import { InMemoryOrderStore } from './risk/OrderStore'
|
|
4
|
+
import { RiskEngine } from './risk/RiskEngine'
|
|
5
|
+
import { RiskScheduler } from './risk/RiskScheduler'
|
|
6
|
+
import type {
|
|
7
|
+
TakeProfitOrder,
|
|
8
|
+
StopLossOrder,
|
|
9
|
+
TrailingStopOrder,
|
|
10
|
+
LogicalOrder,
|
|
11
|
+
} from './core/logicalOrders'
|
|
12
|
+
import { GateSpotAdapter, GateFuturesAdapter } from './exchanges/gate'
|
|
13
|
+
import { GateClient } from './clients/GateClient'
|
|
14
|
+
|
|
15
|
+
export interface FluxActionConfig {
|
|
16
|
+
gate?: { apiKey: string; secret: string }
|
|
17
|
+
gateFutures?: { enabled?: boolean; settle?: 'usdt' | 'btc'; defaultLeverage?: number }
|
|
18
|
+
risk?: { pollIntervalMs?: number; autoStart?: boolean }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class FluxAction {
|
|
22
|
+
private registry = new ExchangeRegistry()
|
|
23
|
+
private riskEngine: RiskEngine
|
|
24
|
+
private riskScheduler: RiskScheduler
|
|
25
|
+
|
|
26
|
+
readonly gate: GateClient
|
|
27
|
+
|
|
28
|
+
constructor(cfg: FluxActionConfig) {
|
|
29
|
+
if (cfg.gate) {
|
|
30
|
+
this.registry.register(new GateSpotAdapter({
|
|
31
|
+
id: 'gate-spot',
|
|
32
|
+
apiKey: cfg.gate.apiKey,
|
|
33
|
+
secret: cfg.gate.secret,
|
|
34
|
+
}))
|
|
35
|
+
|
|
36
|
+
const wantFutures = cfg.gateFutures?.enabled ?? true
|
|
37
|
+
if (wantFutures) {
|
|
38
|
+
this.registry.register(new GateFuturesAdapter({
|
|
39
|
+
id: 'gate-futures',
|
|
40
|
+
apiKey: cfg.gate.apiKey,
|
|
41
|
+
secret: cfg.gate.secret,
|
|
42
|
+
settle: cfg.gateFutures?.settle ?? 'usdt',
|
|
43
|
+
defaultLeverage: cfg.gateFutures?.defaultLeverage,
|
|
44
|
+
}))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const store = new InMemoryOrderStore()
|
|
49
|
+
this.riskEngine = new RiskEngine(store, this.registry.asRecord())
|
|
50
|
+
this.riskScheduler = new RiskScheduler(this.riskEngine, cfg.risk?.pollIntervalMs ?? 3000)
|
|
51
|
+
if (cfg.risk?.autoStart) this.riskScheduler.start()
|
|
52
|
+
|
|
53
|
+
this.gate = new GateClient(this, { spot: 'gate-spot', futures: 'gate-futures' })
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async init(): Promise<void> {
|
|
57
|
+
await Promise.all(this.registry.list().map((a) => a.loadMarkets()))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getExchange(id: string): ExchangeAdapter | undefined {
|
|
61
|
+
return this.registry.get(id)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --------- converters ----------
|
|
65
|
+
async quoteToBaseQty(params: { exchangeId: string; symbol: string; quoteCost: number; price?: number }) {
|
|
66
|
+
const ex = this.registry.require(params.exchangeId)
|
|
67
|
+
const baseQty = await ex.quoteToBaseQty({ symbol: params.symbol, quoteCost: params.quoteCost, price: params.price })
|
|
68
|
+
return { baseQty }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async baseQtyToQuote(params: { exchangeId: string; symbol: string; baseQty: number; price?: number }) {
|
|
72
|
+
const ex = this.registry.require(params.exchangeId)
|
|
73
|
+
const quoteCost = await ex.baseQtyToQuote({ symbol: params.symbol, baseQty: params.baseQty, price: params.price })
|
|
74
|
+
return { quoteCost }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --------- universal spot ----------
|
|
78
|
+
async openSpotPosition(params: { exchangeId: string; symbol: string; quoteCost: number }) {
|
|
79
|
+
const ex = this.registry.require(params.exchangeId)
|
|
80
|
+
const order = await ex.marketBuySpot(params.symbol, params.quoteCost)
|
|
81
|
+
return { orderId: order.id, filledBase: order.amount, avgPrice: order.price ?? 0 }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async closeSpotMarket(params: { exchangeId: string; symbol: string; baseQty: number }) {
|
|
85
|
+
const ex = this.registry.require(params.exchangeId)
|
|
86
|
+
const order = await ex.marketSellSpot(params.symbol, params.baseQty)
|
|
87
|
+
return { orderId: order.id }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async placeSpotLimit(params: { exchangeId: string; symbol: string; side: 'buy'|'sell'; baseQty: number; price: number }) {
|
|
91
|
+
const ex = this.registry.require(params.exchangeId)
|
|
92
|
+
const order = await ex.limitOrderSpot(params.symbol, params.side, params.baseQty, params.price)
|
|
93
|
+
return { orderId: order.id }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// --------- universal futures ----------
|
|
97
|
+
async openFuturesPosition(params: {
|
|
98
|
+
exchangeId: string
|
|
99
|
+
symbol: string
|
|
100
|
+
side: PositionSide
|
|
101
|
+
costUSDT: number
|
|
102
|
+
leverage?: number
|
|
103
|
+
price?: number | null // null/undefined => market
|
|
104
|
+
}) {
|
|
105
|
+
const ex = this.registry.require(params.exchangeId)
|
|
106
|
+
const order = await ex.openFutures({
|
|
107
|
+
symbol: params.symbol,
|
|
108
|
+
side: params.side,
|
|
109
|
+
costUSDT: params.costUSDT,
|
|
110
|
+
leverage: params.leverage,
|
|
111
|
+
price: params.price,
|
|
112
|
+
})
|
|
113
|
+
return { orderId: order.id }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async closeFuturesPosition(params: {
|
|
117
|
+
exchangeId: string
|
|
118
|
+
symbol: string
|
|
119
|
+
side: PositionSide
|
|
120
|
+
closeMode: 'all'|'cost'
|
|
121
|
+
costUSDT?: number
|
|
122
|
+
}) {
|
|
123
|
+
const ex = this.registry.require(params.exchangeId)
|
|
124
|
+
const order = await ex.closeFutures({
|
|
125
|
+
symbol: params.symbol,
|
|
126
|
+
side: params.side,
|
|
127
|
+
closeMode: params.closeMode,
|
|
128
|
+
costUSDT: params.costUSDT,
|
|
129
|
+
})
|
|
130
|
+
return { orderId: order.id }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// --------- risk ----------
|
|
134
|
+
async placeTakeProfit(o: TakeProfitOrder) { this.placeLogical(o); return { id: o.id } }
|
|
135
|
+
async placeStopLoss(o: StopLossOrder) { this.placeLogical(o); return { id: o.id } }
|
|
136
|
+
async placeTrailingStop(o: TrailingStopOrder) { this.placeLogical(o); return { id: o.id } }
|
|
137
|
+
|
|
138
|
+
async cancelLogical(id: string) { this.riskEngine.cancel(id) }
|
|
139
|
+
getActiveLogicalOrders(): LogicalOrder[] { return this.riskEngine.list() }
|
|
140
|
+
|
|
141
|
+
startRiskPolling() { this.riskScheduler.start() }
|
|
142
|
+
stopRiskPolling() { this.riskScheduler.stop() }
|
|
143
|
+
|
|
144
|
+
private placeLogical(o: LogicalOrder) {
|
|
145
|
+
const ex = this.registry.get(o.exchangeId)
|
|
146
|
+
if (!ex) throw new Error(`no exchange ${o.exchangeId}`)
|
|
147
|
+
this.riskEngine.place(o)
|
|
148
|
+
this.riskScheduler.start()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PositionSide } from '../core/exchange'
|
|
2
|
+
import type { FluxAction } from '../FluxAction'
|
|
3
|
+
|
|
4
|
+
export class GateClient {
|
|
5
|
+
constructor(
|
|
6
|
+
private flux: FluxAction,
|
|
7
|
+
private ids: { spot: string; futures: string },
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
// converters
|
|
11
|
+
quoteToBaseQty(params: { symbol: string; quoteCost: number; price?: number }) {
|
|
12
|
+
return this.flux.quoteToBaseQty({ exchangeId: this.ids.spot, ...params })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
baseQtyToQuote(params: { symbol: string; baseQty: number; price?: number }) {
|
|
16
|
+
return this.flux.baseQtyToQuote({ exchangeId: this.ids.spot, ...params })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// spot
|
|
20
|
+
openSpotPosition(params: { symbol: string; quoteCost: number }) {
|
|
21
|
+
return this.flux.openSpotPosition({ exchangeId: this.ids.spot, ...params })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
closeSpotMarket(params: { symbol: string; baseQty: number }) {
|
|
25
|
+
return this.flux.closeSpotMarket({ exchangeId: this.ids.spot, ...params })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
placeSpotLimit(params: { symbol: string; side: 'buy' | 'sell'; baseQty: number; price: number }) {
|
|
29
|
+
return this.flux.placeSpotLimit({ exchangeId: this.ids.spot, ...params })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// futures
|
|
33
|
+
openFuturesPosition(params: {
|
|
34
|
+
symbol: string
|
|
35
|
+
side: PositionSide
|
|
36
|
+
costUSDT: number
|
|
37
|
+
leverage?: number
|
|
38
|
+
price?: number | null // null/undefined => market
|
|
39
|
+
}) {
|
|
40
|
+
return this.flux.openFuturesPosition({ exchangeId: this.ids.futures, ...params })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
closeFuturesPosition(params: { symbol: string; side: PositionSide; closeMode?: 'all' | 'cost'; costUSDT?: number }) {
|
|
44
|
+
return this.flux.closeFuturesPosition({
|
|
45
|
+
exchangeId: this.ids.futures,
|
|
46
|
+
symbol: params.symbol,
|
|
47
|
+
side: params.side,
|
|
48
|
+
closeMode: params.closeMode ?? 'all',
|
|
49
|
+
costUSDT: params.costUSDT,
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/core/exchange.ts
CHANGED
|
@@ -61,6 +61,11 @@ export interface ExchangeAdapter {
|
|
|
61
61
|
fetchOpenOrders(symbol?: string): Promise<OrderInfo[]>
|
|
62
62
|
cancelOrder(symbol: string, orderId: string): Promise<void>
|
|
63
63
|
|
|
64
|
+
// helpers
|
|
65
|
+
quoteToBaseQty(params: { symbol: string; quoteCost: number; price?: number }): Promise<number>
|
|
66
|
+
baseQtyToQuote(params: { symbol: string; baseQty: number; price?: number }): Promise<number>
|
|
67
|
+
|
|
68
|
+
// spot
|
|
64
69
|
marketBuySpot(symbol: string, quoteCost: number): Promise<OrderInfo>
|
|
65
70
|
marketSellSpot(symbol: string, baseAmount: number): Promise<OrderInfo>
|
|
66
71
|
limitOrderSpot(
|
|
@@ -70,16 +75,19 @@ export interface ExchangeAdapter {
|
|
|
70
75
|
price: number,
|
|
71
76
|
): Promise<OrderInfo>
|
|
72
77
|
|
|
78
|
+
// futures
|
|
73
79
|
openFutures(params: {
|
|
74
80
|
symbol: string
|
|
75
81
|
side: PositionSide
|
|
76
82
|
costUSDT: number
|
|
77
83
|
leverage?: number
|
|
84
|
+
price?: number | null
|
|
78
85
|
}): Promise<OrderInfo>
|
|
79
86
|
|
|
80
87
|
closeFutures(params: {
|
|
81
88
|
symbol: string
|
|
82
89
|
side: PositionSide
|
|
90
|
+
closeMode: 'all' | 'cost'
|
|
83
91
|
costUSDT?: number
|
|
84
92
|
}): Promise<OrderInfo>
|
|
85
93
|
|