pyqqq 0.12.167__py3-none-any.whl → 0.12.168__py3-none-any.whl

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.
pyqqq/backtest/broker.py CHANGED
@@ -291,7 +291,7 @@ class TradingBroker(BaseBroker):
291
291
 
292
292
  def create_order(self, asset_code: str, side: OrderSide, quantity: int, order_type: OrderType = OrderType.MARKET, price: int | Decimal = 0, exchange: OrderExchange = OrderExchange.KRX) -> str:
293
293
  self.logger.debug(f"create_order: {asset_code} {side} {quantity} {order_type} {price} {exchange}")
294
- return self.trading_api.create_order(asset_code, side, quantity, order_type, price, exchange)
294
+ return self.trading_api.create_order(asset_code, side, quantity, order_type, price, exchange=exchange)
295
295
 
296
296
  def update_order(self, org_order_no: str, order_type: OrderType, price: int | Decimal, quantity: int = 0, exchange: OrderExchange = OrderExchange.KRX):
297
297
  if isinstance(self.trading_api, KISSimpleOverseasStock):
@@ -305,7 +305,7 @@ class TradingBroker(BaseBroker):
305
305
  raise ValueError(f"order not found: {org_order_no}")
306
306
  else:
307
307
  self.logger.debug(f"update_order: {org_order_no} {order_type} {price} {quantity}")
308
- return self.trading_api.update_order(org_order_no, order_type, price, quantity, exchange)
308
+ return self.trading_api.update_order(org_order_no, order_type, price, quantity, exchange=exchange)
309
309
 
310
310
  def cancel_order(self, order_no: str, quantity: int = 0):
311
311
  if isinstance(self.trading_api, KISSimpleOverseasStock):
@@ -8,6 +8,7 @@ from pyqqq.utils.api_client import raise_for_status, send_request
8
8
  from pyqqq.utils.array import find
9
9
  from pyqqq.utils.logger import get_logger
10
10
  from pyqqq.utils.market_schedule import get_market_schedule, get_last_trading_day
11
+ from pyqqq.utils.singleton import singleton
11
12
  from typing import Dict, Optional, List
12
13
  import asyncio
13
14
  import os
@@ -15,6 +16,111 @@ import pyqqq.config as c
15
16
  import datetime as dtm
16
17
 
17
18
 
19
+ @singleton
20
+ class TrackerSocket:
21
+ """
22
+ 거래 내역 추적을 위한 WebSocket 소켓 클래스입니다.
23
+
24
+ Args:
25
+ simple_api (EBestSimpleDomesticStock | KISSimpleDomesticStock): 간편 거래 API 객체
26
+ """
27
+
28
+ def __init__(self, simple_api: EBestSimpleDomesticStock | KISSimpleDomesticStock):
29
+ self.simple_api = simple_api
30
+ self.task: asyncio.Task = None
31
+ self.stop_event = asyncio.Event()
32
+ self.logger = get_logger(__name__ + ".TrackerSocket")
33
+ self.trading_tracker_counter = 1
34
+ self.event_callbacks: Dict[callable] = {}
35
+
36
+ async def start(self):
37
+ """
38
+ TradingSocket에서 거래내역 추적을 시작합니다.
39
+ """
40
+ if self.task is not None and not self.task.done():
41
+ self.logger.info("TrackerSocket already started!")
42
+ return
43
+
44
+ self.task = asyncio.create_task(self._listen_order_event())
45
+ self.logger.info("TrackerSocket started!")
46
+
47
+ async def _listen_order_event(self):
48
+ """
49
+ 주문 이벤트를 수신하고 처리하는 비동기 메서드입니다.
50
+ """
51
+ self.logger.info("Listening for order events...")
52
+ try:
53
+ async for event in self.simple_api.listen_order_event(self.stop_event):
54
+ self._relay_order_event(event)
55
+ except asyncio.CancelledError:
56
+ self.logger.info("Order event listening cancelled.")
57
+ except Exception as e:
58
+ self.logger.exception(f"Error while listening to order events: {e}")
59
+
60
+ def _relay_order_event(self, event: OrderEvent):
61
+ """
62
+ 주문 이벤트를 등록된 콜백 함수로 전달합니다.
63
+
64
+ Args:
65
+ event (OrderEvent): 주문 이벤트 객체
66
+ """
67
+ self.logger.debug(f"Relay order event: {event}")
68
+
69
+ for callback in self.event_callbacks.values():
70
+ try:
71
+ callback(event)
72
+ except Exception as e:
73
+ self.logger.exception(f"Error in callback {callback}: {e}")
74
+
75
+ def add_tracker(self, callback: callable):
76
+ """
77
+ TradingTracker를 추가합니다.
78
+
79
+ Args:
80
+ callback (callable): 거래 내역 추적 이벤트를 처리할 콜백 함수
81
+ """
82
+ ret = self.trading_tracker_counter
83
+ self.event_callbacks[self.trading_tracker_counter] = callback
84
+ self.trading_tracker_counter += 1
85
+
86
+ self.logger.info(f"Tracker added: {ret}")
87
+ return ret
88
+
89
+ async def remove_tracker(self, tracker_number: int):
90
+ """
91
+ TradingTracker를 제거합니다.
92
+ Args:
93
+ tracker_number (int): 제거할 트래커 번호
94
+ """
95
+ if tracker_number in self.event_callbacks:
96
+ del self.event_callbacks[tracker_number]
97
+ self.logger.info(f"Tracker removed: {tracker_number}")
98
+ else:
99
+ self.logger.warning(f"Tracker {tracker_number} not found!")
100
+
101
+ if len(self.event_callbacks) == 0:
102
+ self.logger.info("No more trackers, stopping TrackerSocket.")
103
+ await self.stop()
104
+
105
+ async def stop(self):
106
+ """
107
+ 거래 내역 추적을 중지합니다.
108
+ """
109
+ if self.task is None or self.task.done():
110
+ self.logger.info("TrackerSocket already stopped!")
111
+ return
112
+
113
+ self.stop_event.set()
114
+ self.task.cancel()
115
+ try:
116
+ await self.task
117
+ except asyncio.CancelledError:
118
+ pass
119
+ self.logger.info("TrackerSocket stopped!")
120
+
121
+ self.task = asyncio.create_task(self._monitor_schedule())
122
+
123
+
18
124
  class TradingTracker:
19
125
  """
20
126
  거래 내역 추적을 위한 클래스입니다
@@ -69,7 +175,6 @@ class TradingTracker:
69
175
  """ 백그라운드로 실행되는 거래 이벤트 모니터링 Task """
70
176
 
71
177
  self.simple_api = simple_api
72
- self.stop_event = asyncio.Event()
73
178
  self.account_no = None
74
179
  self.fee_rate = fee_rate # 증권사 수수료율
75
180
  self.tax_rate = Decimal("0.0018") # KOSPI, KOSDAQ 매도시 거래세율 0.18%
@@ -77,6 +182,8 @@ class TradingTracker:
77
182
  self.ticker_date: dtm.datetime = None # 종목 정보 갱신 시간
78
183
  self.save_trading_history = False # 거래 내역 저장 여부
79
184
  self.last_t_day_tickers: Dict[str, Dict] = {}
185
+ self.tracker_socket = TrackerSocket(simple_api)
186
+ self.tracker_number = None
80
187
 
81
188
  self.started = False
82
189
  self.callback_id = 0
@@ -94,7 +201,7 @@ class TradingTracker:
94
201
  elif isinstance(self.simple_api, KISSimpleDomesticStock):
95
202
  self.account_no = self.simple_api.account_no + self.simple_api.account_product_code
96
203
 
97
- self.logger.info(f"Trading tarcker started! Account No: {self.account_no} / save history: {self.save_trading_history}")
204
+ self.logger.info(f"Trading tracker started! Account No: {self.account_no} / save history: {self.save_trading_history}")
98
205
 
99
206
  self._fetch_tickers()
100
207
  self._sync_positions_and_pending_orders()
@@ -104,8 +211,10 @@ class TradingTracker:
104
211
  for o in self.pending_orders:
105
212
  self.logger.info(f"- {o.order_no}({o.org_order_no})\t{o.side}\t{o.asset_code}\t{o.filled_quantity}/{o.quantity}\t{o.is_pending}")
106
213
 
214
+ self.tracker_number = self.tracker_socket.add_tracker(self._handle_order_event)
215
+
107
216
  self.tasks = [
108
- asyncio.create_task(self._monitor_trading()),
217
+ asyncio.create_task(self.tracker_socket.start()),
109
218
  asyncio.create_task(self._monitor_schedule()),
110
219
  ]
111
220
  self.started = True
@@ -142,25 +251,15 @@ class TradingTracker:
142
251
  """
143
252
  거래 내역 추적을 중지합니다
144
253
  """
145
- self.stop_event.set()
146
254
  for t in self.tasks:
147
255
  t.cancel()
148
-
149
256
  await asyncio.gather(*self.tasks)
257
+ await self.tracker_socket.remove_tracker(self.tracker_number)
150
258
  self.started = False
151
259
 
152
- async def _monitor_trading(self):
153
- try:
154
- async for event in self.simple_api.listen_order_event(self.stop_event):
155
- self._handle_order_event(event)
156
- except asyncio.CancelledError:
157
- return
158
- except Exception as e:
159
- self.logger.exception(f"Error on handling order event: {e}")
160
-
161
260
  async def _monitor_schedule(self):
162
261
  """거래 시간대별 작업을 위한 스케줄을 모니터링합니다"""
163
- while not self.stop_event.is_set():
262
+ while not self.tracker_socket.stop_event.is_set():
164
263
  market_schedule = get_market_schedule(dtm.date.today())
165
264
 
166
265
  if not market_schedule.full_day_closed:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyqqq
3
- Version: 0.12.167
3
+ Version: 0.12.168
4
4
  Summary: Package for quantitative strategy development on the PyQQQ platform
5
5
  License: MIT
6
6
  Author: PyQQQ team
@@ -5,7 +5,7 @@ pyqqq/ai/domestic.py,sha256=FiJNInRlhcnxG7Jxmz2hDvaLhS8_jn-JFpQMze8Ch9s,1888
5
5
  pyqqq/ai/market_schedule.py,sha256=8HiivwC-xI2EKr8lXS_g4mTj2LYpCQ2QfZsJmIq61O0,818
6
6
  pyqqq/ai/minute.py,sha256=C0sTVkBY4-Vuj8Q9VZ7d9kZYAv963FUX4k3vIvhetng,1754
7
7
  pyqqq/backtest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- pyqqq/backtest/broker.py,sha256=y4QRjxlHayvrojYjISamXBY9693sMxxEutN4QLG5ddA,58472
8
+ pyqqq/backtest/broker.py,sha256=9paaihryHhdtPawvmxvMcxHdIX-aXB3fOgDz2t90YgQ,58490
9
9
  pyqqq/backtest/environment.py,sha256=Vb9h-rh_gS--1Ku99tD36XtH0bVgYsPHqKxP1lT0XEQ,8890
10
10
  pyqqq/backtest/logger.py,sha256=BmoEMjUU76z8rZtMCYCwbspD3AVaHJrdbbT1EAFgrAE,3294
11
11
  pyqqq/backtest/positionprovider.py,sha256=wrR7Bntg28Q5_vGQV6XNzxe-SYoO9_GLcV9gDVEDAN4,4164
@@ -27,7 +27,7 @@ pyqqq/brokerage/kis/simple.py,sha256=qX0LpvrC8vsu2PIW0v9fc25hzNQxPjJ-OY53rZr8q2I
27
27
  pyqqq/brokerage/kis/simple_overseas.py,sha256=1DuQBuJosg0mJQV7Ey2N3UOY8F3uOhzPDay4ncothuc,50360
28
28
  pyqqq/brokerage/kis/tr_client.py,sha256=9fTok0d8FmfXw4YxZSdn6T8UTHIG2aN1yMSkiMJUB3c,5530
29
29
  pyqqq/brokerage/multiprocess_tracker.py,sha256=Xx0hSpRZYITBGWjxclOEtNZdHV5agX94s34q1A8EE-Y,7283
30
- pyqqq/brokerage/tracker.py,sha256=KpaxAQoWtFxMcljgTFqNgHVeR8gdDofqTQTl7FQqFmA,20499
30
+ pyqqq/brokerage/tracker.py,sha256=z_bZ3O-G0oaEE7OBJawd2GbQwvPh-lKOLdWXOV1m1mw,23981
31
31
  pyqqq/config.py,sha256=55Vqc_pGkdbrBdCV1aLgoH_n5IFxmMC59sbPHId3LoI,498
32
32
  pyqqq/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  pyqqq/data/daily.py,sha256=hLrVf5COqrZNXXuzp_CDDsBAHDl-I6-82ySkelkMQPU,7973
@@ -58,6 +58,6 @@ pyqqq/utils/mock_api.py,sha256=7EsaVQ9mOVZQAqtQW24isPnk9QTbJII7x3guhFyEMAE,10569
58
58
  pyqqq/utils/position_classifier.py,sha256=EaomByAWM2lVuYow5OFdJNrN64Fpukhj-lhFkjYpjeo,14908
59
59
  pyqqq/utils/retry.py,sha256=4mw9MQvgSBC8bTLvDauaCEI5N9tL8upHCk8rSfaVRG8,2066
60
60
  pyqqq/utils/singleton.py,sha256=m6NZ8fwVDpI6U-gUUihMPgVK_NkDh-Z1NSAtjisrpjY,810
61
- pyqqq-0.12.167.dist-info/METADATA,sha256=EzjcpodmI8pe1mQKvfEPediFokW0_LfEjThpTLRwxK4,1664
62
- pyqqq-0.12.167.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
63
- pyqqq-0.12.167.dist-info/RECORD,,
61
+ pyqqq-0.12.168.dist-info/METADATA,sha256=NAaKZOU8if4_y3Kv3mEQTl-HV1ZtnSlKB7Oq_jaev9Y,1664
62
+ pyqqq-0.12.168.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
63
+ pyqqq-0.12.168.dist-info/RECORD,,