pyqqq 0.12.169__tar.gz → 0.12.171__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.169 → pyqqq-0.12.171}/PKG-INFO +1 -1
  2. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyproject.toml +1 -1
  3. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/backtest/broker.py +61 -12
  4. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/backtest/environment.py +8 -3
  5. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/kis/domestic_stock.py +1 -1
  6. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/kis/simple.py +13 -3
  7. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/market_schedule.py +1 -1
  8. {pyqqq-0.12.169 → pyqqq-0.12.171}/README.md +0 -0
  9. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/__init__.py +0 -0
  10. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/ai/__init__.py +0 -0
  11. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/ai/daily.py +0 -0
  12. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/ai/domestic.py +0 -0
  13. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/ai/market_schedule.py +0 -0
  14. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/ai/minute.py +0 -0
  15. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/backtest/__init__.py +0 -0
  16. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/backtest/logger.py +0 -0
  17. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/backtest/positionprovider.py +0 -0
  18. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/backtest/strategy.py +0 -0
  19. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/backtest/utils.py +0 -0
  20. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/backtest/wallclock.py +0 -0
  21. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/__init__.py +0 -0
  22. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/ebest/__init__.py +0 -0
  23. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/ebest/domestic_stock.py +0 -0
  24. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/ebest/oauth.py +0 -0
  25. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/ebest/simple.py +0 -0
  26. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/ebest/tr_client.py +0 -0
  27. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/helper.py +0 -0
  28. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/kis/__init__.py +0 -0
  29. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/kis/oauth.py +0 -0
  30. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/kis/overseas_stock.py +0 -0
  31. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/kis/simple_overseas.py +0 -0
  32. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/kis/tr_client.py +0 -0
  33. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/multiprocess_tracker.py +0 -0
  34. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/brokerage/tracker.py +0 -0
  35. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/config.py +0 -0
  36. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/__init__.py +0 -0
  37. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/daily.py +0 -0
  38. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/domestic.py +0 -0
  39. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/index.py +0 -0
  40. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/minutes.py +0 -0
  41. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/overseas.py +0 -0
  42. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/realtime.py +0 -0
  43. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/ticks.py +0 -0
  44. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/data/us_stocks.py +0 -0
  45. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/datatypes.py +0 -0
  46. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/executors/__init__.py +0 -0
  47. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/executors/hook.py +0 -0
  48. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/__init__.py +0 -0
  49. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/api_client.py +0 -0
  50. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/array.py +0 -0
  51. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/casting.py +0 -0
  52. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/compute.py +0 -0
  53. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/copycat.py +0 -0
  54. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/daily_tickers.py +0 -0
  55. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/display.py +0 -0
  56. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/kvstore.py +0 -0
  57. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/limiter.py +0 -0
  58. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/local_cache.py +0 -0
  59. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/logger.py +0 -0
  60. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/mock_api.py +0 -0
  61. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/position_classifier.py +0 -0
  62. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/retry.py +0 -0
  63. {pyqqq-0.12.169 → pyqqq-0.12.171}/pyqqq/utils/singleton.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyqqq
3
- Version: 0.12.169
3
+ Version: 0.12.171
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.169"
3
+ version = "0.12.171"
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"
@@ -244,6 +244,7 @@ class TradingBroker(BaseBroker):
244
244
  data_api: Union[KISSimpleDomesticStock, KISSimpleOverseasStock],
245
245
  trading_api: Union[KISSimpleDomesticStock, KISSimpleOverseasStock],
246
246
  clock: WallClock,
247
+ market_nxt_on: bool = False,
247
248
  ):
248
249
  """TradingBroker 클래스의 초기화 메서드입니다.
249
250
 
@@ -260,25 +261,70 @@ class TradingBroker(BaseBroker):
260
261
  self.logger = get_logger("TradingBroker", clock)
261
262
  self.data_api = data_api
262
263
  self.trading_api = trading_api
264
+ self.market_nxt_on = market_nxt_on
263
265
 
264
266
  def get_account(self) -> dict:
265
267
  return self.trading_api.get_account()
266
268
 
267
- def get_price(self, code: str) -> Decimal:
268
- price_data = self.data_api.get_price(code)
269
+ def get_price(self, code: str, data_exchange: Optional[DataExchange] = None) -> Decimal:
270
+ if data_exchange:
271
+ price_data = self.data_api.get_price(code, data_exchange=data_exchange)
272
+ if data_exchange == DataExchange.NXT and price_data.get("open_price", 0) == 0: # current_price는 어제의 값을 반환하므로 open_price가 0인지로 NXT 시장 여부 확인
273
+ price_data = self.data_api.get_price(code, data_exchange=DataExchange.KRX)
274
+ else:
275
+ if self.market_nxt_on: # TODO: 정규장에서도 NXT 가격을 우선 조회하는 것이니 수정 필요
276
+ price_data = self.data_api.get_price(code, data_exchange=DataExchange.NXT)
277
+ if price_data.get("open_price", 0) == 0: # current_price는 어제의 값을 반환하므로 open_price가 0인지로 NXT 시장 여부 확인
278
+ price_data = self.data_api.get_price(code, data_exchange=DataExchange.KRX)
279
+ else:
280
+ price_data = self.data_api.get_price(code, data_exchange=DataExchange.KRX)
281
+
269
282
  result = price_data.get("current_price")
270
283
  if result is None:
271
284
  raise ValueError(f"Current price not found: {code}")
272
285
  return Decimal(str(result))
273
286
 
274
- def get_minute_price(self, code) -> pd.DataFrame:
275
- return self.data_api.get_today_minute_data(code)
276
-
277
- def get_daily_price(self, code: str, from_date: dtm.date, to_date: dtm.date) -> pd.DataFrame:
278
- return self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True)
279
-
280
- def get_orderbook(self):
281
- return self.data_api.get_orderbook()
287
+ def get_minute_price(self, code: str, data_exchange: Optional[DataExchange] = None) -> pd.DataFrame:
288
+ if data_exchange:
289
+ result = self.data_api.get_today_minute_data(code, data_exchange=data_exchange)
290
+ if data_exchange == DataExchange.NXT and (result.empty or result.iloc[-1].close == 0):
291
+ result = self.data_api.get_today_minute_data(code, data_exchange=DataExchange.KRX)
292
+ else:
293
+ if self.market_nxt_on:
294
+ result = self.data_api.get_today_minute_data(code, data_exchange=DataExchange.NXT)
295
+ if result.empty or result.iloc[-1].close == 0:
296
+ result = self.data_api.get_today_minute_data(code, data_exchange=DataExchange.KRX)
297
+ else:
298
+ result = self.data_api.get_today_minute_data(code, data_exchange=DataExchange.KRX)
299
+ return result
300
+
301
+ def get_daily_price(self, code: str, from_date: dtm.date, to_date: dtm.date, data_exchange: Optional[DataExchange] = None) -> pd.DataFrame:
302
+ if data_exchange:
303
+ result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=data_exchange)
304
+ if data_exchange == DataExchange.NXT and result.empty:
305
+ result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.KRX)
306
+ else:
307
+ if self.market_nxt_on:
308
+ result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.NXT)
309
+ if result.empty:
310
+ result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.KRX)
311
+ else:
312
+ result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.KRX)
313
+ return result
314
+
315
+ def get_orderbook(self, code: str, data_exchange: Optional[DataExchange] = None):
316
+ if data_exchange:
317
+ result = self.data_api.get_orderbook(code, data_exchange=data_exchange)
318
+ if data_exchange == DataExchange.NXT and not result:
319
+ result = self.data_api.get_orderbook(code, data_exchange=DataExchange.KRX)
320
+ else:
321
+ if self.market_nxt_on:
322
+ result = self.data_api.get_orderbook(code, data_exchange=DataExchange.NXT)
323
+ if not result:
324
+ result = self.data_api.get_orderbook(code, data_exchange=DataExchange.KRX)
325
+ else:
326
+ result = self.data_api.get_orderbook(code, data_exchange=DataExchange.KRX)
327
+ return result
282
328
 
283
329
  def get_pending_orders(self):
284
330
  return self.trading_api.get_pending_orders()
@@ -288,6 +334,9 @@ class TradingBroker(BaseBroker):
288
334
  return next((o for o in orders if o["order_no"] == order_no), None)
289
335
 
290
336
  def get_positions(self):
337
+ """
338
+ 포지션 조회 (단, NXT 프리마켓, 애프터마켓에서 현재 가격은 NXT 가격으로 조회되지 않음)
339
+ """
291
340
  return self.trading_api.get_positions()
292
341
 
293
342
  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:
@@ -842,8 +891,8 @@ class MockBroker(BaseBroker):
842
891
 
843
892
  nxt_pre_open_time = dtm.datetime.combine(today, dtm.time(8, 0, 0))
844
893
  nxt_pre_close_time = dtm.datetime.combine(today, dtm.time(8, 50, 0))
845
- nxt_main_open_time = dtm.datetime.combine(today, dtm.time(9, 0, 30))
846
- nxt_main_close_time = dtm.datetime.combine(today, dtm.time(15, 20, 0))
894
+ # nxt_main_open_time = dtm.datetime.combine(today, dtm.time(9, 0, 30))
895
+ # nxt_main_close_time = dtm.datetime.combine(today, dtm.time(15, 20, 0))
847
896
  nxt_after_open_time = dtm.datetime.combine(today, dtm.time(15, 40, 0))
848
897
  nxt_after_close_time = dtm.datetime.combine(today, dtm.time(20, 0, 0))
849
898
 
@@ -76,7 +76,9 @@ class BacktestEnvironment(TradingEnvironment):
76
76
  time_unit: str = "minutes",
77
77
  position_provider: BasePositionProvider = None,
78
78
  market: str = "kr_stock",
79
- current_data_handling: Literal["include", "virtual", "exclude"] = "virtual"
79
+ current_data_handling: Literal["include", "virtual", "exclude"] = "virtual",
80
+ local_cache_path: str = "./data",
81
+ market_nxt_on: bool = False,
80
82
  ):
81
83
  """BacktestEnvironment 클래스의 초기화 메서드입니다.
82
84
 
@@ -94,6 +96,8 @@ class BacktestEnvironment(TradingEnvironment):
94
96
  - "include": 현재(분)까지의 실제 데이터 반환
95
97
  - "virtual": 직전(1분전)까지의 데이터 + 현재(분)의 시가로 통일한 가상 데이터 반환 (기본값)
96
98
  - "exclude": 직전(1분전)까지의 데이터만 반환
99
+ local_cache_path (str, optional): 백테스트 사용 데이터 로컬 캐시 경로. Defaults to "./data".
100
+ market_nxt_on (bool, optional): NXT 시장 처리 여부. Defaults to False.
97
101
 
98
102
  Raises:
99
103
  AssertionError: start_time이 end_time보다 늦거나,
@@ -107,7 +111,7 @@ class BacktestEnvironment(TradingEnvironment):
107
111
  tzinfo = None if market == "kr_stock" else ZoneInfo("America/New_York")
108
112
 
109
113
  self.clock = WallClock(live_mode=False, start_time=start_time, end_time=end_time, tzinfo=tzinfo)
110
- self.broker = MockBroker(self.clock, position_provider, market, time_unit, current_data_handling)
114
+ self.broker = MockBroker(self.clock, position_provider, market, time_unit, current_data_handling, local_cache_path=local_cache_path, market_nxt_on=market_nxt_on)
111
115
 
112
116
 
113
117
  class KISDomesticEnvironment(TradingEnvironment):
@@ -138,7 +142,7 @@ class KISDomesticEnvironment(TradingEnvironment):
138
142
  ```
139
143
  """
140
144
 
141
- def __init__(self, paper_trading: bool = False):
145
+ def __init__(self, paper_trading: bool = False, market_nxt_on: bool = False):
142
146
  """KISDomesticEnvironment 클래스의 초기화 메서드입니다.
143
147
 
144
148
  Args:
@@ -155,6 +159,7 @@ class KISDomesticEnvironment(TradingEnvironment):
155
159
  data_api=conn.broker_simple,
156
160
  trading_api=conn.broker_simple if not paper_trading else conn.paper_broker_simple,
157
161
  clock=self.clock,
162
+ market_nxt_on=market_nxt_on,
158
163
  )
159
164
 
160
165
 
@@ -219,7 +219,7 @@ class KISDomesticStock:
219
219
  ]
220
220
 
221
221
  for k in date_keys:
222
- if k in output and len(output[k]) > 0:
222
+ if k in output and len(output[k]) > 0 and output[k] != '0': # NXT 종목이 아닌데 지정한 경우 output[k] 값이 0
223
223
  output[k] = dtm.datetime.strptime(output[k], "%Y%m%d").date()
224
224
 
225
225
  result = {"rt_cd": res_body["rt_cd"], "msg_cd": res_body["msg_cd"], "msg1": res_body["msg1"], "output": output}
@@ -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, get_last_trading_day
8
8
  from pyqqq.utils.mock_api import with_mock
9
9
  from typing import AsyncGenerator, Dict, List, Optional
10
10
  import asyncio
@@ -263,8 +263,9 @@ class KISSimpleDomesticStock:
263
263
  result.extend(chunk)
264
264
 
265
265
  df = pd.DataFrame(result)
266
- df["date"] = pd.to_datetime(df["date"])
267
- df.set_index("date", inplace=True)
266
+ if not df.empty:
267
+ df["date"] = pd.to_datetime(df["date"])
268
+ df.set_index("date", inplace=True)
268
269
 
269
270
  return df
270
271
 
@@ -288,6 +289,13 @@ class KISSimpleDomesticStock:
288
289
  request_time = request_datetime.replace(second=0, microsecond=0)
289
290
  result = []
290
291
  schedule = get_market_schedule(dtm.date.today())
292
+ if schedule.full_day_closed:
293
+ _last_day = get_last_trading_day()
294
+ schedule = get_market_schedule(_last_day)
295
+ if data_exchange == DataExchange.NXT:
296
+ request_time = dtm.datetime.combine(_last_day, dtm.time(20, 0, 0))
297
+ else:
298
+ request_time = dtm.datetime.combine(_last_day, schedule.close_time)
291
299
 
292
300
  while True:
293
301
  r = self.stock_api.inquire_time_itemchartprice(
@@ -1110,6 +1118,8 @@ class KISSimpleDomesticStock:
1110
1118
  r = self.stock_api.inquire_asking_price_exp_ccn(asset_code, self._get_data_exchange(data_exchange))
1111
1119
 
1112
1120
  o1 = r["output1"]
1121
+ if not o1:
1122
+ return {}
1113
1123
 
1114
1124
  result = {
1115
1125
  "total_bid_volume": o1["total_bidp_rsqn"],
@@ -228,7 +228,7 @@ def _get_nxt_schedule(date: datetime.date) -> MarketSchedule:
228
228
  return schedule
229
229
 
230
230
 
231
- def get_last_trading_day(date: datetime.date = None, exchange: Union[str, Exchange] = "KRX") -> datetime.date:
231
+ def get_last_trading_day(date: Optional[datetime.date] = None, exchange: Union[str, Exchange] = "KRX") -> datetime.date:
232
232
  """
233
233
  주어진 날짜의 이전 거래일을 반환합니다.
234
234
 
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