pyqqq 0.12.169__tar.gz → 0.12.170__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.169 → pyqqq-0.12.170}/PKG-INFO +1 -1
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyproject.toml +1 -1
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/backtest/broker.py +48 -10
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/backtest/environment.py +8 -3
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/kis/domestic_stock.py +1 -1
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/kis/simple.py +13 -3
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/market_schedule.py +1 -1
- {pyqqq-0.12.169 → pyqqq-0.12.170}/README.md +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/ai/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/ai/daily.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/ai/domestic.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/ai/market_schedule.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/ai/minute.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/backtest/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/backtest/logger.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/backtest/positionprovider.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/backtest/strategy.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/backtest/utils.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/backtest/wallclock.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/ebest/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/ebest/domestic_stock.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/ebest/oauth.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/ebest/simple.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/ebest/tr_client.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/helper.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/kis/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/kis/oauth.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/kis/overseas_stock.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/kis/simple_overseas.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/kis/tr_client.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/multiprocess_tracker.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/brokerage/tracker.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/config.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/daily.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/domestic.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/index.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/minutes.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/overseas.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/realtime.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/ticks.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/data/us_stocks.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/datatypes.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/executors/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/executors/hook.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/__init__.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/api_client.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/array.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/casting.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/compute.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/copycat.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/daily_tickers.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/display.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/kvstore.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/limiter.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/local_cache.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/logger.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/mock_api.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/position_classifier.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/retry.py +0 -0
- {pyqqq-0.12.169 → pyqqq-0.12.170}/pyqqq/utils/singleton.py +0 -0
|
@@ -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,62 @@ 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
|
-
|
|
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
|
+
else:
|
|
273
|
+
if self.market_nxt_on:
|
|
274
|
+
price_data = self.data_api.get_price(code, data_exchange=DataExchange.NXT)
|
|
275
|
+
if price_data.get("current_price") == 0:
|
|
276
|
+
price_data = self.data_api.get_price(code, data_exchange=DataExchange.KRX)
|
|
277
|
+
else:
|
|
278
|
+
price_data = self.data_api.get_price(code, data_exchange=DataExchange.KRX)
|
|
279
|
+
|
|
269
280
|
result = price_data.get("current_price")
|
|
270
281
|
if result is None:
|
|
271
282
|
raise ValueError(f"Current price not found: {code}")
|
|
272
283
|
return Decimal(str(result))
|
|
273
284
|
|
|
274
|
-
def get_minute_price(self, code) -> pd.DataFrame:
|
|
275
|
-
|
|
285
|
+
def get_minute_price(self, code: str, data_exchange: Optional[DataExchange] = None) -> pd.DataFrame:
|
|
286
|
+
if data_exchange:
|
|
287
|
+
result = self.data_api.get_today_minute_data(code, data_exchange=data_exchange)
|
|
288
|
+
else:
|
|
289
|
+
if self.market_nxt_on:
|
|
290
|
+
result = self.data_api.get_today_minute_data(code, data_exchange=DataExchange.NXT)
|
|
291
|
+
if result.iloc[-1].close == 0:
|
|
292
|
+
result = self.data_api.get_today_minute_data(code, data_exchange=DataExchange.KRX)
|
|
293
|
+
else:
|
|
294
|
+
result = self.data_api.get_today_minute_data(code, data_exchange=DataExchange.KRX)
|
|
295
|
+
return result
|
|
276
296
|
|
|
277
|
-
def get_daily_price(self, code: str, from_date: dtm.date, to_date: dtm.date) -> pd.DataFrame:
|
|
278
|
-
|
|
297
|
+
def get_daily_price(self, code: str, from_date: dtm.date, to_date: dtm.date, data_exchange: Optional[DataExchange] = None) -> pd.DataFrame:
|
|
298
|
+
if data_exchange:
|
|
299
|
+
result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=data_exchange)
|
|
300
|
+
else:
|
|
301
|
+
if self.market_nxt_on:
|
|
302
|
+
result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.NXT)
|
|
303
|
+
if result.empty:
|
|
304
|
+
result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.KRX)
|
|
305
|
+
else:
|
|
306
|
+
result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.KRX)
|
|
307
|
+
return result
|
|
279
308
|
|
|
280
|
-
def get_orderbook(self):
|
|
281
|
-
|
|
309
|
+
def get_orderbook(self, code: str, data_exchange: Optional[DataExchange] = None):
|
|
310
|
+
if data_exchange:
|
|
311
|
+
result = self.data_api.get_orderbook(code, data_exchange=data_exchange)
|
|
312
|
+
else:
|
|
313
|
+
if self.market_nxt_on:
|
|
314
|
+
result = self.data_api.get_orderbook(code, data_exchange=DataExchange.NXT)
|
|
315
|
+
if not result:
|
|
316
|
+
result = self.data_api.get_orderbook(code, data_exchange=DataExchange.KRX)
|
|
317
|
+
else:
|
|
318
|
+
result = self.data_api.get_orderbook(code, data_exchange=DataExchange.KRX)
|
|
319
|
+
return result
|
|
282
320
|
|
|
283
321
|
def get_pending_orders(self):
|
|
284
322
|
return self.trading_api.get_pending_orders()
|
|
@@ -842,8 +880,8 @@ class MockBroker(BaseBroker):
|
|
|
842
880
|
|
|
843
881
|
nxt_pre_open_time = dtm.datetime.combine(today, dtm.time(8, 0, 0))
|
|
844
882
|
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))
|
|
883
|
+
# nxt_main_open_time = dtm.datetime.combine(today, dtm.time(9, 0, 30))
|
|
884
|
+
# nxt_main_close_time = dtm.datetime.combine(today, dtm.time(15, 20, 0))
|
|
847
885
|
nxt_after_open_time = dtm.datetime.combine(today, dtm.time(15, 40, 0))
|
|
848
886
|
nxt_after_close_time = dtm.datetime.combine(today, dtm.time(20, 0, 0))
|
|
849
887
|
|
|
@@ -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
|
-
|
|
267
|
-
|
|
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
|
|
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
|