@alango/dr-manhattan 0.1.3 → 0.1.4

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/index.js CHANGED
@@ -7,39 +7,70 @@ import { io } from 'socket.io-client';
7
7
  import { ClobClient, Side, AssetType } from '@polymarket/clob-client';
8
8
  import pino from 'pino';
9
9
 
10
- // src/types/order.ts
11
- var OrderSide = {
12
- BUY: "buy",
13
- SELL: "sell"
10
+ // src/errors/index.ts
11
+ var DrManhattanError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "DrManhattanError";
15
+ Object.setPrototypeOf(this, new.target.prototype);
16
+ }
14
17
  };
15
- var OrderStatus = {
16
- PENDING: "pending",
17
- OPEN: "open",
18
- FILLED: "filled",
19
- PARTIALLY_FILLED: "partially_filled",
20
- CANCELLED: "cancelled",
21
- REJECTED: "rejected"
18
+ var ExchangeError = class extends DrManhattanError {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = "ExchangeError";
22
+ }
22
23
  };
23
- var OrderUtils = {
24
- /** Get remaining unfilled amount */
25
- remaining(order) {
26
- return order.size - order.filled;
27
- },
28
- /** Check if order is still active */
29
- isActive(order) {
30
- return order.status === OrderStatus.OPEN || order.status === OrderStatus.PARTIALLY_FILLED;
31
- },
32
- /** Check if order is completely filled */
33
- isFilled(order) {
34
- return order.status === OrderStatus.FILLED || order.filled >= order.size;
35
- },
36
- /** Get fill percentage (0-1) */
37
- fillPercentage(order) {
38
- if (order.size === 0) return 0;
39
- return order.filled / order.size;
24
+ var NetworkError = class extends DrManhattanError {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = "NetworkError";
28
+ }
29
+ };
30
+ var RateLimitError = class extends DrManhattanError {
31
+ retryAfter;
32
+ constructor(message, retryAfter) {
33
+ super(message);
34
+ this.name = "RateLimitError";
35
+ this.retryAfter = retryAfter;
36
+ }
37
+ };
38
+ var AuthenticationError = class extends DrManhattanError {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = "AuthenticationError";
42
+ }
43
+ };
44
+ var InsufficientFunds = class extends DrManhattanError {
45
+ constructor(message) {
46
+ super(message);
47
+ this.name = "InsufficientFunds";
48
+ }
49
+ };
50
+ var InvalidOrder = class extends DrManhattanError {
51
+ constructor(message) {
52
+ super(message);
53
+ this.name = "InvalidOrder";
54
+ }
55
+ };
56
+ var MarketNotFound = class extends DrManhattanError {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = "MarketNotFound";
40
60
  }
41
61
  };
42
62
 
63
+ // src/types/crypto-hourly.ts
64
+ var TOKEN_ALIASES = {
65
+ BITCOIN: "BTC",
66
+ ETHEREUM: "ETH",
67
+ SOLANA: "SOL"
68
+ };
69
+ function normalizeTokenSymbol(token) {
70
+ const upper = token.toUpperCase();
71
+ return TOKEN_ALIASES[upper] ?? upper;
72
+ }
73
+
43
74
  // src/types/market.ts
44
75
  var MarketUtils = {
45
76
  /** Check if market is binary (Yes/No) */
@@ -89,49 +120,38 @@ var MarketUtils = {
89
120
  }
90
121
  };
91
122
 
92
- // src/types/position.ts
93
- var PositionUtils = {
94
- /** Get total cost basis */
95
- costBasis(position) {
96
- return position.size * position.averagePrice;
123
+ // src/types/order.ts
124
+ var OrderSide = {
125
+ BUY: "buy",
126
+ SELL: "sell"
127
+ };
128
+ var OrderStatus = {
129
+ PENDING: "pending",
130
+ OPEN: "open",
131
+ FILLED: "filled",
132
+ PARTIALLY_FILLED: "partially_filled",
133
+ CANCELLED: "cancelled",
134
+ REJECTED: "rejected"
135
+ };
136
+ var OrderUtils = {
137
+ /** Get remaining unfilled amount */
138
+ remaining(order) {
139
+ return order.size - order.filled;
97
140
  },
98
- /** Get current market value */
99
- currentValue(position) {
100
- return position.size * position.currentPrice;
141
+ /** Check if order is still active */
142
+ isActive(order) {
143
+ return order.status === OrderStatus.OPEN || order.status === OrderStatus.PARTIALLY_FILLED;
101
144
  },
102
- /** Get unrealized profit/loss */
103
- unrealizedPnl(position) {
104
- return PositionUtils.currentValue(position) - PositionUtils.costBasis(position);
145
+ /** Check if order is completely filled */
146
+ isFilled(order) {
147
+ return order.status === OrderStatus.FILLED || order.filled >= order.size;
105
148
  },
106
- /** Get unrealized P&L as percentage */
107
- unrealizedPnlPercent(position) {
108
- const costBasis = PositionUtils.costBasis(position);
109
- if (costBasis === 0) return 0;
110
- return PositionUtils.unrealizedPnl(position) / costBasis * 100;
149
+ /** Get fill percentage (0-1) */
150
+ fillPercentage(order) {
151
+ if (order.size === 0) return 0;
152
+ return order.filled / order.size;
111
153
  }
112
154
  };
113
- function calculateDelta(positions) {
114
- const entries = Object.entries(positions);
115
- if (entries.length === 0) {
116
- return { delta: 0, maxOutcome: null, maxPosition: 0 };
117
- }
118
- let maxOutcome = null;
119
- let maxPosition = 0;
120
- for (const [outcome, size] of entries) {
121
- if (size > maxPosition) {
122
- maxPosition = size;
123
- maxOutcome = outcome;
124
- }
125
- }
126
- if (entries.length === 2) {
127
- const [first, second] = entries;
128
- if (first && second) {
129
- const delta = Math.abs(first[1] - second[1]);
130
- return { delta, maxOutcome, maxPosition };
131
- }
132
- }
133
- return { delta: maxPosition, maxOutcome, maxPosition };
134
- }
135
155
 
136
156
  // src/types/orderbook.ts
137
157
  var OrderbookUtils = {
@@ -218,69 +238,49 @@ var OrderbookManager = class {
218
238
  }
219
239
  };
220
240
 
221
- // src/types/crypto-hourly.ts
222
- var TOKEN_ALIASES = {
223
- BITCOIN: "BTC",
224
- ETHEREUM: "ETH",
225
- SOLANA: "SOL"
226
- };
227
- function normalizeTokenSymbol(token) {
228
- const upper = token.toUpperCase();
229
- return TOKEN_ALIASES[upper] ?? upper;
230
- }
231
-
232
- // src/errors/index.ts
233
- var DrManhattanError = class extends Error {
234
- constructor(message) {
235
- super(message);
236
- this.name = "DrManhattanError";
237
- Object.setPrototypeOf(this, new.target.prototype);
238
- }
239
- };
240
- var ExchangeError = class extends DrManhattanError {
241
- constructor(message) {
242
- super(message);
243
- this.name = "ExchangeError";
244
- }
245
- };
246
- var NetworkError = class extends DrManhattanError {
247
- constructor(message) {
248
- super(message);
249
- this.name = "NetworkError";
250
- }
251
- };
252
- var RateLimitError = class extends DrManhattanError {
253
- retryAfter;
254
- constructor(message, retryAfter) {
255
- super(message);
256
- this.name = "RateLimitError";
257
- this.retryAfter = retryAfter;
258
- }
259
- };
260
- var AuthenticationError = class extends DrManhattanError {
261
- constructor(message) {
262
- super(message);
263
- this.name = "AuthenticationError";
241
+ // src/types/position.ts
242
+ var PositionUtils = {
243
+ /** Get total cost basis */
244
+ costBasis(position) {
245
+ return position.size * position.averagePrice;
246
+ },
247
+ /** Get current market value */
248
+ currentValue(position) {
249
+ return position.size * position.currentPrice;
250
+ },
251
+ /** Get unrealized profit/loss */
252
+ unrealizedPnl(position) {
253
+ return PositionUtils.currentValue(position) - PositionUtils.costBasis(position);
254
+ },
255
+ /** Get unrealized P&L as percentage */
256
+ unrealizedPnlPercent(position) {
257
+ const costBasis = PositionUtils.costBasis(position);
258
+ if (costBasis === 0) return 0;
259
+ return PositionUtils.unrealizedPnl(position) / costBasis * 100;
264
260
  }
265
261
  };
266
- var InsufficientFunds = class extends DrManhattanError {
267
- constructor(message) {
268
- super(message);
269
- this.name = "InsufficientFunds";
262
+ function calculateDelta(positions) {
263
+ const entries = Object.entries(positions);
264
+ if (entries.length === 0) {
265
+ return { delta: 0, maxOutcome: null, maxPosition: 0 };
270
266
  }
271
- };
272
- var InvalidOrder = class extends DrManhattanError {
273
- constructor(message) {
274
- super(message);
275
- this.name = "InvalidOrder";
267
+ let maxOutcome = null;
268
+ let maxPosition = 0;
269
+ for (const [outcome, size] of entries) {
270
+ if (size > maxPosition) {
271
+ maxPosition = size;
272
+ maxOutcome = outcome;
273
+ }
276
274
  }
277
- };
278
- var MarketNotFound = class extends DrManhattanError {
279
- constructor(message) {
280
- super(message);
281
- this.name = "MarketNotFound";
275
+ if (entries.length === 2) {
276
+ const [first, second] = entries;
277
+ if (first && second) {
278
+ const delta = Math.abs(first[1] - second[1]);
279
+ return { delta, maxOutcome, maxPosition };
280
+ }
282
281
  }
283
- };
282
+ return { delta: maxPosition, maxOutcome, maxPosition };
283
+ }
284
284
 
285
285
  // src/core/exchange.ts
286
286
  var Exchange = class {
@@ -411,6 +411,131 @@ var Exchange = class {
411
411
  return Math.min(maxPositionSize, liquidityBasedSize);
412
412
  }
413
413
  };
414
+ var StrategyState = {
415
+ STOPPED: "stopped",
416
+ RUNNING: "running",
417
+ PAUSED: "paused"
418
+ };
419
+ var Strategy = class extends EventEmitter {
420
+ exchange;
421
+ marketId;
422
+ market = null;
423
+ state = StrategyState.STOPPED;
424
+ config;
425
+ tickTimer = null;
426
+ positions = [];
427
+ openOrders = [];
428
+ constructor(exchange, marketId, config = {}) {
429
+ super();
430
+ this.exchange = exchange;
431
+ this.marketId = marketId;
432
+ this.config = {
433
+ tickInterval: 1e3,
434
+ maxPositionSize: 100,
435
+ spreadBps: 100,
436
+ verbose: false,
437
+ ...config
438
+ };
439
+ }
440
+ async start() {
441
+ if (this.state === StrategyState.RUNNING) return;
442
+ this.market = await this.exchange.fetchMarket(this.marketId);
443
+ this.state = StrategyState.RUNNING;
444
+ this.tickTimer = setInterval(async () => {
445
+ if (this.state !== StrategyState.RUNNING) return;
446
+ try {
447
+ await this.refreshState();
448
+ await this.onTick();
449
+ } catch (error) {
450
+ this.emit("error", error);
451
+ if (this.config.verbose) {
452
+ console.error("Strategy tick error:", error);
453
+ }
454
+ }
455
+ }, this.config.tickInterval);
456
+ this.emit("started");
457
+ }
458
+ async stop() {
459
+ if (this.state === StrategyState.STOPPED) return;
460
+ this.state = StrategyState.STOPPED;
461
+ if (this.tickTimer) {
462
+ clearInterval(this.tickTimer);
463
+ this.tickTimer = null;
464
+ }
465
+ await this.cancelAllOrders();
466
+ this.emit("stopped");
467
+ }
468
+ pause() {
469
+ if (this.state === StrategyState.RUNNING) {
470
+ this.state = StrategyState.PAUSED;
471
+ this.emit("paused");
472
+ }
473
+ }
474
+ resume() {
475
+ if (this.state === StrategyState.PAUSED) {
476
+ this.state = StrategyState.RUNNING;
477
+ this.emit("resumed");
478
+ }
479
+ }
480
+ async refreshState() {
481
+ const [positions, orders] = await Promise.all([
482
+ this.exchange.fetchPositions(this.marketId),
483
+ this.exchange.fetchOpenOrders(this.marketId)
484
+ ]);
485
+ this.positions = positions;
486
+ this.openOrders = orders;
487
+ }
488
+ async cancelAllOrders() {
489
+ for (const order of this.openOrders) {
490
+ try {
491
+ await this.exchange.cancelOrder(order.id, this.marketId);
492
+ } catch {
493
+ }
494
+ }
495
+ this.openOrders = [];
496
+ }
497
+ getPosition(outcome) {
498
+ return this.positions.find((p) => p.outcome === outcome);
499
+ }
500
+ getNetPosition() {
501
+ if (!this.market || this.market.outcomes.length !== 2) return 0;
502
+ const outcome1 = this.market.outcomes[0];
503
+ const outcome2 = this.market.outcomes[1];
504
+ if (!outcome1 || !outcome2) return 0;
505
+ const pos1 = this.getPosition(outcome1)?.size ?? 0;
506
+ const pos2 = this.getPosition(outcome2)?.size ?? 0;
507
+ return pos1 - pos2;
508
+ }
509
+ async placeOrder(outcome, side, price, size, tokenId) {
510
+ try {
511
+ const order = await this.exchange.createOrder({
512
+ marketId: this.marketId,
513
+ outcome,
514
+ side,
515
+ price,
516
+ size,
517
+ tokenId
518
+ });
519
+ this.openOrders.push(order);
520
+ this.emit("order", order);
521
+ return order;
522
+ } catch (error) {
523
+ this.emit("error", error);
524
+ return null;
525
+ }
526
+ }
527
+ log(message) {
528
+ if (this.config.verbose) {
529
+ console.log(`[${this.exchange.id}:${this.marketId}] ${message}`);
530
+ }
531
+ }
532
+ get isRunning() {
533
+ return this.state === StrategyState.RUNNING;
534
+ }
535
+ get currentState() {
536
+ return this.state;
537
+ }
538
+ };
414
539
  var WebSocketState = {
415
540
  DISCONNECTED: "disconnected",
416
541
  CONNECTING: "connecting",
@@ -579,142 +704,20 @@ var OrderBookWebSocket = class extends EventEmitter {
579
704
  }
580
705
  }
581
706
  };
582
- var StrategyState = {
583
- STOPPED: "stopped",
584
- RUNNING: "running",
585
- PAUSED: "paused"
586
- };
587
- var Strategy = class extends EventEmitter {
588
- exchange;
589
- marketId;
590
- market = null;
591
- state = StrategyState.STOPPED;
592
- config;
593
- tickTimer = null;
594
- positions = [];
595
- openOrders = [];
596
- constructor(exchange, marketId, config = {}) {
597
- super();
598
- this.exchange = exchange;
599
- this.marketId = marketId;
600
- this.config = {
601
- tickInterval: 1e3,
602
- maxPositionSize: 100,
603
- spreadBps: 100,
604
- verbose: false,
605
- ...config
606
- };
607
- }
608
- async start() {
609
- if (this.state === StrategyState.RUNNING) return;
610
- this.market = await this.exchange.fetchMarket(this.marketId);
611
- this.state = StrategyState.RUNNING;
612
- this.tickTimer = setInterval(async () => {
613
- if (this.state !== StrategyState.RUNNING) return;
614
- try {
615
- await this.refreshState();
616
- await this.onTick();
617
- } catch (error) {
618
- this.emit("error", error);
619
- if (this.config.verbose) {
620
- console.error("Strategy tick error:", error);
621
- }
622
- }
623
- }, this.config.tickInterval);
624
- this.emit("started");
625
- }
626
- async stop() {
627
- if (this.state === StrategyState.STOPPED) return;
628
- this.state = StrategyState.STOPPED;
629
- if (this.tickTimer) {
630
- clearInterval(this.tickTimer);
631
- this.tickTimer = null;
632
- }
633
- await this.cancelAllOrders();
634
- this.emit("stopped");
635
- }
636
- pause() {
637
- if (this.state === StrategyState.RUNNING) {
638
- this.state = StrategyState.PAUSED;
639
- this.emit("paused");
640
- }
641
- }
642
- resume() {
643
- if (this.state === StrategyState.PAUSED) {
644
- this.state = StrategyState.RUNNING;
645
- this.emit("resumed");
646
- }
647
- }
648
- async refreshState() {
649
- const [positions, orders] = await Promise.all([
650
- this.exchange.fetchPositions(this.marketId),
651
- this.exchange.fetchOpenOrders(this.marketId)
652
- ]);
653
- this.positions = positions;
654
- this.openOrders = orders;
655
- }
656
- async cancelAllOrders() {
657
- for (const order of this.openOrders) {
658
- try {
659
- await this.exchange.cancelOrder(order.id, this.marketId);
660
- } catch {
661
- }
662
- }
663
- this.openOrders = [];
664
- }
665
- getPosition(outcome) {
666
- return this.positions.find((p) => p.outcome === outcome);
667
- }
668
- getNetPosition() {
669
- if (!this.market || this.market.outcomes.length !== 2) return 0;
670
- const outcome1 = this.market.outcomes[0];
671
- const outcome2 = this.market.outcomes[1];
672
- if (!outcome1 || !outcome2) return 0;
673
- const pos1 = this.getPosition(outcome1)?.size ?? 0;
674
- const pos2 = this.getPosition(outcome2)?.size ?? 0;
675
- return pos1 - pos2;
676
- }
677
- async placeOrder(outcome, side, price, size, tokenId) {
678
- try {
679
- const order = await this.exchange.createOrder({
680
- marketId: this.marketId,
681
- outcome,
682
- side,
683
- price,
684
- size,
685
- tokenId
686
- });
687
- this.openOrders.push(order);
688
- this.emit("order", order);
689
- return order;
690
- } catch (error) {
691
- this.emit("error", error);
692
- return null;
693
- }
694
- }
695
- log(message) {
696
- if (this.config.verbose) {
697
- console.log(`[${this.exchange.id}:${this.marketId}] ${message}`);
698
- }
699
- }
700
- get isRunning() {
701
- return this.state === StrategyState.RUNNING;
702
- }
703
- get currentState() {
704
- return this.state;
705
- }
706
- };
707
707
  var BASE_URL = "https://api.elections.kalshi.com/trade-api/v2";
708
708
  var DEMO_URL = "https://demo-api.kalshi.co/trade-api/v2";
709
709
  function createAuth(privateKeyPem) {
710
+ const privateKey = crypto.createPrivateKey(privateKeyPem);
710
711
  return {
711
712
  sign(timestampMs, method, path) {
712
- const message = `${timestampMs}${method.toUpperCase()}${path}`;
713
- const sign = crypto.createSign("RSA-SHA256");
714
- sign.update(message);
715
- sign.end();
716
- const signature = sign.sign(privateKeyPem, "base64");
717
- return signature;
713
+ const pathWithoutQuery = path.split("?")[0];
714
+ const message = `${timestampMs}${method.toUpperCase()}${pathWithoutQuery}`;
715
+ const signature = crypto.sign("sha256", Buffer.from(message), {
716
+ key: privateKey,
717
+ padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
718
+ saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
719
+ });
720
+ return signature.toString("base64");
718
721
  }
719
722
  };
720
723
  }
@@ -3034,7 +3037,7 @@ var Polymarket = class extends Exchange {
3034
3037
  minLiquidity
3035
3038
  });
3036
3039
  const upDownPattern = /(?<token>Bitcoin|Ethereum|Solana|BTC|ETH|SOL)\s+Up or Down/i;
3037
- const strikePattern = /(?:(?<token1>BTC|ETH|SOL|BITCOIN|ETHEREUM|SOLANA)\s+.*?(?<direction>above|below|over|under|reach)\s+[\$]?(?<price1>[\d,]+(?:\.\d+)?))|(?:[\$]?(?<price2>[\d,]+(?:\.\d+)?)\s+.*?(?<token2>BTC|ETH|SOL|BITCOIN|ETHEREUM|SOLANA))/i;
3040
+ const strikePattern = /(?:(?<token1>BTC|ETH|SOL|BITCOIN|ETHEREUM|SOLANA)\s+.*?(?<direction>above|below|over|under|reach)\s+[$]?(?<price1>[\d,]+(?:\.\d+)?))|(?:[$]?(?<price2>[\d,]+(?:\.\d+)?)\s+.*?(?<token2>BTC|ETH|SOL|BITCOIN|ETHEREUM|SOLANA))/i;
3038
3041
  for (const market of markets) {
3039
3042
  if (!MarketUtils.isBinary(market) || !MarketUtils.isOpen(market)) continue;
3040
3043
  if (market.closeTime) {