openbroker 1.0.67 → 1.0.69
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/SKILL.md +102 -57
- package/bin/cli.ts +2 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -9
- package/scripts/auto/cli.ts +114 -20
- package/scripts/auto/examples/dca.ts +72 -0
- package/scripts/auto/examples/funding-arb.ts +98 -0
- package/scripts/auto/examples/grid.ts +135 -0
- package/scripts/auto/examples/mm-maker.ts +125 -0
- package/scripts/auto/examples/mm-spread.ts +115 -0
- package/scripts/auto/loader.ts +47 -1
- package/scripts/auto/runtime.ts +13 -0
- package/scripts/auto/types.ts +14 -0
- package/scripts/plugin/tools.ts +20 -7
- package/scripts/strategies/dca.ts +0 -292
- package/scripts/strategies/funding-arb.ts +0 -352
- package/scripts/strategies/grid.ts +0 -397
- package/scripts/strategies/mm-maker.ts +0 -411
- package/scripts/strategies/mm-spread.ts +0 -402
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
// Maker-Only Market Making - Always provide liquidity, never take
|
|
3
|
-
|
|
4
|
-
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatUsd, parseArgs, sleep } from '../core/utils.js';
|
|
6
|
-
|
|
7
|
-
function printUsage() {
|
|
8
|
-
console.log(`
|
|
9
|
-
Open Broker - Maker-Only Market Making
|
|
10
|
-
======================================
|
|
11
|
-
|
|
12
|
-
Provide liquidity using ALO (Add Liquidity Only) orders that are REJECTED
|
|
13
|
-
if they would cross the spread. This guarantees you always earn maker rebates
|
|
14
|
-
(~0.3 bps) instead of paying taker fees.
|
|
15
|
-
|
|
16
|
-
Usage:
|
|
17
|
-
npx tsx scripts/strategies/mm-maker.ts --coin <COIN> --size <SIZE> --offset <BPS>
|
|
18
|
-
|
|
19
|
-
Options:
|
|
20
|
-
--coin Asset to market make (e.g., ETH, BTC)
|
|
21
|
-
--size Order size on each side (in base asset)
|
|
22
|
-
--offset Offset from best bid/ask in bps (default: 1)
|
|
23
|
-
--max-position Maximum net position (default: 3x size)
|
|
24
|
-
--skew-factor How aggressively to skew for inventory (default: 2.0)
|
|
25
|
-
--refresh Refresh interval in milliseconds (default: 2000)
|
|
26
|
-
--duration How long to run in minutes (default: infinite)
|
|
27
|
-
--dry Dry run - show setup without trading
|
|
28
|
-
|
|
29
|
-
How ALO Works:
|
|
30
|
-
- ALO = Add Liquidity Only (post-only)
|
|
31
|
-
- Orders are REJECTED if they would immediately match
|
|
32
|
-
- You ALWAYS earn maker rebate (~0.3 bps) instead of paying taker fee
|
|
33
|
-
- Guarantees you're providing liquidity, not taking it
|
|
34
|
-
|
|
35
|
-
Pricing Strategy:
|
|
36
|
-
- Reads actual order book (best bid/ask) not just mid price
|
|
37
|
-
- Places bid at: bestBid - offset (or bestBid if joining)
|
|
38
|
-
- Places ask at: bestAsk + offset (or bestAsk if joining)
|
|
39
|
-
- Skews prices based on inventory to stay neutral
|
|
40
|
-
|
|
41
|
-
Examples:
|
|
42
|
-
# Market make HYPE with 1 bps offset from best bid/ask
|
|
43
|
-
npx tsx scripts/strategies/mm-maker.ts --coin HYPE --size 1 --offset 1
|
|
44
|
-
|
|
45
|
-
# Wider offset for volatile assets
|
|
46
|
-
npx tsx scripts/strategies/mm-maker.ts --coin ETH --size 0.1 --offset 2 --max-position 0.5
|
|
47
|
-
|
|
48
|
-
# Preview setup
|
|
49
|
-
npx tsx scripts/strategies/mm-maker.ts --coin HYPE --size 1 --offset 1 --dry
|
|
50
|
-
|
|
51
|
-
Fee Structure (Hyperliquid):
|
|
52
|
-
- Taker fee: ~2.5 bps (you PAY)
|
|
53
|
-
- Maker rebate: ~0.3 bps (you EARN)
|
|
54
|
-
- By using ALO only, you always earn the rebate!
|
|
55
|
-
`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interface Quote {
|
|
59
|
-
side: 'bid' | 'ask';
|
|
60
|
-
price: number;
|
|
61
|
-
size: number;
|
|
62
|
-
oid?: number;
|
|
63
|
-
placedAt: number;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface MmState {
|
|
67
|
-
bid: Quote | null;
|
|
68
|
-
ask: Quote | null;
|
|
69
|
-
lastBidFill: number;
|
|
70
|
-
lastAskFill: number;
|
|
71
|
-
totalBought: number;
|
|
72
|
-
totalSold: number;
|
|
73
|
-
totalBuyCost: number;
|
|
74
|
-
totalSellRevenue: number;
|
|
75
|
-
roundTrips: number;
|
|
76
|
-
rejections: number;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function main() {
|
|
80
|
-
const args = parseArgs(process.argv.slice(2));
|
|
81
|
-
|
|
82
|
-
const coin = args.coin as string;
|
|
83
|
-
const size = parseFloat(args.size as string);
|
|
84
|
-
const offsetBps = args.offset ? parseFloat(args.offset as string) : 1;
|
|
85
|
-
const maxPosition = args['max-position'] ? parseFloat(args['max-position'] as string) : size * 3;
|
|
86
|
-
const skewFactor = args['skew-factor'] ? parseFloat(args['skew-factor'] as string) : 2.0;
|
|
87
|
-
const refreshMs = args.refresh ? parseInt(args.refresh as string) : 2000;
|
|
88
|
-
const durationMins = args.duration ? parseFloat(args.duration as string) : Infinity;
|
|
89
|
-
const dryRun = args.dry as boolean;
|
|
90
|
-
|
|
91
|
-
if (!coin || isNaN(size)) {
|
|
92
|
-
printUsage();
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const client = getClient();
|
|
97
|
-
|
|
98
|
-
if (args.verbose) {
|
|
99
|
-
client.verbose = true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
console.log('Open Broker - Maker-Only MM');
|
|
103
|
-
console.log('===========================\n');
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
// Get order book
|
|
107
|
-
const book = await client.getL2Book(coin);
|
|
108
|
-
|
|
109
|
-
if (book.bestBid === 0 || book.bestAsk === 0) {
|
|
110
|
-
console.error(`Error: No order book for ${coin}`);
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const offsetFraction = offsetBps / 10000;
|
|
115
|
-
const bidPrice = book.bestBid * (1 - offsetFraction);
|
|
116
|
-
const askPrice = book.bestAsk * (1 + offsetFraction);
|
|
117
|
-
const notionalPerSide = size * book.midPrice;
|
|
118
|
-
|
|
119
|
-
console.log('Strategy Configuration');
|
|
120
|
-
console.log('----------------------');
|
|
121
|
-
console.log(`Coin: ${coin}`);
|
|
122
|
-
console.log(`Order Size: ${size} ${coin}`);
|
|
123
|
-
console.log(`Notional/Side: ${formatUsd(notionalPerSide)}`);
|
|
124
|
-
console.log(`Offset: ${offsetBps} bps`);
|
|
125
|
-
console.log(`Max Position: ±${maxPosition} ${coin}`);
|
|
126
|
-
console.log(`Skew Factor: ${skewFactor}x`);
|
|
127
|
-
console.log(`Refresh: ${refreshMs}ms`);
|
|
128
|
-
console.log(`\nCurrent Order Book:`);
|
|
129
|
-
console.log(` Best Bid: ${formatUsd(book.bestBid)}`);
|
|
130
|
-
console.log(` Best Ask: ${formatUsd(book.bestAsk)}`);
|
|
131
|
-
console.log(` Mid Price: ${formatUsd(book.midPrice)}`);
|
|
132
|
-
console.log(` Spread: ${book.spreadBps.toFixed(2)} bps`);
|
|
133
|
-
console.log(`\nQuote Prices (at neutral):`);
|
|
134
|
-
console.log(` Our Bid: ${formatUsd(bidPrice)} (${offsetBps} bps below best)`);
|
|
135
|
-
console.log(` Our Ask: ${formatUsd(askPrice)} (${offsetBps} bps above best)`);
|
|
136
|
-
console.log(`\nOrder Type: ALO (Add Liquidity Only)`);
|
|
137
|
-
console.log(` - Orders rejected if they would cross`);
|
|
138
|
-
console.log(` - Guarantees maker rebate (~0.3 bps)`);
|
|
139
|
-
|
|
140
|
-
if (dryRun) {
|
|
141
|
-
console.log('\n--- Dry run complete ---');
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
console.log('\nStarting maker-only MM...\n');
|
|
146
|
-
|
|
147
|
-
const state: MmState = {
|
|
148
|
-
bid: null,
|
|
149
|
-
ask: null,
|
|
150
|
-
lastBidFill: 0,
|
|
151
|
-
lastAskFill: 0,
|
|
152
|
-
totalBought: 0,
|
|
153
|
-
totalSold: 0,
|
|
154
|
-
totalBuyCost: 0,
|
|
155
|
-
totalSellRevenue: 0,
|
|
156
|
-
roundTrips: 0,
|
|
157
|
-
rejections: 0,
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const endTime = durationMins === Infinity ? Infinity : Date.now() + durationMins * 60 * 1000;
|
|
161
|
-
|
|
162
|
-
// Handle graceful shutdown
|
|
163
|
-
let shuttingDown = false;
|
|
164
|
-
process.on('SIGINT', async () => {
|
|
165
|
-
if (shuttingDown) return;
|
|
166
|
-
shuttingDown = true;
|
|
167
|
-
console.log('\n\nShutting down - cancelling quotes...');
|
|
168
|
-
await cancelQuotes(client, coin, state);
|
|
169
|
-
await printSummary(client, coin, state);
|
|
170
|
-
process.exit(0);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
let lastLogTime = 0;
|
|
174
|
-
|
|
175
|
-
while (Date.now() < endTime && !shuttingDown) {
|
|
176
|
-
const now = Date.now();
|
|
177
|
-
|
|
178
|
-
// Get fresh order book
|
|
179
|
-
const freshBook = await client.getL2Book(coin);
|
|
180
|
-
|
|
181
|
-
// Get actual position from exchange
|
|
182
|
-
const userState = await client.getUserState();
|
|
183
|
-
const position = userState.assetPositions.find(p => p.position.coin === coin);
|
|
184
|
-
const actualPosition = position ? parseFloat(position.position.szi) : 0;
|
|
185
|
-
|
|
186
|
-
// Get our open orders
|
|
187
|
-
const openOrders = await client.getOpenOrders();
|
|
188
|
-
const ourOrders = openOrders.filter(o => o.coin === coin);
|
|
189
|
-
const openOids = new Set(ourOrders.map(o => o.oid));
|
|
190
|
-
|
|
191
|
-
// Check for bid fill
|
|
192
|
-
if (state.bid && state.bid.oid && !openOids.has(state.bid.oid)) {
|
|
193
|
-
const fillPrice = state.bid.price;
|
|
194
|
-
state.totalBought += state.bid.size;
|
|
195
|
-
state.totalBuyCost += fillPrice * state.bid.size;
|
|
196
|
-
state.lastBidFill = now;
|
|
197
|
-
console.log(`[${new Date().toLocaleTimeString()}] BID FILLED @ ${formatUsd(fillPrice)} | Pos: ${actualPosition.toFixed(4)} | +rebate`);
|
|
198
|
-
state.bid = null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Check for ask fill
|
|
202
|
-
if (state.ask && state.ask.oid && !openOids.has(state.ask.oid)) {
|
|
203
|
-
const fillPrice = state.ask.price;
|
|
204
|
-
state.totalSold += state.ask.size;
|
|
205
|
-
state.totalSellRevenue += fillPrice * state.ask.size;
|
|
206
|
-
state.lastAskFill = now;
|
|
207
|
-
state.roundTrips += 0.5;
|
|
208
|
-
console.log(`[${new Date().toLocaleTimeString()}] ASK FILLED @ ${formatUsd(fillPrice)} | Pos: ${actualPosition.toFixed(4)} | +rebate`);
|
|
209
|
-
state.ask = null;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Calculate inventory-based skew
|
|
213
|
-
const inventoryRatio = Math.max(-1, Math.min(1, actualPosition / maxPosition));
|
|
214
|
-
|
|
215
|
-
// Determine if we should quote each side
|
|
216
|
-
const shouldBid = actualPosition < maxPosition;
|
|
217
|
-
const shouldAsk = actualPosition > -maxPosition;
|
|
218
|
-
|
|
219
|
-
// Calculate skewed prices relative to order book
|
|
220
|
-
// When long: bid further from best (wider), ask closer to best (tighter)
|
|
221
|
-
// When short: bid closer to best (tighter), ask further from best (wider)
|
|
222
|
-
const bidSkewMult = 1 + inventoryRatio * skewFactor;
|
|
223
|
-
const askSkewMult = 1 - inventoryRatio * skewFactor;
|
|
224
|
-
|
|
225
|
-
const targetBidPrice = freshBook.bestBid * (1 - offsetFraction * Math.max(0.1, bidSkewMult));
|
|
226
|
-
const targetAskPrice = freshBook.bestAsk * (1 + offsetFraction * Math.max(0.1, askSkewMult));
|
|
227
|
-
|
|
228
|
-
// CRITICAL: Ensure we don't cross the spread
|
|
229
|
-
// Our bid must be BELOW the best ask
|
|
230
|
-
// Our ask must be ABOVE the best bid
|
|
231
|
-
const safeBidPrice = Math.min(targetBidPrice, freshBook.bestAsk * 0.9999);
|
|
232
|
-
const safeAskPrice = Math.max(targetAskPrice, freshBook.bestBid * 1.0001);
|
|
233
|
-
|
|
234
|
-
// Cancel stale quotes
|
|
235
|
-
if (state.bid && state.bid.oid) {
|
|
236
|
-
const bidDrift = Math.abs(state.bid.price - safeBidPrice) / freshBook.midPrice;
|
|
237
|
-
if (bidDrift > 0.0005 || !shouldBid) {
|
|
238
|
-
try {
|
|
239
|
-
await client.cancel(coin, state.bid.oid);
|
|
240
|
-
} catch {
|
|
241
|
-
// May have been filled
|
|
242
|
-
}
|
|
243
|
-
state.bid = null;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (state.ask && state.ask.oid) {
|
|
248
|
-
const askDrift = Math.abs(state.ask.price - safeAskPrice) / freshBook.midPrice;
|
|
249
|
-
if (askDrift > 0.0005 || !shouldAsk) {
|
|
250
|
-
try {
|
|
251
|
-
await client.cancel(coin, state.ask.oid);
|
|
252
|
-
} catch {
|
|
253
|
-
// May have been filled
|
|
254
|
-
}
|
|
255
|
-
state.ask = null;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Place new bid using ALO
|
|
260
|
-
if (shouldBid && !state.bid) {
|
|
261
|
-
// Double check: our bid must be below the best ask
|
|
262
|
-
if (safeBidPrice < freshBook.bestAsk) {
|
|
263
|
-
const response = await client.limitOrder(coin, true, size, safeBidPrice, 'Alo', false);
|
|
264
|
-
|
|
265
|
-
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
266
|
-
const status = response.response.data.statuses[0];
|
|
267
|
-
if (status?.resting) {
|
|
268
|
-
state.bid = {
|
|
269
|
-
side: 'bid',
|
|
270
|
-
price: safeBidPrice,
|
|
271
|
-
size,
|
|
272
|
-
oid: status.resting.oid,
|
|
273
|
-
placedAt: now,
|
|
274
|
-
};
|
|
275
|
-
} else if (status?.error) {
|
|
276
|
-
// ALO rejection - order would have crossed
|
|
277
|
-
state.rejections++;
|
|
278
|
-
if (state.rejections % 10 === 1) {
|
|
279
|
-
console.log(`[${new Date().toLocaleTimeString()}] ALO bid rejected (would cross) - this is expected`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Place new ask using ALO
|
|
287
|
-
if (shouldAsk && !state.ask) {
|
|
288
|
-
// Double check: our ask must be above the best bid
|
|
289
|
-
if (safeAskPrice > freshBook.bestBid) {
|
|
290
|
-
const response = await client.limitOrder(coin, false, size, safeAskPrice, 'Alo', false);
|
|
291
|
-
|
|
292
|
-
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
293
|
-
const status = response.response.data.statuses[0];
|
|
294
|
-
if (status?.resting) {
|
|
295
|
-
state.ask = {
|
|
296
|
-
side: 'ask',
|
|
297
|
-
price: safeAskPrice,
|
|
298
|
-
size,
|
|
299
|
-
oid: status.resting.oid,
|
|
300
|
-
placedAt: now,
|
|
301
|
-
};
|
|
302
|
-
} else if (status?.error) {
|
|
303
|
-
// ALO rejection - order would have crossed
|
|
304
|
-
state.rejections++;
|
|
305
|
-
if (state.rejections % 10 === 1) {
|
|
306
|
-
console.log(`[${new Date().toLocaleTimeString()}] ALO ask rejected (would cross) - this is expected`);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Periodic status log
|
|
314
|
-
if (now - lastLogTime > 30000) {
|
|
315
|
-
const bidStatus = state.bid ? formatUsd(state.bid.price) : (shouldBid ? 'placing...' : 'at max long');
|
|
316
|
-
const askStatus = state.ask ? formatUsd(state.ask.price) : (shouldAsk ? 'placing...' : 'at max short');
|
|
317
|
-
const realizedPnl = state.totalSellRevenue - state.totalBuyCost;
|
|
318
|
-
const skewPct = (inventoryRatio * 100).toFixed(1);
|
|
319
|
-
|
|
320
|
-
console.log(`[${new Date().toLocaleTimeString()}] Book: ${formatUsd(freshBook.bestBid)}/${formatUsd(freshBook.bestAsk)} (${freshBook.spreadBps.toFixed(1)}bps) | Bid: ${bidStatus} | Ask: ${askStatus} | Pos: ${actualPosition.toFixed(4)} (${skewPct}%) | PnL: ${formatUsd(realizedPnl)} | Rej: ${state.rejections}`);
|
|
321
|
-
lastLogTime = now;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
await sleep(refreshMs);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// End of duration
|
|
328
|
-
if (!shuttingDown) {
|
|
329
|
-
console.log('\nDuration ended. Cancelling quotes...');
|
|
330
|
-
await cancelQuotes(client, coin, state);
|
|
331
|
-
await printSummary(client, coin, state);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
} catch (error) {
|
|
335
|
-
console.error('Error:', error);
|
|
336
|
-
process.exit(1);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async function cancelQuotes(
|
|
341
|
-
client: ReturnType<typeof getClient>,
|
|
342
|
-
coin: string,
|
|
343
|
-
state: MmState
|
|
344
|
-
): Promise<void> {
|
|
345
|
-
if (state.bid && state.bid.oid) {
|
|
346
|
-
try {
|
|
347
|
-
await client.cancel(coin, state.bid.oid);
|
|
348
|
-
console.log(` Cancelled bid @ ${formatUsd(state.bid.price)}`);
|
|
349
|
-
} catch {
|
|
350
|
-
// Ignore
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (state.ask && state.ask.oid) {
|
|
354
|
-
try {
|
|
355
|
-
await client.cancel(coin, state.ask.oid);
|
|
356
|
-
console.log(` Cancelled ask @ ${formatUsd(state.ask.price)}`);
|
|
357
|
-
} catch {
|
|
358
|
-
// Ignore
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async function printSummary(
|
|
364
|
-
client: ReturnType<typeof getClient>,
|
|
365
|
-
coin: string,
|
|
366
|
-
state: MmState
|
|
367
|
-
): Promise<void> {
|
|
368
|
-
// Get final position and price
|
|
369
|
-
const userState = await client.getUserState();
|
|
370
|
-
const position = userState.assetPositions.find(p => p.position.coin === coin);
|
|
371
|
-
const finalPosition = position ? parseFloat(position.position.szi) : 0;
|
|
372
|
-
|
|
373
|
-
const book = await client.getL2Book(coin);
|
|
374
|
-
const currentMid = book.midPrice;
|
|
375
|
-
|
|
376
|
-
const inventoryValue = finalPosition * currentMid;
|
|
377
|
-
const realizedPnl = state.totalSellRevenue - state.totalBuyCost;
|
|
378
|
-
|
|
379
|
-
// Estimate rebates earned (0.3 bps per side)
|
|
380
|
-
const totalVolume = state.totalBuyCost + state.totalSellRevenue;
|
|
381
|
-
const estimatedRebates = totalVolume * 0.00003; // 0.3 bps
|
|
382
|
-
|
|
383
|
-
// Calculate inventory PnL
|
|
384
|
-
let inventoryPnl = 0;
|
|
385
|
-
if (finalPosition > 0 && state.totalBought > 0) {
|
|
386
|
-
const avgBuyPrice = state.totalBuyCost / state.totalBought;
|
|
387
|
-
inventoryPnl = (currentMid - avgBuyPrice) * finalPosition;
|
|
388
|
-
} else if (finalPosition < 0 && state.totalSold > 0) {
|
|
389
|
-
const avgSellPrice = state.totalSellRevenue / state.totalSold;
|
|
390
|
-
inventoryPnl = (avgSellPrice - currentMid) * Math.abs(finalPosition);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
console.log('\n========== Maker MM Summary ==========');
|
|
394
|
-
console.log(`Total Bought: ${state.totalBought.toFixed(6)} ${coin}`);
|
|
395
|
-
console.log(`Total Sold: ${state.totalSold.toFixed(6)} ${coin}`);
|
|
396
|
-
console.log(`Final Position: ${finalPosition.toFixed(6)} ${coin}`);
|
|
397
|
-
console.log(`Inventory Value: ${formatUsd(inventoryValue)}`);
|
|
398
|
-
console.log(`Round Trips: ${state.roundTrips.toFixed(1)}`);
|
|
399
|
-
console.log(`ALO Rejections: ${state.rejections}`);
|
|
400
|
-
console.log(`Realized PnL: ${formatUsd(realizedPnl)}`);
|
|
401
|
-
console.log(`Est. Rebates: ${formatUsd(estimatedRebates)} (0.3bps on ${formatUsd(totalVolume)})`);
|
|
402
|
-
console.log(`Inventory PnL: ${formatUsd(inventoryPnl)}`);
|
|
403
|
-
console.log(`Total PnL: ${formatUsd(realizedPnl + inventoryPnl + estimatedRebates)}`);
|
|
404
|
-
|
|
405
|
-
if (Math.abs(finalPosition) > 0.0001) {
|
|
406
|
-
console.log(`\n You have an open position of ${finalPosition.toFixed(6)} ${coin}`);
|
|
407
|
-
console.log(` Close it manually if desired.`);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
main();
|