openbroker 1.3.2 → 1.5.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.
- package/CHANGELOG.md +23 -0
- package/dist/auto/audit.d.ts +57 -0
- package/dist/auto/audit.d.ts.map +1 -0
- package/dist/auto/audit.js +407 -0
- package/dist/auto/cli.d.ts +2 -0
- package/dist/auto/cli.d.ts.map +1 -0
- package/dist/auto/cli.js +423 -0
- package/dist/auto/events.d.ts +11 -0
- package/dist/auto/events.d.ts.map +1 -0
- package/dist/auto/events.js +36 -0
- package/dist/auto/examples/dca.d.ts +4 -0
- package/dist/auto/examples/dca.d.ts.map +1 -0
- package/dist/auto/examples/dca.js +60 -0
- package/dist/auto/examples/funding-arb.d.ts +4 -0
- package/dist/auto/examples/funding-arb.d.ts.map +1 -0
- package/dist/auto/examples/funding-arb.js +81 -0
- package/dist/auto/examples/grid.d.ts +4 -0
- package/dist/auto/examples/grid.d.ts.map +1 -0
- package/dist/auto/examples/grid.js +114 -0
- package/dist/auto/examples/mm-maker.d.ts +4 -0
- package/dist/auto/examples/mm-maker.d.ts.map +1 -0
- package/dist/auto/examples/mm-maker.js +131 -0
- package/dist/auto/examples/mm-spread.d.ts +4 -0
- package/dist/auto/examples/mm-spread.d.ts.map +1 -0
- package/dist/auto/examples/mm-spread.js +119 -0
- package/dist/auto/examples/price-alert.d.ts +4 -0
- package/dist/auto/examples/price-alert.d.ts.map +1 -0
- package/dist/auto/examples/price-alert.js +85 -0
- package/dist/auto/keep-awake.d.ts +11 -0
- package/dist/auto/keep-awake.d.ts.map +1 -0
- package/dist/auto/keep-awake.js +70 -0
- package/dist/auto/loader.d.ts +22 -0
- package/dist/auto/loader.d.ts.map +1 -0
- package/dist/auto/loader.js +127 -0
- package/dist/auto/prune.d.ts +40 -0
- package/dist/auto/prune.d.ts.map +1 -0
- package/dist/auto/prune.js +204 -0
- package/dist/auto/registry.d.ts +24 -0
- package/dist/auto/registry.d.ts.map +1 -0
- package/dist/auto/registry.js +93 -0
- package/dist/auto/report.d.ts +3 -0
- package/dist/auto/report.d.ts.map +1 -0
- package/dist/auto/report.js +385 -0
- package/dist/auto/runtime.d.ts +33 -0
- package/dist/auto/runtime.d.ts.map +1 -0
- package/dist/auto/runtime.js +844 -0
- package/dist/auto/types.d.ts +236 -0
- package/dist/auto/types.d.ts.map +1 -0
- package/dist/auto/types.js +3 -0
- package/dist/core/client.d.ts +691 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +2061 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +143 -0
- package/dist/core/types.d.ts +228 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/utils.d.ts +61 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +142 -0
- package/dist/core/ws.d.ts +121 -0
- package/dist/core/ws.d.ts.map +1 -0
- package/dist/core/ws.js +222 -0
- package/dist/info/account.d.ts +3 -0
- package/dist/info/account.d.ts.map +1 -0
- package/dist/info/account.js +198 -0
- package/dist/info/all-markets.d.ts +3 -0
- package/dist/info/all-markets.d.ts.map +1 -0
- package/dist/info/all-markets.js +272 -0
- package/dist/info/candles.d.ts +3 -0
- package/dist/info/candles.d.ts.map +1 -0
- package/dist/info/candles.js +120 -0
- package/dist/info/fees.d.ts +3 -0
- package/dist/info/fees.d.ts.map +1 -0
- package/dist/info/fees.js +87 -0
- package/dist/info/fills.d.ts +3 -0
- package/dist/info/fills.d.ts.map +1 -0
- package/dist/info/fills.js +105 -0
- package/dist/info/funding-history.d.ts +3 -0
- package/dist/info/funding-history.d.ts.map +1 -0
- package/dist/info/funding-history.js +98 -0
- package/dist/info/funding-scan.d.ts +3 -0
- package/dist/info/funding-scan.d.ts.map +1 -0
- package/dist/info/funding-scan.js +178 -0
- package/dist/info/funding.d.ts +3 -0
- package/dist/info/funding.d.ts.map +1 -0
- package/dist/info/funding.js +158 -0
- package/dist/info/markets.d.ts +3 -0
- package/dist/info/markets.d.ts.map +1 -0
- package/dist/info/markets.js +178 -0
- package/dist/info/order-status.d.ts +3 -0
- package/dist/info/order-status.d.ts.map +1 -0
- package/dist/info/order-status.js +85 -0
- package/dist/info/orders.d.ts +3 -0
- package/dist/info/orders.d.ts.map +1 -0
- package/dist/info/orders.js +162 -0
- package/dist/info/outcomes.d.ts +3 -0
- package/dist/info/outcomes.d.ts.map +1 -0
- package/dist/info/outcomes.js +175 -0
- package/dist/info/positions.d.ts +3 -0
- package/dist/info/positions.d.ts.map +1 -0
- package/dist/info/positions.js +127 -0
- package/dist/info/rate-limit.d.ts +3 -0
- package/dist/info/rate-limit.d.ts.map +1 -0
- package/dist/info/rate-limit.js +58 -0
- package/dist/info/search-markets.d.ts +3 -0
- package/dist/info/search-markets.d.ts.map +1 -0
- package/dist/info/search-markets.js +296 -0
- package/dist/info/spot.d.ts +3 -0
- package/dist/info/spot.d.ts.map +1 -0
- package/dist/info/spot.js +192 -0
- package/dist/info/trades.d.ts +3 -0
- package/dist/info/trades.d.ts.map +1 -0
- package/dist/info/trades.js +97 -0
- package/dist/lib.d.ts +14 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +17 -0
- package/dist/operations/bracket.d.ts +28 -0
- package/dist/operations/bracket.d.ts.map +1 -0
- package/dist/operations/bracket.js +266 -0
- package/dist/operations/cancel.d.ts +3 -0
- package/dist/operations/cancel.d.ts.map +1 -0
- package/dist/operations/cancel.js +107 -0
- package/dist/operations/chase.d.ts +25 -0
- package/dist/operations/chase.d.ts.map +1 -0
- package/dist/operations/chase.js +215 -0
- package/dist/operations/limit-order.d.ts +3 -0
- package/dist/operations/limit-order.d.ts.map +1 -0
- package/dist/operations/limit-order.js +144 -0
- package/dist/operations/market-order.d.ts +3 -0
- package/dist/operations/market-order.d.ts.map +1 -0
- package/dist/operations/market-order.js +153 -0
- package/dist/operations/outcome-order.d.ts +3 -0
- package/dist/operations/outcome-order.d.ts.map +1 -0
- package/dist/operations/outcome-order.js +171 -0
- package/dist/operations/scale.d.ts +3 -0
- package/dist/operations/scale.d.ts.map +1 -0
- package/dist/operations/scale.js +212 -0
- package/dist/operations/set-tpsl.d.ts +3 -0
- package/dist/operations/set-tpsl.d.ts.map +1 -0
- package/dist/operations/set-tpsl.js +277 -0
- package/dist/operations/spot-order.d.ts +3 -0
- package/dist/operations/spot-order.d.ts.map +1 -0
- package/dist/operations/spot-order.js +173 -0
- package/dist/operations/trigger-order.d.ts +3 -0
- package/dist/operations/trigger-order.d.ts.map +1 -0
- package/dist/operations/trigger-order.js +177 -0
- package/dist/operations/twap-cancel.d.ts +3 -0
- package/dist/operations/twap-cancel.d.ts.map +1 -0
- package/dist/operations/twap-cancel.js +57 -0
- package/dist/operations/twap-status.d.ts +3 -0
- package/dist/operations/twap-status.d.ts.map +1 -0
- package/dist/operations/twap-status.js +81 -0
- package/dist/operations/twap.d.ts +3 -0
- package/dist/operations/twap.d.ts.map +1 -0
- package/dist/operations/twap.js +124 -0
- package/dist/setup/approve-builder.d.ts +3 -0
- package/dist/setup/approve-builder.d.ts.map +1 -0
- package/dist/setup/approve-builder.js +155 -0
- package/dist/setup/env.d.ts +4 -0
- package/dist/setup/env.d.ts.map +1 -0
- package/dist/setup/env.js +8 -0
- package/dist/setup/onboard.d.ts +10 -0
- package/dist/setup/onboard.d.ts.map +1 -0
- package/dist/setup/onboard.js +462 -0
- package/package.json +10 -4
- package/scripts/core/client.ts +19 -1
- package/scripts/core/types.ts +7 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Grid Trading — Place buy/sell orders at evenly spaced price levels
|
|
2
|
+
export const config = {
|
|
3
|
+
description: 'Grid trading — buy/sell orders at evenly spaced price levels',
|
|
4
|
+
fields: {
|
|
5
|
+
coin: { type: 'string', description: 'Asset to trade', default: 'HYPE' },
|
|
6
|
+
lower: { type: 'number', description: 'Lower price bound (default: auto -5% from mid)', default: 0 },
|
|
7
|
+
upper: { type: 'number', description: 'Upper price bound (default: auto +5% from mid)', default: 0 },
|
|
8
|
+
grids: { type: 'number', description: 'Number of grid levels', default: 10 },
|
|
9
|
+
size: { type: 'number', description: 'Size per level in base asset', default: 0.1 },
|
|
10
|
+
mode: { type: 'string', description: 'Grid mode: neutral, long, or short', default: 'neutral' },
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export default function grid(api) {
|
|
14
|
+
const COIN = api.state.get('coin', 'HYPE');
|
|
15
|
+
const GRIDS = api.state.get('grids', 10);
|
|
16
|
+
const SIZE = api.state.get('size', 0.1);
|
|
17
|
+
const MODE = api.state.get('mode', 'neutral');
|
|
18
|
+
let levels = [];
|
|
19
|
+
let realizedPnl = api.state.get('realizedPnl', 0);
|
|
20
|
+
let initialized = false;
|
|
21
|
+
api.onStart(async () => {
|
|
22
|
+
const mids = await api.client.getAllMids();
|
|
23
|
+
const mid = parseFloat(mids[COIN]);
|
|
24
|
+
if (!mid) {
|
|
25
|
+
api.log.error(`No price for ${COIN}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const lower = api.state.get('lower', mid * 0.95);
|
|
29
|
+
const upper = api.state.get('upper', mid * 1.05);
|
|
30
|
+
const spacing = (upper - lower) / (GRIDS - 1);
|
|
31
|
+
api.log.info(`Grid: ${COIN} ${api.utils.formatUsd(lower)}-${api.utils.formatUsd(upper)} | ${GRIDS} levels | ${SIZE}/level | ${MODE}`);
|
|
32
|
+
// Build and place grid
|
|
33
|
+
for (let i = 0; i < GRIDS; i++) {
|
|
34
|
+
const price = lower + spacing * i;
|
|
35
|
+
let side;
|
|
36
|
+
if (MODE === 'long')
|
|
37
|
+
side = 'buy';
|
|
38
|
+
else if (MODE === 'short')
|
|
39
|
+
side = 'sell';
|
|
40
|
+
else
|
|
41
|
+
side = price < mid ? 'buy' : 'sell';
|
|
42
|
+
// Skip levels too close to mid
|
|
43
|
+
if (Math.abs(price - mid) / mid < 0.001)
|
|
44
|
+
continue;
|
|
45
|
+
const level = { price, side, size: SIZE };
|
|
46
|
+
const response = await api.client.limitOrder(COIN, side === 'buy', SIZE, price, 'Gtc', false);
|
|
47
|
+
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
48
|
+
const status = response.response.data.statuses[0];
|
|
49
|
+
if (status?.resting) {
|
|
50
|
+
level.oid = status.resting.oid;
|
|
51
|
+
api.log.info(`${side.toUpperCase()} @ ${api.utils.formatUsd(price)} — OID: ${level.oid}`);
|
|
52
|
+
}
|
|
53
|
+
else if (status?.filled) {
|
|
54
|
+
api.log.info(`${side.toUpperCase()} @ ${api.utils.formatUsd(price)} — filled immediately`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
levels.push(level);
|
|
58
|
+
await api.utils.sleep(100);
|
|
59
|
+
}
|
|
60
|
+
initialized = true;
|
|
61
|
+
api.log.info(`Grid initialized: ${levels.filter(l => l.oid).length} open orders`);
|
|
62
|
+
});
|
|
63
|
+
// Monitor fills and replace with opposite orders
|
|
64
|
+
api.on('tick', async () => {
|
|
65
|
+
if (!initialized || levels.length === 0)
|
|
66
|
+
return;
|
|
67
|
+
const openOrders = await api.client.getOpenOrders();
|
|
68
|
+
const openOids = new Set(openOrders.filter(o => o.coin === COIN).map(o => o.oid));
|
|
69
|
+
const mids = await api.client.getAllMids();
|
|
70
|
+
const mid = parseFloat(mids[COIN]);
|
|
71
|
+
const lower = api.state.get('lower', mid * 0.95);
|
|
72
|
+
const upper = api.state.get('upper', mid * 1.05);
|
|
73
|
+
const spacing = (upper - lower) / (GRIDS - 1);
|
|
74
|
+
for (const level of levels) {
|
|
75
|
+
if (!level.oid || openOids.has(level.oid))
|
|
76
|
+
continue;
|
|
77
|
+
// Order was filled
|
|
78
|
+
api.log.info(`${level.side.toUpperCase()} FILLED @ ${api.utils.formatUsd(level.price)}`);
|
|
79
|
+
level.oid = undefined;
|
|
80
|
+
if (MODE !== 'neutral')
|
|
81
|
+
continue;
|
|
82
|
+
// Place opposite order
|
|
83
|
+
const oppositeSide = level.side === 'buy' ? 'sell' : 'buy';
|
|
84
|
+
const oppositePrice = level.side === 'buy' ? level.price + spacing : level.price - spacing;
|
|
85
|
+
if (oppositePrice < lower || oppositePrice > upper)
|
|
86
|
+
continue;
|
|
87
|
+
const response = await api.client.limitOrder(COIN, oppositeSide === 'buy', SIZE, oppositePrice, 'Gtc', false);
|
|
88
|
+
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
89
|
+
const status = response.response.data.statuses[0];
|
|
90
|
+
if (status?.resting) {
|
|
91
|
+
const newLevel = { price: oppositePrice, side: oppositeSide, size: SIZE, oid: status.resting.oid };
|
|
92
|
+
levels.push(newLevel);
|
|
93
|
+
if (level.side === 'buy') {
|
|
94
|
+
realizedPnl += (oppositePrice - level.price) * SIZE;
|
|
95
|
+
}
|
|
96
|
+
api.state.set('realizedPnl', realizedPnl);
|
|
97
|
+
api.log.info(`Placed ${oppositeSide.toUpperCase()} @ ${api.utils.formatUsd(oppositePrice)} | PnL: ${api.utils.formatUsd(realizedPnl)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
api.onStop(async () => {
|
|
103
|
+
api.log.info('Cancelling grid orders...');
|
|
104
|
+
for (const level of levels) {
|
|
105
|
+
if (level.oid) {
|
|
106
|
+
try {
|
|
107
|
+
await api.client.cancel(COIN, level.oid);
|
|
108
|
+
}
|
|
109
|
+
catch { /* may be filled */ }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
api.log.info(`Grid stopped. Realized PnL: ${api.utils.formatUsd(realizedPnl)}`);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mm-maker.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/mm-maker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,eAAO,MAAM,MAAM,EAAE,gBASpB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,GAAG,EAAE,aAAa,QA6GjD"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// Maker-Only Market Making — ALO orders that guarantee maker rebates
|
|
2
|
+
export const config = {
|
|
3
|
+
description: 'Maker-only market making — ALO orders for guaranteed maker rebates',
|
|
4
|
+
fields: {
|
|
5
|
+
coin: { type: 'string', description: 'Asset to market make', default: 'HYPE' },
|
|
6
|
+
size: { type: 'number', description: 'Order size on each side (base asset)', default: 0.1 },
|
|
7
|
+
offsetBps: { type: 'number', description: 'Offset from best bid/ask in bps', default: 1 },
|
|
8
|
+
maxPosition: { type: 'number', description: 'Max net position (default: 3x size)', default: 0.3 },
|
|
9
|
+
skewFactor: { type: 'number', description: 'Inventory skew aggressiveness', default: 2.0 },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
export default function mmMaker(api) {
|
|
13
|
+
const COIN = api.state.get('coin', 'HYPE');
|
|
14
|
+
const SIZE = api.state.get('size', 0.1);
|
|
15
|
+
const OFFSET_BPS = api.state.get('offsetBps', 1);
|
|
16
|
+
const MAX_POS = api.state.get('maxPosition', SIZE * 3);
|
|
17
|
+
const SKEW = api.state.get('skewFactor', 2.0);
|
|
18
|
+
let bidOid;
|
|
19
|
+
let askOid;
|
|
20
|
+
let bidPrice = 0;
|
|
21
|
+
let askPrice = 0;
|
|
22
|
+
let totalBought = 0;
|
|
23
|
+
let totalSold = 0;
|
|
24
|
+
let totalBuyCost = 0;
|
|
25
|
+
let totalSellRevenue = 0;
|
|
26
|
+
let rejections = 0;
|
|
27
|
+
const offsetFraction = OFFSET_BPS / 10000;
|
|
28
|
+
api.onStart(() => {
|
|
29
|
+
api.log.info(`Maker MM: ${COIN} | ${SIZE}/side | ${OFFSET_BPS}bps offset | ALO only`);
|
|
30
|
+
});
|
|
31
|
+
api.on('tick', async () => {
|
|
32
|
+
const book = await api.client.getL2Book(COIN);
|
|
33
|
+
if (book.bestBid === 0 || book.bestAsk === 0)
|
|
34
|
+
return;
|
|
35
|
+
// Get position
|
|
36
|
+
const userState = await api.client.getUserState();
|
|
37
|
+
const pos = userState.assetPositions.find(p => p.position.coin === COIN);
|
|
38
|
+
const position = pos ? parseFloat(pos.position.szi) : 0;
|
|
39
|
+
// Check fills
|
|
40
|
+
const openOrders = await api.client.getOpenOrders();
|
|
41
|
+
const openOids = new Set(openOrders.filter(o => o.coin === COIN).map(o => o.oid));
|
|
42
|
+
if (bidOid && !openOids.has(bidOid)) {
|
|
43
|
+
totalBought += SIZE;
|
|
44
|
+
totalBuyCost += bidPrice * SIZE;
|
|
45
|
+
api.log.info(`BID FILLED @ ${api.utils.formatUsd(bidPrice)} | Pos: ${position.toFixed(4)} | +rebate`);
|
|
46
|
+
bidOid = undefined;
|
|
47
|
+
}
|
|
48
|
+
if (askOid && !openOids.has(askOid)) {
|
|
49
|
+
totalSold += SIZE;
|
|
50
|
+
totalSellRevenue += askPrice * SIZE;
|
|
51
|
+
api.log.info(`ASK FILLED @ ${api.utils.formatUsd(askPrice)} | Pos: ${position.toFixed(4)} | +rebate`);
|
|
52
|
+
askOid = undefined;
|
|
53
|
+
}
|
|
54
|
+
// Inventory skew
|
|
55
|
+
const ratio = Math.max(-1, Math.min(1, position / MAX_POS));
|
|
56
|
+
const bidSkewMult = 1 + ratio * SKEW;
|
|
57
|
+
const askSkewMult = 1 - ratio * SKEW;
|
|
58
|
+
const targetBid = book.bestBid * (1 - offsetFraction * Math.max(0.1, bidSkewMult));
|
|
59
|
+
const targetAsk = book.bestAsk * (1 + offsetFraction * Math.max(0.1, askSkewMult));
|
|
60
|
+
// Ensure no crossing
|
|
61
|
+
const safeBid = Math.min(targetBid, book.bestAsk * 0.9999);
|
|
62
|
+
const safeAsk = Math.max(targetAsk, book.bestBid * 1.0001);
|
|
63
|
+
const shouldBid = position < MAX_POS;
|
|
64
|
+
const shouldAsk = position > -MAX_POS;
|
|
65
|
+
// Cancel stale quotes
|
|
66
|
+
if (bidOid) {
|
|
67
|
+
const drift = Math.abs(bidPrice - safeBid) / book.midPrice;
|
|
68
|
+
if (drift > 0.0005 || !shouldBid) {
|
|
69
|
+
try {
|
|
70
|
+
await api.client.cancel(COIN, bidOid);
|
|
71
|
+
}
|
|
72
|
+
catch { /* */ }
|
|
73
|
+
bidOid = undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (askOid) {
|
|
77
|
+
const drift = Math.abs(askPrice - safeAsk) / book.midPrice;
|
|
78
|
+
if (drift > 0.0005 || !shouldAsk) {
|
|
79
|
+
try {
|
|
80
|
+
await api.client.cancel(COIN, askOid);
|
|
81
|
+
}
|
|
82
|
+
catch { /* */ }
|
|
83
|
+
askOid = undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Place ALO bid
|
|
87
|
+
if (shouldBid && !bidOid && safeBid < book.bestAsk) {
|
|
88
|
+
const resp = await api.client.limitOrder(COIN, true, SIZE, safeBid, 'Alo', false);
|
|
89
|
+
if (resp.status === 'ok' && resp.response && typeof resp.response === 'object') {
|
|
90
|
+
const s = resp.response.data.statuses[0];
|
|
91
|
+
if (s?.resting) {
|
|
92
|
+
bidOid = s.resting.oid;
|
|
93
|
+
bidPrice = safeBid;
|
|
94
|
+
}
|
|
95
|
+
else if (s?.error) {
|
|
96
|
+
rejections++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Place ALO ask
|
|
101
|
+
if (shouldAsk && !askOid && safeAsk > book.bestBid) {
|
|
102
|
+
const resp = await api.client.limitOrder(COIN, false, SIZE, safeAsk, 'Alo', false);
|
|
103
|
+
if (resp.status === 'ok' && resp.response && typeof resp.response === 'object') {
|
|
104
|
+
const s = resp.response.data.statuses[0];
|
|
105
|
+
if (s?.resting) {
|
|
106
|
+
askOid = s.resting.oid;
|
|
107
|
+
askPrice = safeAsk;
|
|
108
|
+
}
|
|
109
|
+
else if (s?.error) {
|
|
110
|
+
rejections++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
api.onStop(async () => {
|
|
116
|
+
if (bidOid)
|
|
117
|
+
try {
|
|
118
|
+
await api.client.cancel(COIN, bidOid);
|
|
119
|
+
}
|
|
120
|
+
catch { /* */ }
|
|
121
|
+
if (askOid)
|
|
122
|
+
try {
|
|
123
|
+
await api.client.cancel(COIN, askOid);
|
|
124
|
+
}
|
|
125
|
+
catch { /* */ }
|
|
126
|
+
const pnl = totalSellRevenue - totalBuyCost;
|
|
127
|
+
const volume = totalBuyCost + totalSellRevenue;
|
|
128
|
+
const rebates = volume * 0.00003;
|
|
129
|
+
api.log.info(`Maker MM stopped. PnL: ${api.utils.formatUsd(pnl)} | Rebates: ~${api.utils.formatUsd(rebates)} | Rejections: ${rejections}`);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mm-spread.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/mm-spread.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,eAAO,MAAM,MAAM,EAAE,gBASpB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,aAAa,QAmGlD"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Market Making (Spread) — Quote bid/ask around mid with inventory skewing
|
|
2
|
+
export const config = {
|
|
3
|
+
description: 'Market making — quote bid/ask around mid price with inventory skewing',
|
|
4
|
+
fields: {
|
|
5
|
+
coin: { type: 'string', description: 'Asset to market make', default: 'HYPE' },
|
|
6
|
+
size: { type: 'number', description: 'Order size on each side (base asset)', default: 0.1 },
|
|
7
|
+
spreadBps: { type: 'number', description: 'Spread in bps from mid price', default: 10 },
|
|
8
|
+
maxPosition: { type: 'number', description: 'Max net position before pausing side (default: 3x size)', default: 0.3 },
|
|
9
|
+
skewFactor: { type: 'number', description: 'Inventory skew aggressiveness', default: 2.0 },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
export default function mmSpread(api) {
|
|
13
|
+
const COIN = api.state.get('coin', 'HYPE');
|
|
14
|
+
const SIZE = api.state.get('size', 0.1);
|
|
15
|
+
const SPREAD_BPS = api.state.get('spreadBps', 10);
|
|
16
|
+
const MAX_POS = api.state.get('maxPosition', SIZE * 3);
|
|
17
|
+
const SKEW = api.state.get('skewFactor', 2.0);
|
|
18
|
+
let bidOid;
|
|
19
|
+
let askOid;
|
|
20
|
+
let bidPrice = 0;
|
|
21
|
+
let askPrice = 0;
|
|
22
|
+
let totalBought = 0;
|
|
23
|
+
let totalSold = 0;
|
|
24
|
+
let totalBuyCost = 0;
|
|
25
|
+
let totalSellRevenue = 0;
|
|
26
|
+
const halfSpread = SPREAD_BPS / 10000 / 2;
|
|
27
|
+
api.onStart(() => {
|
|
28
|
+
api.log.info(`MM Spread: ${COIN} | ${SIZE}/side | ${SPREAD_BPS}bps | Max: ±${MAX_POS}`);
|
|
29
|
+
});
|
|
30
|
+
api.on('tick', async () => {
|
|
31
|
+
const mids = await api.client.getAllMids();
|
|
32
|
+
const mid = parseFloat(mids[COIN]);
|
|
33
|
+
if (!mid)
|
|
34
|
+
return;
|
|
35
|
+
// Get position
|
|
36
|
+
const userState = await api.client.getUserState();
|
|
37
|
+
const pos = userState.assetPositions.find(p => p.position.coin === COIN);
|
|
38
|
+
const position = pos ? parseFloat(pos.position.szi) : 0;
|
|
39
|
+
// Check fills
|
|
40
|
+
const openOrders = await api.client.getOpenOrders();
|
|
41
|
+
const openOids = new Set(openOrders.filter(o => o.coin === COIN).map(o => o.oid));
|
|
42
|
+
if (bidOid && !openOids.has(bidOid)) {
|
|
43
|
+
totalBought += SIZE;
|
|
44
|
+
totalBuyCost += bidPrice * SIZE;
|
|
45
|
+
api.log.info(`BID FILLED @ ${api.utils.formatUsd(bidPrice)} | Pos: ${position.toFixed(4)}`);
|
|
46
|
+
bidOid = undefined;
|
|
47
|
+
}
|
|
48
|
+
if (askOid && !openOids.has(askOid)) {
|
|
49
|
+
totalSold += SIZE;
|
|
50
|
+
totalSellRevenue += askPrice * SIZE;
|
|
51
|
+
api.log.info(`ASK FILLED @ ${api.utils.formatUsd(askPrice)} | Pos: ${position.toFixed(4)}`);
|
|
52
|
+
askOid = undefined;
|
|
53
|
+
}
|
|
54
|
+
// Inventory skew
|
|
55
|
+
const ratio = Math.max(-1, Math.min(1, position / MAX_POS));
|
|
56
|
+
const bidSkew = halfSpread * (1 + ratio * SKEW);
|
|
57
|
+
const askSkew = halfSpread * (1 - ratio * SKEW);
|
|
58
|
+
const targetBid = mid * (1 - Math.max(0.0001, bidSkew));
|
|
59
|
+
const targetAsk = mid * (1 + Math.max(0.0001, askSkew));
|
|
60
|
+
const shouldBid = position < MAX_POS;
|
|
61
|
+
const shouldAsk = position > -MAX_POS;
|
|
62
|
+
// Cancel stale quotes
|
|
63
|
+
if (bidOid) {
|
|
64
|
+
const drift = Math.abs(bidPrice - targetBid) / mid;
|
|
65
|
+
if (drift > 0.001 || !shouldBid) {
|
|
66
|
+
try {
|
|
67
|
+
await api.client.cancel(COIN, bidOid);
|
|
68
|
+
}
|
|
69
|
+
catch { /* */ }
|
|
70
|
+
bidOid = undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (askOid) {
|
|
74
|
+
const drift = Math.abs(askPrice - targetAsk) / mid;
|
|
75
|
+
if (drift > 0.001 || !shouldAsk) {
|
|
76
|
+
try {
|
|
77
|
+
await api.client.cancel(COIN, askOid);
|
|
78
|
+
}
|
|
79
|
+
catch { /* */ }
|
|
80
|
+
askOid = undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Place new quotes
|
|
84
|
+
if (shouldBid && !bidOid) {
|
|
85
|
+
const resp = await api.client.limitOrder(COIN, true, SIZE, targetBid, 'Gtc', false);
|
|
86
|
+
if (resp.status === 'ok' && resp.response && typeof resp.response === 'object') {
|
|
87
|
+
const s = resp.response.data.statuses[0];
|
|
88
|
+
if (s?.resting) {
|
|
89
|
+
bidOid = s.resting.oid;
|
|
90
|
+
bidPrice = targetBid;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (shouldAsk && !askOid) {
|
|
95
|
+
const resp = await api.client.limitOrder(COIN, false, SIZE, targetAsk, 'Gtc', false);
|
|
96
|
+
if (resp.status === 'ok' && resp.response && typeof resp.response === 'object') {
|
|
97
|
+
const s = resp.response.data.statuses[0];
|
|
98
|
+
if (s?.resting) {
|
|
99
|
+
askOid = s.resting.oid;
|
|
100
|
+
askPrice = targetAsk;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
api.onStop(async () => {
|
|
106
|
+
if (bidOid)
|
|
107
|
+
try {
|
|
108
|
+
await api.client.cancel(COIN, bidOid);
|
|
109
|
+
}
|
|
110
|
+
catch { /* */ }
|
|
111
|
+
if (askOid)
|
|
112
|
+
try {
|
|
113
|
+
await api.client.cancel(COIN, askOid);
|
|
114
|
+
}
|
|
115
|
+
catch { /* */ }
|
|
116
|
+
const pnl = totalSellRevenue - totalBuyCost;
|
|
117
|
+
api.log.info(`MM stopped. Bought: ${totalBought.toFixed(6)} | Sold: ${totalSold.toFixed(6)} | PnL: ${api.utils.formatUsd(pnl)}`);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price-alert.d.ts","sourceRoot":"","sources":["../../../scripts/auto/examples/price-alert.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,eAAO,MAAM,MAAM,EAAE,gBAQpB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,GAAG,EAAE,aAAa,QAgFpD"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Price Alert — Real-time price monitoring via WebSocket
|
|
2
|
+
// Showcases WebSocket-driven price_change and order_update events
|
|
3
|
+
export const config = {
|
|
4
|
+
description: 'Real-time price alerts via WebSocket — log price moves and order updates',
|
|
5
|
+
fields: {
|
|
6
|
+
coin: { type: 'string', description: 'Asset to monitor', default: 'BTC' },
|
|
7
|
+
threshold: { type: 'number', description: 'Min price change % to alert on', default: 0.1 },
|
|
8
|
+
above: { type: 'number', description: 'Alert when price goes above this level (0 = disabled)', default: 0 },
|
|
9
|
+
below: { type: 'number', description: 'Alert when price goes below this level (0 = disabled)', default: 0 },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
export default function priceAlert(api) {
|
|
13
|
+
const COIN = api.state.get('coin', 'BTC');
|
|
14
|
+
const THRESHOLD = api.state.get('threshold', 0.1);
|
|
15
|
+
const ABOVE = api.state.get('above', 0);
|
|
16
|
+
const BELOW = api.state.get('below', 0);
|
|
17
|
+
let alertCount = 0;
|
|
18
|
+
let lastAlertPrice = 0;
|
|
19
|
+
let aboveTriggered = false;
|
|
20
|
+
let belowTriggered = false;
|
|
21
|
+
api.onStart(() => {
|
|
22
|
+
api.log.info(`Monitoring ${COIN} via WebSocket`);
|
|
23
|
+
api.log.info(`Threshold: ${THRESHOLD}% change`);
|
|
24
|
+
if (ABOVE > 0)
|
|
25
|
+
api.log.info(`Alert above: $${ABOVE}`);
|
|
26
|
+
if (BELOW > 0)
|
|
27
|
+
api.log.info(`Alert below: $${BELOW}`);
|
|
28
|
+
});
|
|
29
|
+
// Real-time price changes via WebSocket
|
|
30
|
+
api.on('price_change', ({ coin, oldPrice, newPrice, changePct }) => {
|
|
31
|
+
if (coin !== COIN)
|
|
32
|
+
return;
|
|
33
|
+
// Threshold alerts — fires when move exceeds configured %
|
|
34
|
+
if (Math.abs(changePct) >= THRESHOLD) {
|
|
35
|
+
const dir = changePct > 0 ? 'UP' : 'DOWN';
|
|
36
|
+
api.log.info(`${COIN} ${dir} ${changePct.toFixed(3)}%: $${oldPrice.toFixed(2)} -> $${newPrice.toFixed(2)}`);
|
|
37
|
+
alertCount++;
|
|
38
|
+
lastAlertPrice = newPrice;
|
|
39
|
+
}
|
|
40
|
+
// Level alerts — fires once when price crosses a level, resets when it crosses back
|
|
41
|
+
if (ABOVE > 0) {
|
|
42
|
+
if (newPrice >= ABOVE && !aboveTriggered) {
|
|
43
|
+
aboveTriggered = true;
|
|
44
|
+
api.log.info(`${COIN} ABOVE $${ABOVE}: now $${newPrice.toFixed(2)}`);
|
|
45
|
+
api.publish(`${COIN} broke above $${ABOVE} — now $${newPrice.toFixed(2)}`, { name: 'price-alert' });
|
|
46
|
+
}
|
|
47
|
+
else if (newPrice < ABOVE) {
|
|
48
|
+
aboveTriggered = false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (BELOW > 0) {
|
|
52
|
+
if (newPrice <= BELOW && !belowTriggered) {
|
|
53
|
+
belowTriggered = true;
|
|
54
|
+
api.log.info(`${COIN} BELOW $${BELOW}: now $${newPrice.toFixed(2)}`);
|
|
55
|
+
api.publish(`${COIN} dropped below $${BELOW} — now $${newPrice.toFixed(2)}`, { name: 'price-alert' });
|
|
56
|
+
}
|
|
57
|
+
else if (newPrice > BELOW) {
|
|
58
|
+
belowTriggered = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// Real-time order lifecycle via WebSocket
|
|
63
|
+
api.on('order_update', ({ coin, oid, side, size, price, status }) => {
|
|
64
|
+
if (status === 'filled') {
|
|
65
|
+
api.log.info(`ORDER FILLED: ${side.toUpperCase()} ${size} ${coin} @ $${price.toFixed(2)} (oid: ${oid})`);
|
|
66
|
+
}
|
|
67
|
+
else if (status === 'canceled' || status.includes('Canceled') || status.includes('Rejected')) {
|
|
68
|
+
api.log.warn(`ORDER ${status.toUpperCase()}: ${side} ${size} ${coin} @ $${price.toFixed(2)} (oid: ${oid})`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Liquidation alerts via WebSocket
|
|
72
|
+
api.on('liquidation', ({ liquidatedNtlPos, liquidatedAccountValue }) => {
|
|
73
|
+
api.log.error(`LIQUIDATION: $${liquidatedNtlPos.toFixed(2)} notional, account value: $${liquidatedAccountValue.toFixed(2)}`);
|
|
74
|
+
api.publish(`LIQUIDATED: $${liquidatedNtlPos.toFixed(2)} notional, account value: $${liquidatedAccountValue.toFixed(2)}`, { name: 'liquidation-alert' });
|
|
75
|
+
});
|
|
76
|
+
// Periodic summary via REST heartbeat
|
|
77
|
+
api.on('tick', ({ pollCount }) => {
|
|
78
|
+
if (pollCount % 10 === 0 && alertCount > 0) {
|
|
79
|
+
api.log.info(`Summary: ${alertCount} alerts fired, last price: $${lastAlertPrice.toFixed(2)}`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
api.onStop(() => {
|
|
83
|
+
api.log.info(`Stopped. Total alerts: ${alertCount}`);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type KeepAwakeLogger = {
|
|
2
|
+
info(message: string): void;
|
|
3
|
+
warn(message: string): void;
|
|
4
|
+
};
|
|
5
|
+
export interface KeepAwakeHandle {
|
|
6
|
+
backend: string;
|
|
7
|
+
stop(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function startKeepAwake(reason: string, log: KeepAwakeLogger): KeepAwakeHandle | null;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=keep-awake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keep-awake.d.ts","sourceRoot":"","sources":["../../scripts/auto/keep-awake.ts"],"names":[],"mappings":"AAEA,KAAK,eAAe,GAAG;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,IAAI,IAAI,CAAC;CACd;AA6ED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,eAAe,GAAG,IAAI,CAY3F"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
function createHandle(child, backend, log) {
|
|
3
|
+
let stopping = false;
|
|
4
|
+
child.once('error', (error) => {
|
|
5
|
+
if (!stopping) {
|
|
6
|
+
log.warn(`keep-awake unavailable via ${backend}: ${error.message}`);
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
child.once('exit', (code, signal) => {
|
|
10
|
+
if (!stopping) {
|
|
11
|
+
const suffix = signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`;
|
|
12
|
+
log.warn(`keep-awake helper ${backend} exited unexpectedly (${suffix}); host may sleep.`);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
backend,
|
|
17
|
+
stop() {
|
|
18
|
+
stopping = true;
|
|
19
|
+
if (!child.killed)
|
|
20
|
+
child.kill();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function startDarwin(log) {
|
|
25
|
+
const child = spawn('caffeinate', ['-i', '-w', String(process.pid)], {
|
|
26
|
+
stdio: 'ignore',
|
|
27
|
+
});
|
|
28
|
+
return createHandle(child, 'caffeinate', log);
|
|
29
|
+
}
|
|
30
|
+
function startLinux(reason, log) {
|
|
31
|
+
const child = spawn('systemd-inhibit', [
|
|
32
|
+
'--what=sleep',
|
|
33
|
+
'--who=OpenBroker',
|
|
34
|
+
`--why=${reason}`,
|
|
35
|
+
'--mode=block',
|
|
36
|
+
'sh',
|
|
37
|
+
'-c',
|
|
38
|
+
'while kill -0 "$1" 2>/dev/null; do sleep 30; done',
|
|
39
|
+
'sh',
|
|
40
|
+
String(process.pid),
|
|
41
|
+
], { stdio: 'ignore' });
|
|
42
|
+
return createHandle(child, 'systemd-inhibit', log);
|
|
43
|
+
}
|
|
44
|
+
function startWindows(log) {
|
|
45
|
+
const script = [
|
|
46
|
+
'Add-Type -Namespace OpenBroker -Name Native -MemberDefinition',
|
|
47
|
+
'\'"[DllImport(\\\"kernel32.dll\\\")] public static extern uint SetThreadExecutionState(uint esFlags);"\';',
|
|
48
|
+
'[OpenBroker.Native]::SetThreadExecutionState(0x80000001) | Out-Null;',
|
|
49
|
+
`Wait-Process -Id ${process.pid};`,
|
|
50
|
+
'[OpenBroker.Native]::SetThreadExecutionState(0x80000000) | Out-Null;',
|
|
51
|
+
].join(' ');
|
|
52
|
+
const child = spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
53
|
+
stdio: 'ignore',
|
|
54
|
+
windowsHide: true,
|
|
55
|
+
});
|
|
56
|
+
return createHandle(child, 'SetThreadExecutionState', log);
|
|
57
|
+
}
|
|
58
|
+
export function startKeepAwake(reason, log) {
|
|
59
|
+
switch (process.platform) {
|
|
60
|
+
case 'darwin':
|
|
61
|
+
return startDarwin(log);
|
|
62
|
+
case 'linux':
|
|
63
|
+
return startLinux(reason, log);
|
|
64
|
+
case 'win32':
|
|
65
|
+
return startWindows(log);
|
|
66
|
+
default:
|
|
67
|
+
log.warn(`keep-awake is not supported on platform ${process.platform}; host may sleep.`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AutomationFactory, AutomationConfig } from './types.js';
|
|
2
|
+
/** Resolve a script path from a name or path */
|
|
3
|
+
export declare function resolveScriptPath(nameOrPath: string): string;
|
|
4
|
+
/** Resolve a bundled example by name */
|
|
5
|
+
export declare function resolveExamplePath(name: string): string;
|
|
6
|
+
/** List bundled example automations */
|
|
7
|
+
export declare function listExamples(): Array<{
|
|
8
|
+
name: string;
|
|
9
|
+
path: string;
|
|
10
|
+
}>;
|
|
11
|
+
/** Load config metadata from all bundled examples */
|
|
12
|
+
export declare function loadExampleConfigs(): Promise<Record<string, AutomationConfig>>;
|
|
13
|
+
/** Load an automation module and validate the default export */
|
|
14
|
+
export declare function loadAutomation(scriptPath: string): Promise<AutomationFactory>;
|
|
15
|
+
/** List available automation scripts in ~/.openbroker/automations/ */
|
|
16
|
+
export declare function listAutomations(): Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
path: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** Ensure the automations directory exists */
|
|
21
|
+
export declare function ensureAutomationsDir(): void;
|
|
22
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../scripts/auto/loader.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAQtE,gDAAgD;AAChD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA2B5D;AAED,wCAAwC;AACxC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOvD;AAED,uCAAuC;AACvC,wBAAgB,YAAY,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CASpE;AAED,qDAAqD;AACrD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAiBpF;AAmCD,gEAAgE;AAChE,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAenF;AAED,sEAAsE;AACtE,wBAAgB,eAAe,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CASvE;AAED,8CAA8C;AAC9C,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|