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.
- package/README.md +14 -17
- package/fonts/Quicksilver.otf +0 -0
- package/index.js +13 -3
- package/lib/asset.js +170 -0
- package/lib/createThumbnail.js +138 -1
- package/lib/orderbook.js +182 -0
- package/lib/qc.js +446 -1
- package/lib/soal.json +2071 -0
- package/lib/tools.js +47 -38
- package/package.json +7 -5
package/lib/orderbook.js
ADDED
|
@@ -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;
|