pyqqq 0.12.180__tar.gz → 0.12.182__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.
Potentially problematic release.
This version of pyqqq might be problematic. Click here for more details.
- {pyqqq-0.12.180 → pyqqq-0.12.182}/PKG-INFO +1 -1
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyproject.toml +1 -1
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/backtest/broker.py +1 -1
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/kis/simple.py +25 -6
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/minutes.py +16 -1
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/realtime.py +44 -16
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/market_schedule.py +5 -2
- {pyqqq-0.12.180 → pyqqq-0.12.182}/README.md +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/ai/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/ai/daily.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/ai/domestic.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/ai/market_schedule.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/ai/minute.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/backtest/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/backtest/environment.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/backtest/logger.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/backtest/positionprovider.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/backtest/strategy.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/backtest/utils.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/backtest/wallclock.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/ebest/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/ebest/domestic_stock.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/ebest/oauth.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/ebest/simple.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/ebest/tr_client.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/helper.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/kis/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/kis/domestic_stock.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/kis/oauth.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/kis/overseas_stock.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/kis/simple_overseas.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/kis/tr_client.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/multiprocess_tracker.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/brokerage/tracker.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/config.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/daily.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/domestic.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/index.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/overseas.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/ticks.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/data/us_stocks.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/datatypes.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/executors/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/executors/hook.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/__init__.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/api_client.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/array.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/casting.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/compute.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/copycat.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/daily_tickers.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/display.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/kvstore.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/limiter.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/local_cache.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/logger.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/mock_api.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/position_classifier.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/retry.py +0 -0
- {pyqqq-0.12.180 → pyqqq-0.12.182}/pyqqq/utils/singleton.py +0 -0
|
@@ -4,7 +4,7 @@ from pyqqq.brokerage.kis.oauth import KISAuth
|
|
|
4
4
|
from pyqqq.data.realtime import get_all_last_trades
|
|
5
5
|
from pyqqq.datatypes import *
|
|
6
6
|
from pyqqq.utils.logger import get_logger
|
|
7
|
-
from pyqqq.utils.market_schedule import get_market_schedule,
|
|
7
|
+
from pyqqq.utils.market_schedule import get_market_schedule, is_full_day_closed
|
|
8
8
|
from pyqqq.utils.mock_api import with_mock
|
|
9
9
|
from typing import AsyncGenerator, Dict, List, Optional
|
|
10
10
|
import asyncio
|
|
@@ -219,6 +219,7 @@ class KISSimpleDomesticStock:
|
|
|
219
219
|
Note:
|
|
220
220
|
- 두 거래소에서 공통으로 거래정지된 종목의 시가/고가/저가/종가는 모두 동일하며, 그 외 값은 모두 0 입니다.
|
|
221
221
|
- NXT 매매체결 종목이 아니거나, NXT 거래소에서 거래가 불가능한 종목을 DataExchange.NXT 또는 DataExchange.UN으로 조회하면, 모든 값은 0 입니다.
|
|
222
|
+
- 조회 기간 중 휴장일은 포함되지 않습니다.
|
|
222
223
|
|
|
223
224
|
Args:
|
|
224
225
|
asset_code(str): 종목코드
|
|
@@ -294,6 +295,7 @@ class KISSimpleDomesticStock:
|
|
|
294
295
|
- 시간외 종가, 시간외 단일가 데이터는 포함되어 있지 않습니다.
|
|
295
296
|
- 두 거래소에서 공통으로 거래정지된 종목의 시가/고가/저가/종가는 모두 동일하며, 그 외 값은 모두 0 입니다.
|
|
296
297
|
- NXT 매매체결 종목이 아니거나, NXT 거래소에서 거래가 불가능한 종목을 DataExchange.NXT 또는 DataExchange.UN으로 조회하면, 모든 값은 0 입니다.
|
|
298
|
+
- 휴장일에는 빈 DataFrame을 반환합니다.
|
|
297
299
|
|
|
298
300
|
Args:
|
|
299
301
|
asset_code(str): 종목코드
|
|
@@ -303,11 +305,32 @@ class KISSimpleDomesticStock:
|
|
|
303
305
|
pd.DataFrame: 분봉 데이터 (시간의 역순)
|
|
304
306
|
"""
|
|
305
307
|
|
|
308
|
+
def _create_minute_dataframe(data: list = None) -> pd.DataFrame:
|
|
309
|
+
"""
|
|
310
|
+
분봉 데이터를 위한 DataFrame을 생성합니다.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
data (list, optional): 분봉 데이터 리스트. None이면 빈 DataFrame을 반환합니다.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
pd.DataFrame: 분봉 데이터 DataFrame
|
|
317
|
+
"""
|
|
318
|
+
if data is None:
|
|
319
|
+
data = []
|
|
320
|
+
|
|
321
|
+
df = pd.DataFrame(data, columns=["time", "open", "high", "low", "close", "volume", "value", "cum_volume", "cum_value"])
|
|
322
|
+
df["time"] = pd.to_datetime(df["time"], format="%H:%M:%S")
|
|
323
|
+
df.set_index("time", inplace=True)
|
|
324
|
+
return df
|
|
325
|
+
|
|
306
326
|
request_datetime = dtm.datetime.now()
|
|
307
327
|
request_time = request_datetime.replace(second=0, microsecond=0)
|
|
308
328
|
result = []
|
|
309
329
|
schedule = get_market_schedule(dtm.date.today(), exchange="KRX" if data_exchange == DataExchange.KRX else "NXT")
|
|
310
330
|
|
|
331
|
+
if schedule.full_day_closed:
|
|
332
|
+
return _create_minute_dataframe()
|
|
333
|
+
|
|
311
334
|
# 만약 현재 시간이 거래소 마감 시간을 지났다면, 마감 시간으로 설정
|
|
312
335
|
if request_time.time() > schedule.close_time:
|
|
313
336
|
request_time = request_datetime.replace(hour=schedule.close_time.hour, minute=schedule.close_time.minute)
|
|
@@ -365,11 +388,7 @@ class KISSimpleDomesticStock:
|
|
|
365
388
|
prev_cum_value = curr["cum_value"]
|
|
366
389
|
prev_cum_volume = curr["cum_volume"]
|
|
367
390
|
|
|
368
|
-
|
|
369
|
-
df["time"] = pd.to_datetime(df["time"], format="%H:%M:%S")
|
|
370
|
-
df.set_index("time", inplace=True)
|
|
371
|
-
|
|
372
|
-
return df
|
|
391
|
+
return _create_minute_dataframe(result)
|
|
373
392
|
|
|
374
393
|
def get_price(
|
|
375
394
|
self,
|
|
@@ -29,6 +29,9 @@ def get_all_minute_data(
|
|
|
29
29
|
|
|
30
30
|
NXT 거래소 데이터의 조회 가능 시작일은 데이터 소스에 따라 다릅니다. kis는 2025년 3월 4일부터, ebest는 2025년 5월 12일부터 데이터를 조회할 수 있습니다.
|
|
31
31
|
|
|
32
|
+
Note:
|
|
33
|
+
- KRX, NXT 거래소의 분봉 데이터를 조회할 수 있습니다. UN 거래소는 지원되지 않습니다.
|
|
34
|
+
|
|
32
35
|
Args:
|
|
33
36
|
time (datetime.datetime): 조회할 시간
|
|
34
37
|
source (str): 데이터를 검색할 API. 'ebest' 또는 'kis'를 지정할 수 있습니다. 기본값은 'kis'입니다.
|
|
@@ -63,6 +66,9 @@ def get_all_minute_data(
|
|
|
63
66
|
- mdvolumetm (int): 시간별매도체결량
|
|
64
67
|
- msvolumetm (int): 시간별매수체결량
|
|
65
68
|
|
|
69
|
+
Raises:
|
|
70
|
+
ValueError: 지원하지 않는 거래소 코드가 전달된 경우.
|
|
71
|
+
|
|
66
72
|
Examples:
|
|
67
73
|
>>> df = get_all_minute_data(datetime.datetime(2024, 5, 2, 15, 30))
|
|
68
74
|
>>> print(df)
|
|
@@ -77,7 +83,10 @@ def get_all_minute_data(
|
|
|
77
83
|
[5 rows x 23 columns]
|
|
78
84
|
"""
|
|
79
85
|
tz = pytz.timezone("Asia/Seoul")
|
|
86
|
+
|
|
80
87
|
exchange = DataExchange.validate(exchange)
|
|
88
|
+
if exchange == DataExchange.UN:
|
|
89
|
+
raise ValueError("UN 거래소는 지원되지 않습니다.")
|
|
81
90
|
|
|
82
91
|
url = f"{c.PYQQQ_API_URL}/domestic-stock/ohlcv/minutes/all/{time.date()}/{time.strftime('%H%M')}"
|
|
83
92
|
params = {
|
|
@@ -142,6 +151,9 @@ def get_all_day_data(
|
|
|
142
151
|
|
|
143
152
|
NXT 거래소 데이터의 조회 가능 시작일은 데이터 소스에 따라 다릅니다. kis는 2025년 3월 4일부터, ebest는 2025년 5월 12일부터 데이터를 조회할 수 있습니다.
|
|
144
153
|
|
|
154
|
+
Note:
|
|
155
|
+
- KRX, NXT 거래소의 분봉 데이터를 조회할 수 있습니다. UN 거래소는 지원되지 않습니다.
|
|
156
|
+
|
|
145
157
|
Args:
|
|
146
158
|
date (datetime.date): 데이터를 검색할 날짜.
|
|
147
159
|
codes (list[str]): 조회할 주식 코드들의 리스트. 최대 20개까지 지정할 수 있습니다.
|
|
@@ -195,6 +207,7 @@ def get_all_day_data(
|
|
|
195
207
|
2024-04-26 09:04:00 77400 77600 77400 77500 3268502 2 1200 1.57
|
|
196
208
|
"""
|
|
197
209
|
assert isinstance(date, datetime.date), "date must be a datetime.date object"
|
|
210
|
+
assert type(date) is datetime.date, "date must be a datetime.date object"
|
|
198
211
|
assert isinstance(codes, list) or isinstance(codes, str), "codes must be a list of strings or single code"
|
|
199
212
|
|
|
200
213
|
if isinstance(codes, list):
|
|
@@ -210,7 +223,9 @@ def get_all_day_data(
|
|
|
210
223
|
target_codes = codes if isinstance(codes, list) else [codes]
|
|
211
224
|
|
|
212
225
|
exchange = DataExchange.validate(exchange)
|
|
213
|
-
if exchange == DataExchange.
|
|
226
|
+
if exchange == DataExchange.UN:
|
|
227
|
+
raise ValueError("UN 거래소는 지원되지 않습니다.")
|
|
228
|
+
elif exchange == DataExchange.NXT or source == "kis":
|
|
214
229
|
url = f"{c.PYQQQ_API_URL}/domestic-stock/ohlcv/minutes/{date}"
|
|
215
230
|
else:
|
|
216
231
|
url = f"{c.PYQQQ_API_URL}/domestic-stock/ohlcv/half-minutes/{date}"
|
|
@@ -3,11 +3,12 @@ import datetime as dtm
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
-
from typing import List
|
|
6
|
+
from typing import List, Union
|
|
7
7
|
|
|
8
8
|
import websockets
|
|
9
9
|
|
|
10
10
|
import pyqqq.config as c
|
|
11
|
+
from pyqqq.datatypes import DataExchange
|
|
11
12
|
from pyqqq.utils.api_client import raise_for_status, send_request
|
|
12
13
|
from pyqqq.utils.logger import get_logger
|
|
13
14
|
from pyqqq.utils.singleton import singleton
|
|
@@ -15,10 +16,16 @@ from pyqqq.utils.singleton import singleton
|
|
|
15
16
|
logger = get_logger(__name__)
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def get_all_last_trades(
|
|
19
|
+
def get_all_last_trades(
|
|
20
|
+
codes: List[str] = None,
|
|
21
|
+
exchange: Union[str, DataExchange] = "KRX",
|
|
22
|
+
):
|
|
19
23
|
"""
|
|
20
24
|
모든 종목의 최근 체결 정보를 반환합니다.
|
|
21
25
|
|
|
26
|
+
Note:
|
|
27
|
+
- KRX, UN 거래소의 최근 체결 정보를 조회할 수 있습니다. NXT 거래소는 지원되지 않습니다.
|
|
28
|
+
|
|
22
29
|
Args:
|
|
23
30
|
codes (List[str], optional): 종목 코드 리스트. 모든 종목의 체결 정보를 반환하려면 None을 전달합니다.
|
|
24
31
|
exchange (str): 거래소. 기본값은 "KRX"입니다. 지원하는 거래소는 "KRX", "UN" 입니다.
|
|
@@ -50,9 +57,15 @@ def get_all_last_trades(codes: List[str] = None, exchange: str = "KRX"):
|
|
|
50
57
|
- exchname (str): 거래소명
|
|
51
58
|
- date (str): 체결일자
|
|
52
59
|
- update_time (str): 업데이트 시간
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: 지원하지 않는 거래소 코드가 전달된 경우.
|
|
53
63
|
"""
|
|
64
|
+
exchange = DataExchange.validate(exchange)
|
|
65
|
+
if exchange == DataExchange.NXT:
|
|
66
|
+
raise ValueError("NXT 거래소는 지원되지 않습니다.")
|
|
54
67
|
|
|
55
|
-
params = {"exchange": exchange}
|
|
68
|
+
params = {"exchange": exchange.value}
|
|
56
69
|
if codes:
|
|
57
70
|
params["codes"] = ",".join(codes) if isinstance(codes, list) else codes
|
|
58
71
|
|
|
@@ -201,24 +214,38 @@ class TickEventListener:
|
|
|
201
214
|
await asyncio.sleep(self.retry_cnt * 2)
|
|
202
215
|
await self.connect_ws()
|
|
203
216
|
|
|
204
|
-
def append_event(self, ticker, event_id, price, once, side, price_comparison, listen_callback):
|
|
217
|
+
def append_event(self, ticker, event_id, price, once, side, price_comparison, listen_callback, is_unified=False):
|
|
205
218
|
"""
|
|
206
219
|
사용자로부터 틱데이터 이벤트 추가 요청을 등록합니다. 즉시 처리되진 않습니다.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
ticker (str): 종목 코드
|
|
223
|
+
event_id (str): 이벤트 ID. 고유한 값이어야 합니다.
|
|
224
|
+
price (int): 가격
|
|
225
|
+
once (bool): 한번만 실행할지 여부
|
|
226
|
+
side (int | OrderSide): 매도/매수 구분 (0: all, 1: sell[cgubun: "-"], 2: buy[cgubun: "+"]) default: 0
|
|
227
|
+
price_comparison (str): 가격 비교 방식 ("<", "<=", "==", ">=", ">")
|
|
228
|
+
listen_callback (callable): 이벤트 발생시 호출할 콜백 함수
|
|
229
|
+
is_unified (bool): 통합 이벤트인지 여부. 기본값은 False입니다.
|
|
207
230
|
"""
|
|
208
|
-
self.events[event_id] = TickEvent(
|
|
231
|
+
self.events[event_id] = TickEvent(
|
|
232
|
+
ticker=ticker, event_listener=self, client_id=self.client_id, event_id=event_id, price=price, once=once, side=side, price_comparison=price_comparison, listen_callback=listen_callback, is_unified=is_unified
|
|
233
|
+
)
|
|
209
234
|
self.pending_event_ids.append(event_id)
|
|
210
235
|
|
|
211
|
-
async def append_event_async(self, ticker, event_id, price, once, side, price_comparison, listen_callback):
|
|
236
|
+
async def append_event_async(self, ticker, event_id, price, once, side, price_comparison, listen_callback, is_unified=False):
|
|
212
237
|
"""
|
|
213
238
|
사용자로부터 틱데이터 이벤트 추가 요청을 등록합니다. 비동기로 가능한 한 즉시 처리됩니다.
|
|
214
239
|
"""
|
|
215
240
|
if self.ws_connected:
|
|
216
|
-
self.events[event_id] = TickEvent(
|
|
241
|
+
self.events[event_id] = TickEvent(
|
|
242
|
+
ticker=ticker, event_listener=self, client_id=self.client_id, event_id=event_id, price=price, once=once, side=side, price_comparison=price_comparison, listen_callback=listen_callback, is_unified=is_unified
|
|
243
|
+
)
|
|
217
244
|
logger.info(f"{self.LOG_TAG}append_event_async {event_id}")
|
|
218
245
|
await self.send_subscribe(event_id)
|
|
219
246
|
else:
|
|
220
247
|
logger.info(f"{self.LOG_TAG}append_event_async {event_id} failed")
|
|
221
|
-
self.append_event(ticker, event_id, price, once, side, price_comparison, listen_callback)
|
|
248
|
+
self.append_event(ticker, event_id, price, once, side, price_comparison, listen_callback, is_unified)
|
|
222
249
|
|
|
223
250
|
async def close_event(self, event_id):
|
|
224
251
|
"""
|
|
@@ -340,7 +367,7 @@ class TickEvent:
|
|
|
340
367
|
LOG_TAG = "[TickEvent]"
|
|
341
368
|
CLOSE_TIME = dtm.time(18, 0)
|
|
342
369
|
|
|
343
|
-
def __init__(self, event_listener, ticker, client_id, event_id, price, once, side, price_comparison, listen_callback):
|
|
370
|
+
def __init__(self, event_listener, ticker, client_id, event_id, price, once, side, price_comparison, listen_callback, is_unified=False):
|
|
344
371
|
logger.info(f"{self.LOG_TAG} create {event_id}, {ticker}, {price}, {price_comparison}")
|
|
345
372
|
self.removed = False
|
|
346
373
|
self.event_listener = event_listener
|
|
@@ -352,20 +379,21 @@ class TickEvent:
|
|
|
352
379
|
self.side = side
|
|
353
380
|
self.price_comparison = price_comparison
|
|
354
381
|
self.listen_callback = listen_callback
|
|
382
|
+
self.is_unified = is_unified # 통합 이벤트인지 여부
|
|
355
383
|
|
|
356
384
|
self.date = dtm.datetime.now().strftime("%Y%m%d")
|
|
357
385
|
|
|
358
|
-
async def listen_tick_event(self, ws):
|
|
359
|
-
logger.info(f"{self.LOG_TAG} listen {self.event_id}")
|
|
360
|
-
self.removed = False
|
|
361
|
-
await ws.send(json.dumps({"type": "subscribe", "ticker": self.ticker, "client_id": self.client_id, "event_id": self.event_id, "price": self.price, "once": self.once, "side": self.side, "price_comparison": self.price_comparison}))
|
|
362
|
-
|
|
363
386
|
def get_subscribe_dump_data(self):
|
|
364
387
|
logger.info(f"{self.LOG_TAG} subscribe {self.event_id}, {self.ticker}, {self.price}, {self.price_comparison}, {self.side}")
|
|
365
|
-
|
|
388
|
+
|
|
389
|
+
ticker = "U" + self.ticker if self.is_unified else self.ticker
|
|
390
|
+
|
|
391
|
+
return json.dumps({"type": "subscribe", "ticker": ticker, "client_id": self.client_id, "event_id": self.event_id, "price": self.price, "once": self.once, "side": self.side, "price_comparison": self.price_comparison})
|
|
366
392
|
|
|
367
393
|
def get_unsubscribe_dump_data(self):
|
|
368
|
-
|
|
394
|
+
ticker = "U" + self.ticker if self.is_unified else self.ticker
|
|
395
|
+
|
|
396
|
+
return json.dumps({"type": "unsubscribe", "ticker": ticker, "client_id": self.client_id, "event_id": self.event_id})
|
|
369
397
|
|
|
370
398
|
async def handle_tick_data(self, data):
|
|
371
399
|
if self.listen_callback:
|
|
@@ -137,6 +137,9 @@ def is_trading_time(
|
|
|
137
137
|
Returns:
|
|
138
138
|
bool: 거래 시간 여부
|
|
139
139
|
"""
|
|
140
|
+
if now is None:
|
|
141
|
+
now = datetime.datetime.now()
|
|
142
|
+
|
|
140
143
|
exchange = _validate_exchange(exchange)
|
|
141
144
|
if exchange == Exchange.NXT and now.date() < datetime.date(2025, 3, 4):
|
|
142
145
|
raise ValueError("NXT 거래소는 2025년 3월 4일 부터 운영되었습니다. 이전 날짜는 지원하지 않습니다.")
|
|
@@ -170,7 +173,7 @@ def get_market_schedule(
|
|
|
170
173
|
return _get_krx_schedule(date)
|
|
171
174
|
|
|
172
175
|
|
|
173
|
-
@ttl_cache(maxsize=
|
|
176
|
+
@ttl_cache(maxsize=10, ttl=3600)
|
|
174
177
|
def _get_nyse_schedule(date: datetime.date) -> MarketSchedule:
|
|
175
178
|
"""NYSE 시장 스케줄을 조회합니다."""
|
|
176
179
|
cal = mcal.get_calendar("NYSE")
|
|
@@ -338,7 +341,7 @@ def get_trading_day_with_offset(from_date: Optional[datetime.date] = None, offse
|
|
|
338
341
|
return offset_date
|
|
339
342
|
|
|
340
343
|
|
|
341
|
-
@ttl_cache(maxsize=
|
|
344
|
+
@ttl_cache(maxsize=10, ttl=3600)
|
|
342
345
|
def _fetch_market_scheldue(date: datetime.date, exchange: str) -> requests.Response | None:
|
|
343
346
|
url = f"{c.PYQQQ_API_URL}/domestic-stock/market-schedules/{exchange}"
|
|
344
347
|
params = {"date": date}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|