walrasquant-lib 0.4.24__tar.gz → 0.4.26__tar.gz

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.
Files changed (105) hide show
  1. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/PKG-INFO +3 -3
  2. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/pyproject.toml +3 -3
  3. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/aggregation.py +5 -2
  4. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/backends/db_postgresql.py +9 -8
  5. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/backends/db_sqlite.py +9 -8
  6. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/connector.py +18 -8
  7. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/ems.py +10 -7
  8. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/oms.py +26 -17
  9. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/retry.py +11 -5
  10. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/sms.py +5 -5
  11. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/ws_client.py +19 -12
  12. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/config.py +14 -4
  13. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/core/cache.py +17 -12
  14. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/core/entity.py +12 -8
  15. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/core/nautilius_core.py +3 -0
  16. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/core/registry.py +5 -5
  17. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/engine.py +51 -24
  18. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/connector.py +40 -18
  19. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/exchange.py +8 -5
  20. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/oms.py +44 -29
  21. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/rest_api.py +4 -4
  22. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/websockets.py +3 -3
  23. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/connector.py +4 -4
  24. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/exchange.py +1 -1
  25. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/oms.py +65 -38
  26. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/rest_api.py +8 -4
  27. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/websockets.py +2 -2
  28. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/connector.py +3 -3
  29. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/exchange.py +1 -1
  30. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/oms.py +41 -25
  31. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/rest_api.py +4 -4
  32. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/websockets.py +10 -8
  33. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/connector.py +1 -1
  34. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/exchange.py +5 -2
  35. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/oms.py +42 -22
  36. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/rest_api.py +4 -4
  37. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/websockets.py +2 -2
  38. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/connector.py +5 -5
  39. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/exchange.py +1 -1
  40. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/oms.py +43 -24
  41. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/rest_api.py +8 -4
  42. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/websockets.py +2 -2
  43. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/registry.py +5 -2
  44. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/execution/algorithm.py +43 -21
  45. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/execution/algorithms/twap.py +48 -23
  46. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/push.py +1 -1
  47. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/strategy.py +56 -16
  48. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/tools/pm2_wrapper.py +1 -1
  49. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/README.md +0 -0
  50. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/__init__.py +0 -0
  51. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/backends/__init__.py +0 -0
  52. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/backends/db.py +0 -0
  53. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/backends/db_memory.py +0 -0
  54. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/__init__.py +0 -0
  55. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/api_client.py +0 -0
  56. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/base/exchange.py +0 -0
  57. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/constants.py +0 -0
  58. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/core/__init__.py +0 -0
  59. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/core/clock.py +0 -0
  60. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/core/connection.py +0 -0
  61. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/error.py +0 -0
  62. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/__init__.py +0 -0
  63. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/base_factory.py +0 -0
  64. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/__init__.py +0 -0
  65. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/constants.py +0 -0
  66. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/ems.py +0 -0
  67. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/error.py +0 -0
  68. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/factory.py +0 -0
  69. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/binance/schema.py +0 -0
  70. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/__init__.py +0 -0
  71. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/constants.py +0 -0
  72. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/ems.py +0 -0
  73. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/error.py +0 -0
  74. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/factory.py +0 -0
  75. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bitget/schema.py +0 -0
  76. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/__init__.py +0 -0
  77. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/constants.py +0 -0
  78. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/ems.py +0 -0
  79. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/error.py +0 -0
  80. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/factory.py +0 -0
  81. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/bybit/schema.py +0 -0
  82. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/__init__.py +0 -0
  83. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/constants.py +0 -0
  84. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/ems.py +0 -0
  85. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/error.py +0 -0
  86. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/factory.py +0 -0
  87. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/schema.py +0 -0
  88. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/hyperliquid/signing.py +0 -0
  89. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/__init__.py +0 -0
  90. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/constants.py +0 -0
  91. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/ems.py +0 -0
  92. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/error.py +0 -0
  93. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/factory.py +0 -0
  94. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/exchange/okx/schema.py +0 -0
  95. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/execution/__init__.py +0 -0
  96. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/execution/algorithms/__init__.py +0 -0
  97. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/execution/config.py +0 -0
  98. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/execution/constants.py +0 -0
  99. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/execution/schema.py +0 -0
  100. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/indicator.py +0 -0
  101. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/schema.py +0 -0
  102. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/tools/__init__.py +0 -0
  103. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/web/__init__.py +0 -0
  104. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/web/app.py +0 -0
  105. {walrasquant_lib-0.4.24 → walrasquant_lib-0.4.26}/src/walrasquant/web/server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: walrasquant-lib
3
- Version: 0.4.24
3
+ Version: 0.4.26
4
4
  Summary: fastest python trading bot
5
5
  Author: River-Shi
6
6
  Author-email: River-Shi <nachuan.shi.quant@gmail.com>
@@ -23,9 +23,9 @@ Requires-Dist: dynaconf[redis]>=3.2.12
23
23
  Requires-Dist: aiohttp>=3.13.3
24
24
  Requires-Dist: rich>=14.0.0
25
25
  Requires-Dist: picows>=2.1.1
26
- Requires-Dist: nexuscore-lib>=0.2.0
26
+ Requires-Dist: nexuscore-lib>=0.2.1
27
27
  Requires-Dist: flashduty-lib>=0.2.0
28
- Requires-Dist: nexuslog-lib>=0.5.0
28
+ Requires-Dist: nexuslog-lib>=0.8.0
29
29
  Requires-Dist: msgspec>=0.21.1
30
30
  Requires-Dist: pandas<3.0.0
31
31
  Requires-Dist: numpy>=2.2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "walrasquant-lib"
3
- version = "0.4.24"
3
+ version = "0.4.26"
4
4
  description = "fastest python trading bot"
5
5
  authors = [
6
6
  {name = "River-Shi",email = "nachuan.shi.quant@gmail.com"}
@@ -27,9 +27,9 @@ dependencies = [
27
27
  "aiohttp>=3.13.3",
28
28
  "rich>=14.0.0",
29
29
  "picows>=2.1.1",
30
- "nexuscore-lib>=0.2.0",
30
+ "nexuscore-lib>=0.2.1",
31
31
  "flashduty-lib>=0.2.0",
32
- "nexuslog-lib>=0.5.0",
32
+ "nexuslog-lib>=0.8.0",
33
33
  "msgspec>=0.21.1",
34
34
  "pandas<3.0.0",
35
35
  "numpy>=2.2.0",
@@ -396,7 +396,10 @@ class TimeKlineAggregator(KlineAggregator):
396
396
  callback=self._build_bar,
397
397
  )
398
398
  self._log.debug(
399
- f"Timer set: {self._timer_name}, start={start_time}, interval={interval_td}"
399
+ "Timer set: %s, start=%s, interval=%s",
400
+ self._timer_name,
401
+ start_time,
402
+ interval_td,
400
403
  )
401
404
 
402
405
  def _get_start_time(self):
@@ -446,4 +449,4 @@ class TimeKlineAggregator(KlineAggregator):
446
449
 
447
450
  # # Build and send kline (only if trades were received)
448
451
  self._build_and_send(ts_event, ts_init)
449
- self._log.debug(f"Kline built: ts_event={ts_event}, ts_init={ts_init}")
452
+ self._log.debug("Kline built: ts_event=%s, ts_init=%s", ts_event, ts_init)
@@ -139,7 +139,8 @@ class PostgreSQLBackend(StorageBackend):
139
139
  str(symbol),
140
140
  )
141
141
  self._log.debug(
142
- f"Deleted {len(positions_to_delete)} stale positions from database"
142
+ "Deleted %s stale positions from database",
143
+ len(positions_to_delete),
143
144
  )
144
145
 
145
146
  for symbol, position in mem_positions.copy().items():
@@ -219,7 +220,7 @@ class PostgreSQLBackend(StorageBackend):
219
220
  return order
220
221
  return None
221
222
  except psycopg2.DatabaseError as error:
222
- self._log.error(f"Error getting order from PostgreSQL: {error}")
223
+ self._log.error("Error getting order from PostgreSQL: %s", error)
223
224
  return None
224
225
 
225
226
  def get_symbol_orders(self, symbol: str) -> Set[str]:
@@ -277,9 +278,9 @@ class PostgreSQLBackend(StorageBackend):
277
278
  serialized_value,
278
279
  )
279
280
  except msgspec.EncodeError as e:
280
- self._log.error(f"Error serializing parameter {key}: {e}")
281
+ self._log.error("Error serializing parameter %s: %s", key, e)
281
282
  except Exception as e:
282
- self._log.error(f"Error storing parameter {key}: {e}")
283
+ self._log.error("Error storing parameter %s: %s", key, e)
283
284
 
284
285
  def get_param(self, key: str, default: Any = None) -> Any:
285
286
  import msgspec
@@ -296,11 +297,11 @@ class PostgreSQLBackend(StorageBackend):
296
297
  try:
297
298
  return self._decode_param(row[0])
298
299
  except msgspec.DecodeError as e:
299
- self._log.error(f"Error deserializing parameter {key}: {e}")
300
+ self._log.error("Error deserializing parameter %s: %s", key, e)
300
301
  return default
301
302
  return default
302
303
  except Exception as e:
303
- self._log.error(f"Error getting parameter {key}: {e}")
304
+ self._log.error("Error getting parameter %s: %s", key, e)
304
305
  return default
305
306
 
306
307
  def get_all_params(self) -> Dict[str, Any]:
@@ -314,8 +315,8 @@ class PostgreSQLBackend(StorageBackend):
314
315
  try:
315
316
  params[row[0]] = self._decode_param(row[1])
316
317
  except msgspec.DecodeError as e:
317
- self._log.error(f"Error deserializing parameter {row[0]}: {e}")
318
+ self._log.error("Error deserializing parameter %s: %s", row[0], e)
318
319
  cursor.close()
319
320
  except Exception as e:
320
- self._log.error(f"Error getting all parameters: {e}")
321
+ self._log.error("Error getting all parameters: %s", e)
321
322
  return params
@@ -142,7 +142,8 @@ class SQLiteBackend(StorageBackend):
142
142
  [(symbol,) for symbol in positions_to_delete],
143
143
  )
144
144
  self._log.debug(
145
- f"Deleted {len(positions_to_delete)} stale positions from database"
145
+ "Deleted %s stale positions from database",
146
+ len(positions_to_delete),
146
147
  )
147
148
 
148
149
  for symbol, position in mem_positions.copy().items():
@@ -217,7 +218,7 @@ class SQLiteBackend(StorageBackend):
217
218
  return None
218
219
 
219
220
  except sqlite3.Error as e:
220
- self._log.error(f"Error getting order from SQLite: {e}")
221
+ self._log.error("Error getting order from SQLite: %s", e)
221
222
  return None
222
223
 
223
224
  def get_symbol_orders(self, symbol: str) -> Set[str]:
@@ -269,9 +270,9 @@ class SQLiteBackend(StorageBackend):
269
270
  (key, serialized_value),
270
271
  )
271
272
  except msgspec.EncodeError as e:
272
- self._log.error(f"Error serializing parameter {key}: {e}")
273
+ self._log.error("Error serializing parameter %s: %s", key, e)
273
274
  except sqlite3.Error as e:
274
- self._log.error(f"Error storing parameter {key}: {e}")
275
+ self._log.error("Error storing parameter %s: %s", key, e)
275
276
  await self._async_conn.commit()
276
277
 
277
278
  def get_param(self, key: str, default: Any = None) -> Any:
@@ -286,11 +287,11 @@ class SQLiteBackend(StorageBackend):
286
287
  try:
287
288
  return self._decode_param(row[0])
288
289
  except msgspec.DecodeError as e:
289
- self._log.error(f"Error deserializing parameter {key}: {e}")
290
+ self._log.error("Error deserializing parameter %s: %s", key, e)
290
291
  return default
291
292
  return default
292
293
  except sqlite3.Error as e:
293
- self._log.error(f"Error getting parameter {key}: {e}")
294
+ self._log.error("Error getting parameter %s: %s", key, e)
294
295
  return default
295
296
 
296
297
  def get_all_params(self) -> Dict[str, Any]:
@@ -304,7 +305,7 @@ class SQLiteBackend(StorageBackend):
304
305
  try:
305
306
  params[row[0]] = self._decode_param(row[1])
306
307
  except msgspec.DecodeError as e:
307
- self._log.error(f"Error deserializing parameter {row[0]}: {e}")
308
+ self._log.error("Error deserializing parameter %s: %s", row[0], e)
308
309
  except sqlite3.Error as e:
309
- self._log.error(f"Error getting all parameters: {e}")
310
+ self._log.error("Error getting all parameters: %s", e)
310
311
  return params
@@ -223,7 +223,7 @@ class PublicConnector(ABC):
223
223
  self._add_aggregator(symbol, aggregator)
224
224
 
225
225
  self._log.info(
226
- f"Time kline aggregator created for {symbol} with interval {interval}"
226
+ "Time kline aggregator created for %s with interval %s", symbol, interval
227
227
  )
228
228
 
229
229
  def unsubscribe_kline_aggregator(
@@ -247,7 +247,9 @@ class PublicConnector(ABC):
247
247
  aggregator.stop()
248
248
  aggregators.remove(aggregator)
249
249
  self._log.info(
250
- f"Time kline aggregator stopped for {symbol} with interval {interval}"
250
+ "Time kline aggregator stopped for %s with interval %s",
251
+ symbol,
252
+ interval,
251
253
  )
252
254
  if not aggregators:
253
255
  self._aggregators.pop(symbol, None)
@@ -275,7 +277,10 @@ class PublicConnector(ABC):
275
277
  self._add_aggregator(symbol, aggregator)
276
278
 
277
279
  self._log.info(
278
- f"Volume kline aggregator created for {symbol} with threshold {volume_threshold} and type {volume_type}"
280
+ "Volume kline aggregator created for %s with threshold %s and type %s",
281
+ symbol,
282
+ volume_threshold,
283
+ volume_type,
279
284
  )
280
285
 
281
286
  def unsubscribe_volume_kline_aggregator(
@@ -296,7 +301,10 @@ class PublicConnector(ABC):
296
301
  ):
297
302
  aggregators.remove(aggregator)
298
303
  self._log.info(
299
- f"Volume kline aggregator stopped for {symbol} with threshold {volume_threshold} and type {volume_type}"
304
+ "Volume kline aggregator stopped for %s with threshold %s and type %s",
305
+ symbol,
306
+ volume_threshold,
307
+ volume_type,
300
308
  )
301
309
  if not aggregators:
302
310
  self._aggregators.pop(symbol, None)
@@ -690,7 +698,7 @@ class MockLinearConnector:
690
698
  )
691
699
  return order
692
700
  except OrderError as e:
693
- self._log.error(f"Error creating order: {e}")
701
+ self._log.error("Error creating order: %s", e)
694
702
  return Order(
695
703
  exchange=self._exchange_id,
696
704
  timestamp=self._clock.timestamp_ms(),
@@ -726,7 +734,8 @@ class MockLinearConnector:
726
734
  book = self._cache.bookl1(symbol)
727
735
  if not book:
728
736
  self._log.warning(
729
- f"Please subscribe to the `bookl1` data for {symbol} or data not ready"
737
+ "Please subscribe to the `bookl1` data for %s or data not ready",
738
+ symbol,
730
739
  )
731
740
  continue
732
741
  notional += float(position.amount) * book.mid
@@ -739,7 +748,8 @@ class MockLinearConnector:
739
748
  book = self._cache.bookl1(symbol)
740
749
  if not book:
741
750
  self._log.warning(
742
- f"Please subscribe to the `bookl1` data for {symbol} or data not ready"
751
+ "Please subscribe to the `bookl1` data for %s or data not ready",
752
+ symbol,
743
753
  )
744
754
  return
745
755
 
@@ -851,7 +861,7 @@ class MockLinearConnector:
851
861
  self._apply_fee(order)
852
862
 
853
863
  async def connect(self):
854
- self._log.debug(f"Starting mock connector for {self._account_type}")
864
+ self._log.debug("Starting mock connector for %s", self._account_type)
855
865
  await self._init_position()
856
866
  await self._init_balance()
857
867
 
@@ -138,7 +138,7 @@ class ExecutionManagementSystem(ABC):
138
138
  def _do_put(self, queue: PriorityOrderQueue, item: Any) -> None:
139
139
  # Always runs on the loop thread: queue access + bookkeeping are serialized.
140
140
  if self._shutdown:
141
- self._log.warning(f"EMS is shutting down; order submit rejected: {item}")
141
+ self._log.warning("EMS is shutting down; order submit rejected: %s", item)
142
142
  return
143
143
  priority = _item_priority(item)
144
144
 
@@ -161,7 +161,7 @@ class ExecutionManagementSystem(ABC):
161
161
  return
162
162
  exc = task.exception()
163
163
  if exc:
164
- self._log.error(f"Queue put failed: {exc!r}")
164
+ self._log.error("Queue put failed: %r", exc)
165
165
 
166
166
  def _build(self, private_connectors: Dict[AccountType, PrivateConnector]):
167
167
  self._private_connectors = private_connectors
@@ -440,7 +440,7 @@ class ExecutionManagementSystem(ABC):
440
440
  SubmitType.CANCEL_BATCH: self._cancel_batch_orders,
441
441
  }
442
442
 
443
- self._log.debug(f"Handling orders for account type: {account_type}")
443
+ self._log.debug("Handling orders for account type: %s", account_type)
444
444
  # Each dequeued item runs as its own task so one in-flight REST round
445
445
  # trip does not block the rest of the queue. The semaphore throttles
446
446
  # the dequeue itself: excess items stay in the priority queue (where
@@ -452,7 +452,7 @@ class ExecutionManagementSystem(ABC):
452
452
  queue_item = await queue.get()
453
453
  await semaphore.acquire()
454
454
  (order_submit, submit_type) = queue_item
455
- self._log.debug(f"[ORDER SUBMIT]: {order_submit}")
455
+ self._log.debug("[ORDER SUBMIT]: %s", order_submit)
456
456
  self._task_manager.create_task(
457
457
  self._run_submit(
458
458
  submit_handlers[submit_type],
@@ -476,9 +476,12 @@ class ExecutionManagementSystem(ABC):
476
476
  except Exception:
477
477
  self._log.error(
478
478
  "Order submit handler failed, "
479
- f"account_type={account_type}, submit_type={submit_type}, "
480
- f"order_submit={order_submit}\n"
481
- f"Traceback: {traceback.format_exc()}"
479
+ "account_type=%s, submit_type=%s, order_submit=%s\n"
480
+ "Traceback: %s",
481
+ account_type,
482
+ submit_type,
483
+ order_submit,
484
+ traceback.format_exc(),
482
485
  )
483
486
  finally:
484
487
  semaphore.release()
@@ -81,31 +81,31 @@ class OrderManagementSystem(ABC):
81
81
  valid = self._cache._order_status_update(order) # INITIALIZED -> PENDING
82
82
  match order.status:
83
83
  case OrderStatus.PENDING:
84
- self._log.debug(f"ORDER STATUS PENDING: {str(order)}")
84
+ self._log.debug("ORDER STATUS PENDING: %s", order)
85
85
  self._msgbus.send(endpoint="pending", msg=order)
86
86
  case OrderStatus.FAILED:
87
- self._log.debug(f"ORDER STATUS FAILED: {str(order)}")
87
+ self._log.debug("ORDER STATUS FAILED: %s", order)
88
88
  self._msgbus.send(endpoint="failed", msg=order)
89
89
  case OrderStatus.ACCEPTED:
90
- self._log.debug(f"ORDER STATUS ACCEPTED: {str(order)}")
90
+ self._log.debug("ORDER STATUS ACCEPTED: %s", order)
91
91
  self._msgbus.send(endpoint="accepted", msg=order)
92
92
  case OrderStatus.PARTIALLY_FILLED:
93
- self._log.debug(f"ORDER STATUS PARTIALLY FILLED: {str(order)}")
93
+ self._log.debug("ORDER STATUS PARTIALLY FILLED: %s", order)
94
94
  self._msgbus.send(endpoint="partially_filled", msg=order)
95
95
  case OrderStatus.CANCELED:
96
- self._log.debug(f"ORDER STATUS CANCELED: {str(order)}")
96
+ self._log.debug("ORDER STATUS CANCELED: %s", order)
97
97
  self._msgbus.send(endpoint="canceled", msg=order)
98
98
  case OrderStatus.CANCELING:
99
- self._log.debug(f"ORDER STATUS CANCELING: {str(order)}")
99
+ self._log.debug("ORDER STATUS CANCELING: %s", order)
100
100
  self._msgbus.send(endpoint="canceling", msg=order)
101
101
  case OrderStatus.CANCEL_FAILED:
102
- self._log.debug(f"ORDER STATUS CANCEL FAILED: {str(order)}")
102
+ self._log.debug("ORDER STATUS CANCEL FAILED: %s", order)
103
103
  self._msgbus.send(endpoint="cancel_failed", msg=order)
104
104
  case OrderStatus.FILLED:
105
- self._log.debug(f"ORDER STATUS FILLED: {str(order)}")
105
+ self._log.debug("ORDER STATUS FILLED: %s", order)
106
106
  self._msgbus.send(endpoint="filled", msg=order)
107
107
  case OrderStatus.EXPIRED:
108
- self._log.debug(f"ORDER STATUS EXPIRED: {str(order)}")
108
+ self._log.debug("ORDER STATUS EXPIRED: %s", order)
109
109
  self._msgbus.send(endpoint="expired", msg=order)
110
110
 
111
111
  if valid and order.status == OrderStatus.CANCEL_FAILED:
@@ -139,7 +139,7 @@ class OrderManagementSystem(ABC):
139
139
  except asyncio.CancelledError:
140
140
  raise
141
141
  except Exception as exc:
142
- self._log.error(f"Order query scan loop stopped unexpectedly: {exc}")
142
+ self._log.error("Order query scan loop stopped unexpectedly: %s", exc)
143
143
  raise
144
144
 
145
145
  def _scan_timed_out_orders(self) -> None:
@@ -209,19 +209,25 @@ class OrderManagementSystem(ABC):
209
209
 
210
210
  try:
211
211
  self._log.debug(
212
- f"Querying order status: reason={reason} symbol={order.symbol} oid={oid}"
212
+ "Querying order status: reason=%s symbol=%s oid=%s",
213
+ reason,
214
+ order.symbol,
215
+ oid,
213
216
  )
214
217
  queried_order = await self.query_order(oid=oid, symbol=order.symbol)
215
218
  if queried_order is None:
216
219
  self._log.warning(
217
- f"Order status query returned no order: reason={reason} "
218
- f"symbol={order.symbol} oid={oid}"
220
+ "Order status query returned no order: reason=%s symbol=%s oid=%s",
221
+ reason,
222
+ order.symbol,
223
+ oid,
219
224
  )
220
225
  return
221
226
  if queried_order.oid is None:
222
227
  self._log.error(
223
- f"Order status query returned order without oid: "
224
- f"symbol={order.symbol} oid={oid}"
228
+ "Order status query returned order without oid: symbol=%s oid=%s",
229
+ order.symbol,
230
+ oid,
225
231
  )
226
232
  return
227
233
  self.order_status_update(queried_order)
@@ -229,8 +235,11 @@ class OrderManagementSystem(ABC):
229
235
  raise
230
236
  except Exception as exc:
231
237
  self._log.error(
232
- f"Order status query failed: reason={reason} "
233
- f"symbol={order.symbol} oid={oid}: {exc}"
238
+ "Order status query failed: reason=%s symbol=%s oid=%s: %s",
239
+ reason,
240
+ order.symbol,
241
+ oid,
242
+ exc,
234
243
  )
235
244
  finally:
236
245
  self._querying_oids.discard(oid)
@@ -181,7 +181,7 @@ class RetryManager(Generic[T]):
181
181
  """
182
182
  Cancel the retry operation.
183
183
  """
184
- self._log.debug(f"Canceling {self!r}")
184
+ self._log.debug("Canceling %r", self)
185
185
  self.cancel_event.set()
186
186
 
187
187
  def clear(self) -> None:
@@ -195,19 +195,25 @@ class RetryManager(Generic[T]):
195
195
  self.message = None
196
196
 
197
197
  def _cancel(self) -> None:
198
- self._log.warning(f"Canceled retry for '{self.name}'")
198
+ self._log.warning("Canceled retry for '%s'", self.name)
199
199
  self.result = False
200
200
  self.message = "Canceled retry"
201
201
 
202
202
  def _log_retry(self, retry: int, retry_delay_ms: int) -> None:
203
203
  self._log.warning(
204
- f"Retrying {retry}/{self.max_retries} for '{self.name}' "
205
- f"in {retry_delay_ms / 1000}s{self._details_str()}",
204
+ "Retrying %s/%s for '%s' in %ss%s",
205
+ retry,
206
+ self.max_retries,
207
+ self.name,
208
+ retry_delay_ms / 1000,
209
+ self._details_str(),
206
210
  )
207
211
 
208
212
  def _log_error(self) -> None:
209
213
  self._log.error(
210
- f"Failed on '{self.name}'{self._details_str()}",
214
+ "Failed on '%s'%s",
215
+ self.name,
216
+ self._details_str(),
211
217
  )
212
218
 
213
219
  def _details_str(self) -> str:
@@ -73,7 +73,7 @@ class SubscriptionManagementSystem:
73
73
  return
74
74
  exc = task.exception()
75
75
  if exc:
76
- self._log.error(f"Queue put failed: {exc!r}")
76
+ self._log.error("Queue put failed: %r", exc)
77
77
 
78
78
  def _infer_account_type(self, symbol: str) -> AccountType:
79
79
  """Infer account type from symbol"""
@@ -143,7 +143,7 @@ class SubscriptionManagementSystem:
143
143
  )
144
144
 
145
145
  self._safe_put(self._subscribe_queue, subscription)
146
- self._log.debug(f"Subscription queued: {data_type} for {symbols}")
146
+ self._log.debug("Subscription queued: %s for %s", data_type, symbols)
147
147
 
148
148
  def unsubscribe(
149
149
  self,
@@ -172,7 +172,7 @@ class SubscriptionManagementSystem:
172
172
  )
173
173
 
174
174
  self._safe_put(self._unsubscribe_queue, unsubscription)
175
- self._log.debug(f"Unsubscription queued: {data_type} for {symbols}")
175
+ self._log.debug("Unsubscription queued: %s for %s", data_type, symbols)
176
176
 
177
177
  def _subscribe_trade(
178
178
  self, subscription: SubscriptionSubmit, account_type: AccountType
@@ -448,7 +448,7 @@ class SubscriptionManagementSystem:
448
448
 
449
449
  while True:
450
450
  subscription = await self._subscribe_queue.get()
451
- self._log.debug(f"[SUBSCRIPTION]: {subscription}")
451
+ self._log.debug("[SUBSCRIPTION]: %s", subscription)
452
452
 
453
453
  grouped = self._group_symbols_by_account_type(subscription.symbols)
454
454
 
@@ -494,7 +494,7 @@ class SubscriptionManagementSystem:
494
494
 
495
495
  while True:
496
496
  unsubscription = await self._unsubscribe_queue.get()
497
- self._log.debug(f"[UNSUBSCRIPTION]: {unsubscription}")
497
+ self._log.debug("[UNSUBSCRIPTION]: %s", unsubscription)
498
498
 
499
499
  grouped = self._group_symbols_by_account_type(unsubscription.symbols)
500
500
  # Process each account type group
@@ -67,7 +67,7 @@ class Listener(WSListener):
67
67
  if self._specific_ping_msg:
68
68
  transport.send(WSMsgType.TEXT, self._specific_ping_msg)
69
69
  self._log.debug(
70
- f"Sent user specific ping: `{self._specific_ping_msg.decode()}`."
70
+ "Sent user specific ping: `%s`.", self._specific_ping_msg.decode()
71
71
  )
72
72
  else:
73
73
  transport.send_ping()
@@ -124,14 +124,17 @@ class Listener(WSListener):
124
124
  case WSMsgType.CLOSE:
125
125
  close_code = frame.get_close_code()
126
126
  self._log.warning(
127
- f"Received close frame. Close code: {str(close_code)}"
127
+ "Received close frame. Close code: %s", close_code
128
128
  )
129
129
  return
130
130
  except Exception as e:
131
131
  import traceback
132
132
 
133
133
  self._log.error(
134
- f"Error processing message: {str(e)}\nTraceback: {traceback.format_exc()}\nws_frame: {self._decode_frame(frame)}"
134
+ "Error processing message: %s\nTraceback: %s\nws_frame: %s",
135
+ e,
136
+ traceback.format_exc(),
137
+ self._decode_frame(frame),
135
138
  )
136
139
 
137
140
 
@@ -204,7 +207,7 @@ class WSClient(ABC):
204
207
  try:
205
208
  self._connection_change_callback(client_id, connected)
206
209
  except Exception as exc: # pragma: no cover - defensive logging
207
- self._log.warning(f"Connection change callback error: {exc}")
210
+ self._log.warning("Connection change callback error: %s", exc)
208
211
 
209
212
  def _is_client_connected(self, client_id: int) -> bool:
210
213
  return self._transports.get(client_id) is not None
@@ -289,7 +292,7 @@ class WSClient(ABC):
289
292
 
290
293
  async def _connect(self, client_id: int):
291
294
  self._log.debug(
292
- f"Connecting to Websocket at {self._url} (client {client_id})..."
295
+ "Connecting to Websocket at %s (client %s)...", self._url, client_id
293
296
  )
294
297
  WSListenerFactory = lambda: Listener( # noqa: E731
295
298
  self._callback,
@@ -309,7 +312,9 @@ class WSClient(ABC):
309
312
  self._transports[client_id] = transport
310
313
  self._listeners[client_id] = listener
311
314
  self._log.debug(
312
- f"Websocket connected successfully to {self._url} (client {client_id})."
315
+ "Websocket connected successfully to %s (client %s).",
316
+ self._url,
317
+ client_id,
313
318
  )
314
319
  self._notify_connection_change(client_id, True)
315
320
 
@@ -350,20 +355,22 @@ class WSClient(ABC):
350
355
  if transport is None:
351
356
  break
352
357
  await transport.wait_disconnected()
353
- self._log.debug(f"Websocket disconnected (client {client_id}).")
358
+ self._log.debug("Websocket disconnected (client %s).", client_id)
354
359
  self._notify_connection_change(client_id, False)
355
360
  except asyncio.CancelledError:
356
361
  self._log.debug(
357
- f"Websocket connection loop cancelled (client {client_id})."
362
+ "Websocket connection loop cancelled (client %s).", client_id
358
363
  )
359
364
  break
360
365
  except Exception as e:
361
- self._log.error(f"Connection error: {e}")
366
+ self._log.error("Connection error: %s", e)
362
367
  # finally:
363
368
  # self._clean_up_client(client_id)
364
369
 
365
370
  self._log.warning(
366
- f"Websocket reconnecting in {self._reconnect_interval} seconds (client {client_id})..."
371
+ "Websocket reconnecting in %s seconds (client %s)...",
372
+ self._reconnect_interval,
373
+ client_id,
367
374
  )
368
375
  await asyncio.sleep(self._reconnect_interval)
369
376
 
@@ -384,11 +391,11 @@ class WSClient(ABC):
384
391
  if target_id is None:
385
392
  target_id = self._primary_client_id()
386
393
  if target_id is None:
387
- self._log.warning(f"Websocket not connected. drop msg: {str(payload)}")
394
+ self._log.warning("Websocket not connected. drop msg: %s", payload)
388
395
  return
389
396
  transport = self._transports.get(target_id)
390
397
  if transport is None:
391
- self._log.warning(f"Websocket not connected. drop msg: {str(payload)}")
398
+ self._log.warning("Websocket not connected. drop msg: %s", payload)
392
399
  return
393
400
  transport.send(WSMsgType.TEXT, msgspec.json.encode(payload))
394
401
 
@@ -14,16 +14,21 @@ class LogConfig:
14
14
  Maps directly to nexuslog.basicConfig() parameters.
15
15
 
16
16
  Attributes:
17
- filename: Optional file path for log output. If None, logs to stdout.
17
+ filename: Optional file path for log output. If "auto", writes to
18
+ logs/{strategy_id}_{user_id}.log. If None, logs to stdout.
18
19
  level: Minimum log level to record. Options: TRACE, DEBUG, INFO, WARNING, ERROR
19
20
  unix_ts: If True, emit unix timestamps instead of formatted local time.
21
+ color: ANSI colorization of log output. "auto" colors only when writing
22
+ to a color-capable terminal, "off" disables it, "always" forces it
23
+ even for files and pipes.
20
24
  """
21
25
 
22
- filename: str | None = None
26
+ filename: Literal["auto"] | str | None = None
23
27
  level: LOG_LEVELS = "INFO"
24
28
  name_levels: dict[str | None, LOG_LEVELS] | None = None
25
29
  unix_ts: bool = False
26
30
  batch_size: int = 1
31
+ color: Literal["auto", "off", "always"] = "auto"
27
32
 
28
33
  def __post_init__(self):
29
34
  if self.level not in ["TRACE", "DEBUG", "INFO", "WARNING", "ERROR"]:
@@ -38,6 +43,11 @@ class LogConfig:
38
43
  f"Invalid level: {level} for logger name: {name}. Must be one of TRACE, DEBUG, INFO, WARNING, ERROR."
39
44
  )
40
45
 
46
+ if self.color not in ["auto", "off", "always"]:
47
+ raise ValueError(
48
+ f"Invalid color: {self.color}. Must be one of auto, off, always."
49
+ )
50
+
41
51
 
42
52
  @dataclass
43
53
  class BasicConfig:
@@ -265,7 +275,7 @@ class Config:
265
275
  )
266
276
  zero_mq_signal_config: ZeroMQSignalConfig | None = None
267
277
  db_path: str = ".keys/cache.db"
268
- storage_backend: StorageType = StorageType.SQLITE
278
+ storage_backend: StorageType = StorageType.MEMORY
269
279
  cache_sync_interval: int = 60
270
280
  cache_expired_time: int = 3600
271
281
  log_config: LogConfig = field(default_factory=LogConfig)
@@ -277,7 +287,7 @@ class Config:
277
287
  order_query_config: OrderQueryConfig = field(default_factory=OrderQueryConfig)
278
288
 
279
289
  def __post_init__(self):
280
- if self.log_config.filename is None:
290
+ if self.log_config.filename == "auto":
281
291
  import os
282
292
 
283
293
  log_folder = "logs"