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):
|
pyqqq/brokerage/tracker.py
CHANGED
|
@@ -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
|
|
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.
|
|
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:
|
|
@@ -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=
|
|
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=
|
|
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.
|
|
62
|
-
pyqqq-0.12.
|
|
63
|
-
pyqqq-0.12.
|
|
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,,
|
|
File without changes
|