pyqqq 0.12.180__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.

Files changed (63) hide show
  1. {pyqqq-0.12.180 → pyqqq-0.12.181}/PKG-INFO +1 -1
  2. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyproject.toml +1 -1
  3. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/kis/simple.py +25 -6
  4. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/realtime.py +28 -13
  5. {pyqqq-0.12.180 → pyqqq-0.12.181}/README.md +0 -0
  6. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/__init__.py +0 -0
  7. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/ai/__init__.py +0 -0
  8. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/ai/daily.py +0 -0
  9. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/ai/domestic.py +0 -0
  10. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/ai/market_schedule.py +0 -0
  11. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/ai/minute.py +0 -0
  12. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/backtest/__init__.py +0 -0
  13. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/backtest/broker.py +0 -0
  14. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/backtest/environment.py +0 -0
  15. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/backtest/logger.py +0 -0
  16. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/backtest/positionprovider.py +0 -0
  17. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/backtest/strategy.py +0 -0
  18. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/backtest/utils.py +0 -0
  19. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/backtest/wallclock.py +0 -0
  20. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/__init__.py +0 -0
  21. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/__init__.py +0 -0
  22. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/domestic_stock.py +0 -0
  23. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/oauth.py +0 -0
  24. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/simple.py +0 -0
  25. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/ebest/tr_client.py +0 -0
  26. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/helper.py +0 -0
  27. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/kis/__init__.py +0 -0
  28. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/kis/domestic_stock.py +0 -0
  29. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/kis/oauth.py +0 -0
  30. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/kis/overseas_stock.py +0 -0
  31. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/kis/simple_overseas.py +0 -0
  32. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/kis/tr_client.py +0 -0
  33. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/multiprocess_tracker.py +0 -0
  34. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/brokerage/tracker.py +0 -0
  35. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/config.py +0 -0
  36. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/__init__.py +0 -0
  37. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/daily.py +0 -0
  38. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/domestic.py +0 -0
  39. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/index.py +0 -0
  40. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/minutes.py +0 -0
  41. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/overseas.py +0 -0
  42. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/ticks.py +0 -0
  43. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/data/us_stocks.py +0 -0
  44. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/datatypes.py +0 -0
  45. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/executors/__init__.py +0 -0
  46. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/executors/hook.py +0 -0
  47. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/__init__.py +0 -0
  48. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/api_client.py +0 -0
  49. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/array.py +0 -0
  50. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/casting.py +0 -0
  51. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/compute.py +0 -0
  52. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/copycat.py +0 -0
  53. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/daily_tickers.py +0 -0
  54. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/display.py +0 -0
  55. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/kvstore.py +0 -0
  56. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/limiter.py +0 -0
  57. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/local_cache.py +0 -0
  58. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/logger.py +0 -0
  59. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/market_schedule.py +0 -0
  60. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/mock_api.py +0 -0
  61. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/position_classifier.py +0 -0
  62. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/retry.py +0 -0
  63. {pyqqq-0.12.180 → pyqqq-0.12.181}/pyqqq/utils/singleton.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyqqq
3
- Version: 0.12.180
3
+ Version: 0.12.181
4
4
  Summary: Package for quantitative strategy development on the PyQQQ platform
5
5
  License: MIT
6
6
  Author: PyQQQ team
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyqqq"
3
- version = "0.12.180"
3
+ version = "0.12.181"
4
4
  description = "Package for quantitative strategy development on the PyQQQ platform"
5
5
  authors = ["PyQQQ team <pyqqq.cs@gmail.com>"]
6
6
  readme = "README.md"
@@ -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, get_last_trading_day, is_full_day_closed
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
- df = pd.DataFrame(result, columns=["time", "open", "high", "low", "close", "volume", "value", "cum_volume", "cum_value"])
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(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)
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(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)
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
- return 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})
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
- return json.dumps({"type": "unsubscribe", "ticker": self.ticker, "client_id": self.client_id, "event_id": self.event_id})
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