@icgio/icg-exchanges-wrapper 1.14.228 → 1.14.229
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 +72 -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,11 @@ 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 idx_order_status_created_at ON order_status(created_at);
|
|
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);
|
|
72
74
|
`
|
|
73
75
|
|
|
74
76
|
const client = await this.pool.connect()
|
|
@@ -85,7 +87,7 @@ class OrderStatusStore {
|
|
|
85
87
|
|
|
86
88
|
this.flush_interval = setInterval(() => {
|
|
87
89
|
this.flush().catch((err) => {
|
|
88
|
-
console.error('
|
|
90
|
+
console.error('OpenOrdersStore flush error:', err.message)
|
|
89
91
|
})
|
|
90
92
|
}, this.flush_interval_ms)
|
|
91
93
|
}
|
|
@@ -109,6 +111,15 @@ class OrderStatusStore {
|
|
|
109
111
|
store(exchange, pair, type, order_id, data, note) {
|
|
110
112
|
if (!this.enabled) return
|
|
111
113
|
|
|
114
|
+
// Convert Date objects to UTC ISO strings for consistent timezone handling
|
|
115
|
+
const to_utc_iso = (date) => {
|
|
116
|
+
if (!date) return null
|
|
117
|
+
if (date instanceof Date) {
|
|
118
|
+
return date.toISOString()
|
|
119
|
+
}
|
|
120
|
+
return date
|
|
121
|
+
}
|
|
122
|
+
|
|
112
123
|
const record = {
|
|
113
124
|
exchange,
|
|
114
125
|
pair,
|
|
@@ -122,8 +133,8 @@ class OrderStatusStore {
|
|
|
122
133
|
f_percentage: data.f_percentage || null,
|
|
123
134
|
t_amount: data.t_amount || null,
|
|
124
135
|
fees: data.fees ? JSON.stringify(data.fees) : null,
|
|
125
|
-
open_time: data.open_time
|
|
126
|
-
close_time: data.close_time
|
|
136
|
+
open_time: to_utc_iso(data.open_time),
|
|
137
|
+
close_time: to_utc_iso(data.close_time),
|
|
127
138
|
note: note || null,
|
|
128
139
|
}
|
|
129
140
|
|
|
@@ -143,35 +154,56 @@ class OrderStatusStore {
|
|
|
143
154
|
|
|
144
155
|
const batch = this.queue.splice(0, 100) // Process up to 100 at a time
|
|
145
156
|
|
|
146
|
-
const
|
|
147
|
-
INSERT INTO
|
|
157
|
+
const upsert_query = `
|
|
158
|
+
INSERT INTO open_orders (
|
|
148
159
|
exchange, pair, type, order_id, order_type, status,
|
|
149
160
|
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
|
|
161
|
+
fees, open_time, close_time, note, updated_at
|
|
162
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, (NOW() AT TIME ZONE 'UTC'))
|
|
163
|
+
ON CONFLICT (exchange, pair, type, order_id) DO UPDATE SET
|
|
164
|
+
order_type = EXCLUDED.order_type,
|
|
165
|
+
status = EXCLUDED.status,
|
|
166
|
+
price = EXCLUDED.price,
|
|
167
|
+
amount = EXCLUDED.amount,
|
|
168
|
+
f_amount = EXCLUDED.f_amount,
|
|
169
|
+
f_percentage = EXCLUDED.f_percentage,
|
|
170
|
+
t_amount = EXCLUDED.t_amount,
|
|
171
|
+
fees = EXCLUDED.fees,
|
|
172
|
+
open_time = EXCLUDED.open_time,
|
|
173
|
+
close_time = EXCLUDED.close_time,
|
|
174
|
+
note = EXCLUDED.note,
|
|
175
|
+
updated_at = (NOW() AT TIME ZONE 'UTC')
|
|
176
|
+
`
|
|
177
|
+
|
|
178
|
+
const delete_query = `
|
|
179
|
+
DELETE FROM open_orders
|
|
180
|
+
WHERE exchange = $1 AND pair = $2 AND type = $3 AND order_id = $4
|
|
153
181
|
`
|
|
154
182
|
|
|
155
183
|
const client = await this.pool.connect()
|
|
156
184
|
try {
|
|
157
185
|
for (const record of batch) {
|
|
158
|
-
|
|
159
|
-
record.exchange,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
186
|
+
if (record.status === 'DELETED') {
|
|
187
|
+
await client.query(delete_query, [record.exchange, record.pair, record.type, record.order_id])
|
|
188
|
+
} else {
|
|
189
|
+
await client.query(upsert_query, [
|
|
190
|
+
record.exchange,
|
|
191
|
+
record.pair,
|
|
192
|
+
record.type,
|
|
193
|
+
record.order_id,
|
|
194
|
+
record.order_type,
|
|
195
|
+
record.status,
|
|
196
|
+
record.price,
|
|
197
|
+
record.amount,
|
|
198
|
+
record.f_amount,
|
|
199
|
+
record.f_percentage,
|
|
200
|
+
record.t_amount,
|
|
201
|
+
record.fees,
|
|
202
|
+
record.open_time,
|
|
203
|
+
record.close_time,
|
|
204
|
+
record.note,
|
|
205
|
+
])
|
|
206
|
+
}
|
|
175
207
|
}
|
|
176
208
|
} catch (err) {
|
|
177
209
|
// Re-queue failed records
|
|
@@ -182,130 +214,13 @@ class OrderStatusStore {
|
|
|
182
214
|
}
|
|
183
215
|
}
|
|
184
216
|
|
|
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
217
|
async close() {
|
|
303
218
|
this.stop_flush_interval()
|
|
304
219
|
if (this.queue.length > 0) {
|
|
305
220
|
try {
|
|
306
221
|
await this.flush()
|
|
307
222
|
} catch (err) {
|
|
308
|
-
console.error('
|
|
223
|
+
console.error('OpenOrdersStore final flush error:', err.message)
|
|
309
224
|
}
|
|
310
225
|
}
|
|
311
226
|
if (this.pool) {
|
|
@@ -315,4 +230,4 @@ class OrderStatusStore {
|
|
|
315
230
|
}
|
|
316
231
|
}
|
|
317
232
|
|
|
318
|
-
module.exports =
|
|
233
|
+
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.229",
|
|
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",
|