@icgio/icg-exchanges-wrapper 1.14.228 → 1.14.230
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/lib/order-status-store.js +73 -157
- package/middleware/orders.js +5 -2
- package/package.json +2 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const { Pool } = require('pg')
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Async PostgreSQL store for
|
|
4
|
+
* Async PostgreSQL store for open orders
|
|
5
5
|
* Non-blocking: all writes are fire-and-forget to avoid blocking the main process
|
|
6
6
|
*/
|
|
7
|
-
class
|
|
7
|
+
class OpenOrdersStore {
|
|
8
8
|
constructor(connection_string) {
|
|
9
9
|
this.enabled = true
|
|
10
10
|
this.pool = null
|
|
@@ -30,23 +30,27 @@ class OrderStatusStore {
|
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
this.pool.on('error', (err) => {
|
|
33
|
-
console.error('
|
|
33
|
+
console.error('OpenOrdersStore pool error:', err.message)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Set timezone to UTC for all connections
|
|
37
|
+
this.pool.on('connect', async (client) => {
|
|
38
|
+
await client.query("SET timezone = 'UTC'")
|
|
34
39
|
})
|
|
35
40
|
|
|
36
41
|
await this.ensure_table()
|
|
37
42
|
this.enabled = true
|
|
38
43
|
this.start_flush_interval()
|
|
39
|
-
console.log('
|
|
44
|
+
console.log('OpenOrdersStore initialized successfully')
|
|
40
45
|
} catch (err) {
|
|
41
|
-
console.error('
|
|
46
|
+
console.error('OpenOrdersStore init failed:', err.message)
|
|
42
47
|
this.enabled = false
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
async ensure_table() {
|
|
47
52
|
const create_table_query = `
|
|
48
|
-
CREATE TABLE IF NOT EXISTS
|
|
49
|
-
id SERIAL PRIMARY KEY,
|
|
53
|
+
CREATE TABLE IF NOT EXISTS open_orders (
|
|
50
54
|
exchange VARCHAR(64) NOT NULL,
|
|
51
55
|
pair VARCHAR(32) NOT NULL,
|
|
52
56
|
order_type VARCHAR(64),
|
|
@@ -62,13 +66,12 @@ class OrderStatusStore {
|
|
|
62
66
|
open_time TIMESTAMPTZ,
|
|
63
67
|
close_time TIMESTAMPTZ,
|
|
64
68
|
note VARCHAR(256),
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
updated_at TIMESTAMPTZ DEFAULT (NOW() AT TIME ZONE 'UTC'),
|
|
70
|
+
PRIMARY KEY (exchange, pair, type, order_id)
|
|
67
71
|
);
|
|
68
|
-
CREATE INDEX IF NOT EXISTS
|
|
69
|
-
CREATE INDEX IF NOT EXISTS
|
|
70
|
-
CREATE INDEX IF NOT EXISTS
|
|
71
|
-
CREATE INDEX IF NOT EXISTS idx_order_status_status ON order_status(status);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_open_orders_status ON open_orders(status);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_open_orders_exchange ON open_orders(exchange);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_open_orders_ex_pair_order ON open_orders(exchange, pair, order_id);
|
|
72
75
|
`
|
|
73
76
|
|
|
74
77
|
const client = await this.pool.connect()
|
|
@@ -85,7 +88,7 @@ class OrderStatusStore {
|
|
|
85
88
|
|
|
86
89
|
this.flush_interval = setInterval(() => {
|
|
87
90
|
this.flush().catch((err) => {
|
|
88
|
-
console.error('
|
|
91
|
+
console.error('OpenOrdersStore flush error:', err.message)
|
|
89
92
|
})
|
|
90
93
|
}, this.flush_interval_ms)
|
|
91
94
|
}
|
|
@@ -109,6 +112,15 @@ class OrderStatusStore {
|
|
|
109
112
|
store(exchange, pair, type, order_id, data, note) {
|
|
110
113
|
if (!this.enabled) return
|
|
111
114
|
|
|
115
|
+
// Convert Date objects to UTC ISO strings for consistent timezone handling
|
|
116
|
+
const to_utc_iso = (date) => {
|
|
117
|
+
if (!date) return null
|
|
118
|
+
if (date instanceof Date) {
|
|
119
|
+
return date.toISOString()
|
|
120
|
+
}
|
|
121
|
+
return date
|
|
122
|
+
}
|
|
123
|
+
|
|
112
124
|
const record = {
|
|
113
125
|
exchange,
|
|
114
126
|
pair,
|
|
@@ -122,8 +134,8 @@ class OrderStatusStore {
|
|
|
122
134
|
f_percentage: data.f_percentage || null,
|
|
123
135
|
t_amount: data.t_amount || null,
|
|
124
136
|
fees: data.fees ? JSON.stringify(data.fees) : null,
|
|
125
|
-
open_time: data.open_time
|
|
126
|
-
close_time: data.close_time
|
|
137
|
+
open_time: to_utc_iso(data.open_time),
|
|
138
|
+
close_time: to_utc_iso(data.close_time),
|
|
127
139
|
note: note || null,
|
|
128
140
|
}
|
|
129
141
|
|
|
@@ -143,35 +155,56 @@ class OrderStatusStore {
|
|
|
143
155
|
|
|
144
156
|
const batch = this.queue.splice(0, 100) // Process up to 100 at a time
|
|
145
157
|
|
|
146
|
-
const
|
|
147
|
-
INSERT INTO
|
|
158
|
+
const upsert_query = `
|
|
159
|
+
INSERT INTO open_orders (
|
|
148
160
|
exchange, pair, type, order_id, order_type, status,
|
|
149
161
|
price, amount, f_amount, f_percentage, t_amount,
|
|
150
|
-
fees, open_time, close_time, note
|
|
151
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
|
152
|
-
ON CONFLICT (exchange, pair, type, order_id
|
|
162
|
+
fees, open_time, close_time, note, updated_at
|
|
163
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, (NOW() AT TIME ZONE 'UTC'))
|
|
164
|
+
ON CONFLICT (exchange, pair, type, order_id) DO UPDATE SET
|
|
165
|
+
order_type = EXCLUDED.order_type,
|
|
166
|
+
status = EXCLUDED.status,
|
|
167
|
+
price = EXCLUDED.price,
|
|
168
|
+
amount = EXCLUDED.amount,
|
|
169
|
+
f_amount = EXCLUDED.f_amount,
|
|
170
|
+
f_percentage = EXCLUDED.f_percentage,
|
|
171
|
+
t_amount = EXCLUDED.t_amount,
|
|
172
|
+
fees = EXCLUDED.fees,
|
|
173
|
+
open_time = EXCLUDED.open_time,
|
|
174
|
+
close_time = EXCLUDED.close_time,
|
|
175
|
+
note = EXCLUDED.note,
|
|
176
|
+
updated_at = (NOW() AT TIME ZONE 'UTC')
|
|
177
|
+
`
|
|
178
|
+
|
|
179
|
+
const delete_query = `
|
|
180
|
+
DELETE FROM open_orders
|
|
181
|
+
WHERE exchange = $1 AND pair = $2 AND type = $3 AND order_id = $4
|
|
153
182
|
`
|
|
154
183
|
|
|
155
184
|
const client = await this.pool.connect()
|
|
156
185
|
try {
|
|
157
186
|
for (const record of batch) {
|
|
158
|
-
|
|
159
|
-
record.exchange,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
187
|
+
if (record.status === 'DELETED') {
|
|
188
|
+
await client.query(delete_query, [record.exchange, record.pair, record.type, record.order_id])
|
|
189
|
+
} else {
|
|
190
|
+
await client.query(upsert_query, [
|
|
191
|
+
record.exchange,
|
|
192
|
+
record.pair,
|
|
193
|
+
record.type,
|
|
194
|
+
record.order_id,
|
|
195
|
+
record.order_type,
|
|
196
|
+
record.status,
|
|
197
|
+
record.price,
|
|
198
|
+
record.amount,
|
|
199
|
+
record.f_amount,
|
|
200
|
+
record.f_percentage,
|
|
201
|
+
record.t_amount,
|
|
202
|
+
record.fees,
|
|
203
|
+
record.open_time,
|
|
204
|
+
record.close_time,
|
|
205
|
+
record.note,
|
|
206
|
+
])
|
|
207
|
+
}
|
|
175
208
|
}
|
|
176
209
|
} catch (err) {
|
|
177
210
|
// Re-queue failed records
|
|
@@ -182,130 +215,13 @@ class OrderStatusStore {
|
|
|
182
215
|
}
|
|
183
216
|
}
|
|
184
217
|
|
|
185
|
-
/**
|
|
186
|
-
* Query order status history
|
|
187
|
-
* @param {object} filters - { exchange, pair, order_id, status, limit, offset }
|
|
188
|
-
* @returns {Promise<Array>}
|
|
189
|
-
*/
|
|
190
|
-
async query(filters = {}) {
|
|
191
|
-
if (!this.enabled || !this.table_ready) {
|
|
192
|
-
return []
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const conditions = []
|
|
196
|
-
const params = []
|
|
197
|
-
let param_index = 1
|
|
198
|
-
|
|
199
|
-
if (filters.exchange) {
|
|
200
|
-
conditions.push(`exchange = $${param_index++}`)
|
|
201
|
-
params.push(filters.exchange)
|
|
202
|
-
}
|
|
203
|
-
if (filters.pair) {
|
|
204
|
-
conditions.push(`pair = $${param_index++}`)
|
|
205
|
-
params.push(filters.pair)
|
|
206
|
-
}
|
|
207
|
-
if (filters.order_id) {
|
|
208
|
-
conditions.push(`order_id = $${param_index++}`)
|
|
209
|
-
params.push(filters.order_id)
|
|
210
|
-
}
|
|
211
|
-
if (filters.status) {
|
|
212
|
-
conditions.push(`status = $${param_index++}`)
|
|
213
|
-
params.push(filters.status)
|
|
214
|
-
}
|
|
215
|
-
if (filters.from_time) {
|
|
216
|
-
conditions.push(`created_at >= $${param_index++}`)
|
|
217
|
-
params.push(filters.from_time)
|
|
218
|
-
}
|
|
219
|
-
if (filters.to_time) {
|
|
220
|
-
conditions.push(`created_at <= $${param_index++}`)
|
|
221
|
-
params.push(filters.to_time)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const where_clause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
|
225
|
-
const limit = filters.limit || 100
|
|
226
|
-
const offset = filters.offset || 0
|
|
227
|
-
|
|
228
|
-
const query_str = `
|
|
229
|
-
SELECT * FROM order_status
|
|
230
|
-
${where_clause}
|
|
231
|
-
ORDER BY created_at DESC
|
|
232
|
-
LIMIT ${limit} OFFSET ${offset}
|
|
233
|
-
`
|
|
234
|
-
|
|
235
|
-
const client = await this.pool.connect()
|
|
236
|
-
try {
|
|
237
|
-
const result = await client.query(query_str, params)
|
|
238
|
-
return result.rows
|
|
239
|
-
} finally {
|
|
240
|
-
client.release()
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Get aggregated stats for orders
|
|
246
|
-
* @param {object} filters - { exchange, pair, from_time, to_time }
|
|
247
|
-
* @returns {Promise<object>}
|
|
248
|
-
*/
|
|
249
|
-
async get_stats(filters = {}) {
|
|
250
|
-
if (!this.enabled || !this.table_ready) {
|
|
251
|
-
return null
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const conditions = []
|
|
255
|
-
const params = []
|
|
256
|
-
let param_index = 1
|
|
257
|
-
|
|
258
|
-
if (filters.exchange) {
|
|
259
|
-
conditions.push(`exchange = $${param_index++}`)
|
|
260
|
-
params.push(filters.exchange)
|
|
261
|
-
}
|
|
262
|
-
if (filters.pair) {
|
|
263
|
-
conditions.push(`pair = $${param_index++}`)
|
|
264
|
-
params.push(filters.pair)
|
|
265
|
-
}
|
|
266
|
-
if (filters.from_time) {
|
|
267
|
-
conditions.push(`created_at >= $${param_index++}`)
|
|
268
|
-
params.push(filters.from_time)
|
|
269
|
-
}
|
|
270
|
-
if (filters.to_time) {
|
|
271
|
-
conditions.push(`created_at <= $${param_index++}`)
|
|
272
|
-
params.push(filters.to_time)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const where_clause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
|
276
|
-
|
|
277
|
-
const query_str = `
|
|
278
|
-
SELECT
|
|
279
|
-
status,
|
|
280
|
-
COUNT(*) as count,
|
|
281
|
-
SUM(COALESCE(f_amount, 0)) as total_filled_amount
|
|
282
|
-
FROM order_status
|
|
283
|
-
${where_clause}
|
|
284
|
-
GROUP BY status
|
|
285
|
-
`
|
|
286
|
-
|
|
287
|
-
const client = await this.pool.connect()
|
|
288
|
-
try {
|
|
289
|
-
const result = await client.query(query_str, params)
|
|
290
|
-
return result.rows.reduce((acc, row) => {
|
|
291
|
-
acc[row.status] = {
|
|
292
|
-
count: parseInt(row.count, 10),
|
|
293
|
-
total_filled_amount: parseFloat(row.total_filled_amount) || 0,
|
|
294
|
-
}
|
|
295
|
-
return acc
|
|
296
|
-
}, {})
|
|
297
|
-
} finally {
|
|
298
|
-
client.release()
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
218
|
async close() {
|
|
303
219
|
this.stop_flush_interval()
|
|
304
220
|
if (this.queue.length > 0) {
|
|
305
221
|
try {
|
|
306
222
|
await this.flush()
|
|
307
223
|
} catch (err) {
|
|
308
|
-
console.error('
|
|
224
|
+
console.error('OpenOrdersStore final flush error:', err.message)
|
|
309
225
|
}
|
|
310
226
|
}
|
|
311
227
|
if (this.pool) {
|
|
@@ -315,4 +231,4 @@ class OrderStatusStore {
|
|
|
315
231
|
}
|
|
316
232
|
}
|
|
317
233
|
|
|
318
|
-
module.exports =
|
|
234
|
+
module.exports = OpenOrdersStore
|
package/middleware/orders.js
CHANGED
|
@@ -7,7 +7,7 @@ const utils = require('@icgio/icg-utils').utils
|
|
|
7
7
|
const { coinmarketcap: get_coinmarketcap_rates, observable: Observable } = require('@icgio/icg-utils')
|
|
8
8
|
|
|
9
9
|
const Account = require('./account.js')
|
|
10
|
-
const
|
|
10
|
+
const OpenOrdersStore = require('../lib/order-status-store.js')
|
|
11
11
|
|
|
12
12
|
// Default precision functions - will be overridden if provided in settings
|
|
13
13
|
let round_price = (exchange, pair, price) => price
|
|
@@ -126,7 +126,7 @@ module.exports = class Orders {
|
|
|
126
126
|
this.balance_benchmark = settings.balance_benchmark || {}
|
|
127
127
|
this.min_reserve_balance = settings.min_reserve_balance || {}
|
|
128
128
|
// Initialize order status store for PostgreSQL persistence (async, non-blocking)
|
|
129
|
-
this.order_status_store = new
|
|
129
|
+
this.order_status_store = new OpenOrdersStore(cd.order_status_store_connection_string)
|
|
130
130
|
for (let exchange in exchange_pairs) {
|
|
131
131
|
let api_key_temp = cd[exchange]['api_keys'] || cd[exchange]['api_key'],
|
|
132
132
|
secret_key_temp = cd[exchange]['secret_keys'] || cd[exchange]['secret_key'],
|
|
@@ -761,8 +761,11 @@ module.exports = class Orders {
|
|
|
761
761
|
this.order_status_store.store(exchange, pair, type, order_id, order_data, note)
|
|
762
762
|
}
|
|
763
763
|
delete_order_info(exchange, pair, type, order_id, note) {
|
|
764
|
+
const order_data = _.get(this.order_info, [exchange, pair, type, order_id], {})
|
|
764
765
|
this.order_info[exchange][pair][type] = _.omit(this.order_info[exchange][pair][type], [order_id])
|
|
765
766
|
console.log('DELETE %s: %s %s %s %s', note, exchange, pair, type, order_id)
|
|
767
|
+
// Store deletion to PostgreSQL (non-blocking)
|
|
768
|
+
this.order_status_store.store(exchange, pair, type, order_id, { ...order_data, status: 'DELETED' }, note)
|
|
766
769
|
}
|
|
767
770
|
check_balances() {
|
|
768
771
|
let balances_handler = (exchange, res) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icgio/icg-exchanges-wrapper",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.230",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/icgio/icg-exchanges-wrapper#readme",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@icgio/icg-exchanges": "^1.32.
|
|
20
|
+
"@icgio/icg-exchanges": "^1.32.119",
|
|
21
21
|
"@icgio/icg-utils": "^1.9.38",
|
|
22
22
|
"lodash": "^4.17.20",
|
|
23
23
|
"pg": "^8.13.1",
|