pyqqq 0.12.179__tar.gz → 0.12.181__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.179 → pyqqq-0.12.181}/PKG-INFO +1 -1
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyproject.toml +1 -1
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/backtest/broker.py +1 -1
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/kis/simple.py +25 -6
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/realtime.py +28 -13
- {pyqqq-0.12.179 → pyqqq-0.12.181}/README.md +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/ai/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/ai/daily.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/ai/domestic.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/ai/market_schedule.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/ai/minute.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/backtest/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/backtest/environment.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/backtest/logger.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/backtest/positionprovider.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/backtest/strategy.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/backtest/utils.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/backtest/wallclock.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/domestic_stock.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/oauth.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/simple.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/tr_client.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/helper.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/kis/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/kis/domestic_stock.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/kis/oauth.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/kis/overseas_stock.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/kis/simple_overseas.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/kis/tr_client.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/multiprocess_tracker.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/brokerage/tracker.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/config.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/daily.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/domestic.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/index.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/minutes.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/overseas.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/ticks.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/data/us_stocks.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/datatypes.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/executors/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/executors/hook.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/__init__.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/api_client.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/array.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/casting.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/compute.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/copycat.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/daily_tickers.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/display.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/kvstore.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/limiter.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/local_cache.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/logger.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/market_schedule.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/mock_api.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/position_classifier.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/retry.py +0 -0
- {pyqqq-0.12.179 → pyqqq-0.12.181}/pyqqq/utils/singleton.py +0 -0
|
@@ -25,7 +25,7 @@ from pyqqq.data.us_stocks import get_ticker_info as get_us_ticker_info
|
|
|
25
25
|
from pyqqq.datatypes import *
|
|
26
26
|
from pyqqq.utils.casting import casting
|
|
27
27
|
from pyqqq.utils.market_schedule import get_last_trading_day, get_market_schedule, get_trading_day_with_offset
|
|
28
|
-
from pyqqq.utils.
|
|
28
|
+
from pyqqq.utils.position_classifier import PositionClassifier
|
|
29
29
|
|
|
30
30
|
MarketType = Literal["kr_stock", "us_stock"]
|
|
31
31
|
|
|
@@ -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,
|
|
@@ -201,24 +201,38 @@ class TickEventListener:
|
|
|
201
201
|
await asyncio.sleep(self.retry_cnt * 2)
|
|
202
202
|
await self.connect_ws()
|
|
203
203
|
|
|
204
|
-
def append_event(self, ticker, event_id, price, once, side, price_comparison, listen_callback):
|
|
204
|
+
def append_event(self, ticker, event_id, price, once, side, price_comparison, listen_callback, is_unified=False):
|
|
205
205
|
"""
|
|
206
206
|
사용자로부터 틱데이터 이벤트 추가 요청을 등록합니다. 즉시 처리되진 않습니다.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
ticker (str): 종목 코드
|
|
210
|
+
event_id (str): 이벤트 ID. 고유한 값이어야 합니다.
|
|
211
|
+
price (int): 가격
|
|
212
|
+
once (bool): 한번만 실행할지 여부
|
|
213
|
+
side (int | OrderSide): 매도/매수 구분 (0: all, 1: sell[cgubun: "-"], 2: buy[cgubun: "+"]) default: 0
|
|
214
|
+
price_comparison (str): 가격 비교 방식 ("<", "<=", "==", ">=", ">")
|
|
215
|
+
listen_callback (callable): 이벤트 발생시 호출할 콜백 함수
|
|
216
|
+
is_unified (bool): 통합 이벤트인지 여부. 기본값은 False입니다.
|
|
207
217
|
"""
|
|
208
|
-
self.events[event_id] = TickEvent(
|
|
218
|
+
self.events[event_id] = TickEvent(
|
|
219
|
+
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
|
|
220
|
+
)
|
|
209
221
|
self.pending_event_ids.append(event_id)
|
|
210
222
|
|
|
211
|
-
async def append_event_async(self, ticker, event_id, price, once, side, price_comparison, listen_callback):
|
|
223
|
+
async def append_event_async(self, ticker, event_id, price, once, side, price_comparison, listen_callback, is_unified=False):
|
|
212
224
|
"""
|
|
213
225
|
사용자로부터 틱데이터 이벤트 추가 요청을 등록합니다. 비동기로 가능한 한 즉시 처리됩니다.
|
|
214
226
|
"""
|
|
215
227
|
if self.ws_connected:
|
|
216
|
-
self.events[event_id] = TickEvent(
|
|
228
|
+
self.events[event_id] = TickEvent(
|
|
229
|
+
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
|
|
230
|
+
)
|
|
217
231
|
logger.info(f"{self.LOG_TAG}append_event_async {event_id}")
|
|
218
232
|
await self.send_subscribe(event_id)
|
|
219
233
|
else:
|
|
220
234
|
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)
|
|
235
|
+
self.append_event(ticker, event_id, price, once, side, price_comparison, listen_callback, is_unified)
|
|
222
236
|
|
|
223
237
|
async def close_event(self, event_id):
|
|
224
238
|
"""
|
|
@@ -340,7 +354,7 @@ class TickEvent:
|
|
|
340
354
|
LOG_TAG = "[TickEvent]"
|
|
341
355
|
CLOSE_TIME = dtm.time(18, 0)
|
|
342
356
|
|
|
343
|
-
def __init__(self, event_listener, ticker, client_id, event_id, price, once, side, price_comparison, listen_callback):
|
|
357
|
+
def __init__(self, event_listener, ticker, client_id, event_id, price, once, side, price_comparison, listen_callback, is_unified=False):
|
|
344
358
|
logger.info(f"{self.LOG_TAG} create {event_id}, {ticker}, {price}, {price_comparison}")
|
|
345
359
|
self.removed = False
|
|
346
360
|
self.event_listener = event_listener
|
|
@@ -352,20 +366,21 @@ class TickEvent:
|
|
|
352
366
|
self.side = side
|
|
353
367
|
self.price_comparison = price_comparison
|
|
354
368
|
self.listen_callback = listen_callback
|
|
369
|
+
self.is_unified = is_unified # 통합 이벤트인지 여부
|
|
355
370
|
|
|
356
371
|
self.date = dtm.datetime.now().strftime("%Y%m%d")
|
|
357
372
|
|
|
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
373
|
def get_subscribe_dump_data(self):
|
|
364
374
|
logger.info(f"{self.LOG_TAG} subscribe {self.event_id}, {self.ticker}, {self.price}, {self.price_comparison}, {self.side}")
|
|
365
|
-
|
|
375
|
+
|
|
376
|
+
ticker = "U" + self.ticker if self.is_unified else self.ticker
|
|
377
|
+
|
|
378
|
+
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
379
|
|
|
367
380
|
def get_unsubscribe_dump_data(self):
|
|
368
|
-
|
|
381
|
+
ticker = "U" + self.ticker if self.is_unified else self.ticker
|
|
382
|
+
|
|
383
|
+
return json.dumps({"type": "unsubscribe", "ticker": ticker, "client_id": self.client_id, "event_id": self.event_id})
|
|
369
384
|
|
|
370
385
|
async def handle_tick_data(self, data):
|
|
371
386
|
if self.listen_callback:
|
|
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
|
|
File without changes
|
|
File without changes
|