almtools 2.0.8 → 3.0.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.
@@ -0,0 +1,182 @@
1
+ /*!
2
+ * Copyright (c) 2026 Nezzx
3
+ * All Rights Reserved.
4
+ * For execution only.
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const EventEmitter = require('events');
9
+
10
+ /* =======================
11
+ ORDER BOOK
12
+ ======================= */
13
+
14
+ class OrderBook extends EventEmitter {
15
+ constructor(asset) {
16
+ super();
17
+ this.asset = asset;
18
+
19
+ this.bids = []; // buy orders (price desc)
20
+ this.asks = []; // sell orders (price asc)
21
+
22
+ this.ohlcStores = {};
23
+ }
24
+
25
+ /* ===== OHLC ===== */
26
+
27
+ ensureOHLC(timeframe) {
28
+ if (!this.ohlcStores[timeframe]) {
29
+ const { OHLCStore } = require('./ohlc'); // optional split
30
+ this.ohlcStores[timeframe] = new OHLCStore(timeframe);
31
+ }
32
+ }
33
+
34
+ getOHLC(timeframe, limit = 100) {
35
+ return this.ohlcStores[timeframe]?.getCandles(limit) || [];
36
+ }
37
+
38
+ saveToDB(filename = 'ohlc.json') {
39
+ const data = {
40
+ meta: {
41
+ savedAt: Date.now(),
42
+ resolutions: Object.keys(this.ohlcStores)
43
+ },
44
+ ohlc: {}
45
+ };
46
+
47
+ for (const tf in this.ohlcStores) {
48
+ data.ohlc[tf] = this.ohlcStores[tf].getCandles();
49
+ }
50
+
51
+ fs.writeFileSync(filename, JSON.stringify(data, null, 2));
52
+ }
53
+
54
+ /* ===== ORDER API ===== */
55
+
56
+ addOrder({ id, user, side, price, qty }) {
57
+ if (!id || !user) return;
58
+ if (!['buy', 'sell'].includes(side)) return;
59
+ if (price <= 0 || qty <= 0) return;
60
+
61
+ const locked = price * qty;
62
+
63
+ if (!this.asset.lock(user, locked)) {
64
+ this.emit('order_rejected', {
65
+ id,
66
+ user,
67
+ reason: 'INSUFFICIENT_BALANCE'
68
+ });
69
+ return;
70
+ }
71
+
72
+ const order = {
73
+ id,
74
+ user,
75
+ side,
76
+ price,
77
+ qty,
78
+ remaining: qty,
79
+ locked,
80
+ time: Date.now()
81
+ };
82
+
83
+ if (side === 'buy') {
84
+ this.bids.push(order);
85
+ this.bids.sort((a, b) => b.price - a.price || a.time - b.time);
86
+ } else {
87
+ this.asks.push(order);
88
+ this.asks.sort((a, b) => a.price - b.price || a.time - b.time);
89
+ }
90
+
91
+ this.emit('order_added', order);
92
+ this._match();
93
+ }
94
+
95
+ /* ===== MATCHING ENGINE ===== */
96
+
97
+ _match() {
98
+ while (this.bids.length && this.asks.length) {
99
+ const buy = this.bids[0];
100
+ const sell = this.asks[0];
101
+
102
+ if (buy.price < sell.price) break;
103
+
104
+ const qty = Math.min(buy.remaining, sell.remaining);
105
+ const price = sell.price;
106
+ const value = qty * price;
107
+
108
+ // SETTLEMENT
109
+ this.asset.unlock(buy.user, value);
110
+ this.asset.unlock(sell.user, value);
111
+
112
+ this.asset.transfer(buy.user, sell.user, value);
113
+
114
+ buy.remaining -= qty;
115
+ sell.remaining -= qty;
116
+
117
+ buy.locked -= value;
118
+ sell.locked -= value;
119
+
120
+ // OHLC UPDATE (example: 1m & 5m)
121
+ const now = Date.now();
122
+ this._updateOHLC(price, qty, now);
123
+
124
+ this.emit('trade', {
125
+ buyer: buy.user,
126
+ seller: sell.user,
127
+ price,
128
+ qty,
129
+ value,
130
+ time: now
131
+ });
132
+
133
+ if (buy.remaining <= 0) {
134
+ if (buy.locked > 0) {
135
+ this.asset.unlock(buy.user, buy.locked);
136
+ }
137
+ this.bids.shift();
138
+ }
139
+
140
+ if (sell.remaining <= 0) {
141
+ if (sell.locked > 0) {
142
+ this.asset.unlock(sell.user, sell.locked);
143
+ }
144
+ this.asks.shift();
145
+ }
146
+ }
147
+ }
148
+
149
+ _updateOHLC(price, qty, timestamp) {
150
+ const frames = [60000, 300000]; // 1m, 5m
151
+ for (const tf of frames) {
152
+ this.ensureOHLC(tf);
153
+ this.ohlcStores[tf].addPrice(price, qty, timestamp);
154
+ }
155
+ }
156
+
157
+ /* ===== CANCEL ===== */
158
+
159
+ cancel(orderId) {
160
+ const cancelFrom = list => {
161
+ const idx = list.findIndex(o => o.id === orderId);
162
+ if (idx === -1) return false;
163
+
164
+ const o = list[idx];
165
+ if (o.locked > 0) {
166
+ this.asset.unlock(o.user, o.locked);
167
+ }
168
+
169
+ list.splice(idx, 1);
170
+ return true;
171
+ };
172
+
173
+ const cancelled =
174
+ cancelFrom(this.bids) || cancelFrom(this.asks);
175
+
176
+ if (cancelled) {
177
+ this.emit('order_cancelled', orderId);
178
+ }
179
+ }
180
+ }
181
+
182
+ module.exports = OrderBook;