@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.
@@ -1,10 +1,10 @@
1
1
  const { Pool } = require('pg')
2
2
 
3
3
  /**
4
- * Async PostgreSQL store for order status
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 OrderStatusStore {
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('OrderStatusStore pool error:', err.message)
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('OrderStatusStore initialized successfully')
44
+ console.log('OpenOrdersStore initialized successfully')
40
45
  } catch (err) {
41
- console.error('OrderStatusStore init failed:', err.message)
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 order_status (
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
- created_at TIMESTAMPTZ DEFAULT NOW(),
66
- UNIQUE(exchange, pair, type, order_id, status, created_at)
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 idx_order_status_exchange_pair ON order_status(exchange, pair);
69
- CREATE INDEX IF NOT EXISTS idx_order_status_order_id ON order_status(order_id);
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);
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('OrderStatusStore flush error:', err.message)
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 || null,
126
- close_time: data.close_time || null,
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 insert_query = `
147
- INSERT INTO order_status (
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, status, created_at) DO NOTHING
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
- await client.query(insert_query, [
159
- record.exchange,
160
- record.pair,
161
- record.type,
162
- record.order_id,
163
- record.order_type,
164
- record.status,
165
- record.price,
166
- record.amount,
167
- record.f_amount,
168
- record.f_percentage,
169
- record.t_amount,
170
- record.fees,
171
- record.open_time,
172
- record.close_time,
173
- record.note,
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('OrderStatusStore final flush error:', err.message)
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 = OrderStatusStore
234
+ module.exports = OpenOrdersStore
@@ -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 OrderStatusStore = require('../lib/order-status-store.js')
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 OrderStatusStore(cd.order_status_store_connection_string)
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.228",
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.118",
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",