pyqqq 0.12.177__tar.gz → 0.12.179__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.177 → pyqqq-0.12.179}/PKG-INFO +1 -1
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyproject.toml +1 -1
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/backtest/broker.py +33 -1
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/backtest/environment.py +10 -2
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/kis/simple.py +5 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/minutes.py +4 -4
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/position_classifier.py +1 -1
- {pyqqq-0.12.177 → pyqqq-0.12.179}/README.md +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/ai/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/ai/daily.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/ai/domestic.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/ai/market_schedule.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/ai/minute.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/backtest/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/backtest/logger.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/backtest/positionprovider.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/backtest/strategy.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/backtest/utils.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/backtest/wallclock.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/ebest/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/ebest/domestic_stock.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/ebest/oauth.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/ebest/simple.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/ebest/tr_client.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/helper.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/kis/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/kis/domestic_stock.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/kis/oauth.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/kis/overseas_stock.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/kis/simple_overseas.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/kis/tr_client.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/multiprocess_tracker.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/brokerage/tracker.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/config.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/daily.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/domestic.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/index.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/overseas.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/realtime.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/ticks.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/data/us_stocks.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/datatypes.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/executors/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/executors/hook.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/__init__.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/api_client.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/array.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/casting.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/compute.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/copycat.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/daily_tickers.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/display.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/kvstore.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/limiter.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/local_cache.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/logger.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/market_schedule.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/mock_api.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/retry.py +0 -0
- {pyqqq-0.12.177 → pyqqq-0.12.179}/pyqqq/utils/singleton.py +0 -0
|
@@ -25,6 +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.position_classfier import PositionClassifier
|
|
28
29
|
|
|
29
30
|
MarketType = Literal["kr_stock", "us_stock"]
|
|
30
31
|
|
|
@@ -249,6 +250,8 @@ class TradingBroker(BaseBroker):
|
|
|
249
250
|
trading_api: Union[KISSimpleDomesticStock, KISSimpleOverseasStock],
|
|
250
251
|
clock: WallClock,
|
|
251
252
|
market_nxt_on: bool = False,
|
|
253
|
+
strategy_name: Optional[str] = None,
|
|
254
|
+
classifier_type: Optional[str] = None,
|
|
252
255
|
):
|
|
253
256
|
"""TradingBroker 클래스의 초기화 메서드입니다.
|
|
254
257
|
|
|
@@ -266,6 +269,14 @@ class TradingBroker(BaseBroker):
|
|
|
266
269
|
self.data_api = data_api
|
|
267
270
|
self.trading_api = trading_api
|
|
268
271
|
self.market_nxt_on = market_nxt_on
|
|
272
|
+
self.position_classifier = None
|
|
273
|
+
|
|
274
|
+
if classifier_type and classifier_type in ["auto", "direct"]:
|
|
275
|
+
if strategy_name:
|
|
276
|
+
kv_store_collection = f"{strategy_name}_classifier"
|
|
277
|
+
else:
|
|
278
|
+
kv_store_collection = f"account_{self.trading_api.account_no}_classifier"
|
|
279
|
+
self.position_classifier = PositionClassifier(simple_data_api=self.trading_api, kv_store_collection=kv_store_collection, default_type=classifier_type)
|
|
269
280
|
|
|
270
281
|
def get_account(self) -> dict:
|
|
271
282
|
return self.trading_api.get_account()
|
|
@@ -314,6 +325,7 @@ class TradingBroker(BaseBroker):
|
|
|
314
325
|
result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.KRX)
|
|
315
326
|
else:
|
|
316
327
|
result = self.data_api.get_historical_daily_data(code, from_date, to_date, adjusted_price=True, data_exchange=DataExchange.KRX)
|
|
328
|
+
result = result.sort_index(ascending=True)
|
|
317
329
|
return result
|
|
318
330
|
|
|
319
331
|
def get_orderbook(self, code: str, data_exchange: Optional[DataExchange] = None):
|
|
@@ -349,7 +361,20 @@ class TradingBroker(BaseBroker):
|
|
|
349
361
|
|
|
350
362
|
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:
|
|
351
363
|
self.logger.debug(f"create_order: {asset_code} {side} {quantity} {order_type} {price} {exchange}")
|
|
352
|
-
|
|
364
|
+
|
|
365
|
+
if side is OrderSide.SELL and self.position_classifier:
|
|
366
|
+
# 포지션 분류기를 사용하여 전략에서 보유중인 수량으로 매도 가능 수량 재 계산
|
|
367
|
+
calc_quantity = self.position_classifier.get_sellable_quantity(asset_code, quantity)
|
|
368
|
+
|
|
369
|
+
if calc_quantity != quantity:
|
|
370
|
+
self.logger.warning(f"Requested sell quantity {quantity} for {asset_code} adjusted to {calc_quantity} based on position classifier.")
|
|
371
|
+
quantity = calc_quantity
|
|
372
|
+
|
|
373
|
+
order_no = self.trading_api.create_order(asset_code, side, quantity, order_type, price, exchange=exchange)
|
|
374
|
+
if order_no and self.position_classifier:
|
|
375
|
+
# 주문이 성공적으로 생성되면 포지션 분류기에 주문 정보를 업데이트
|
|
376
|
+
self.position_classifier.tagging_order_auto(order_no)
|
|
377
|
+
return order_no
|
|
353
378
|
|
|
354
379
|
def update_order(self, org_order_no: str, order_type: OrderType, price: int | Decimal, quantity: int = 0, exchange: OrderExchange = OrderExchange.KRX):
|
|
355
380
|
if isinstance(self.trading_api, KISSimpleOverseasStock):
|
|
@@ -386,6 +411,13 @@ class TradingBroker(BaseBroker):
|
|
|
386
411
|
self.logger.debug(f"cancel_order: {order_no} {quantity}")
|
|
387
412
|
return self.trading_api.cancel_order(order_no, quantity)
|
|
388
413
|
|
|
414
|
+
async def start_classifier(self):
|
|
415
|
+
""" 포지션 분류기를 시작합니다.
|
|
416
|
+
이 메서드는 포지션 분류기를 사용한다면 반드시 호출해야 합니다.
|
|
417
|
+
"""
|
|
418
|
+
if self.position_classifier:
|
|
419
|
+
await self.position_classifier.start()
|
|
420
|
+
|
|
389
421
|
|
|
390
422
|
# 백테스팅 할때 사용되는 브로커
|
|
391
423
|
class MockBroker(BaseBroker):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime as dtm
|
|
2
2
|
import os
|
|
3
3
|
from abc import ABC
|
|
4
|
-
from typing import Literal
|
|
4
|
+
from typing import Literal, Optional
|
|
5
5
|
from zoneinfo import ZoneInfo
|
|
6
6
|
|
|
7
7
|
from pyqqq.backtest.broker import BaseBroker, MockBroker, TradingBroker
|
|
@@ -142,12 +142,18 @@ class KISDomesticEnvironment(TradingEnvironment):
|
|
|
142
142
|
```
|
|
143
143
|
"""
|
|
144
144
|
|
|
145
|
-
def __init__(self, paper_trading: bool = False, market_nxt_on: bool = False):
|
|
145
|
+
def __init__(self, paper_trading: bool = False, market_nxt_on: bool = False, strategy_name: Optional[str] = None, classifier_type: Optional[str] = None):
|
|
146
146
|
"""KISDomesticEnvironment 클래스의 초기화 메서드입니다.
|
|
147
147
|
|
|
148
148
|
Args:
|
|
149
149
|
paper_trading (bool, optional): 모의투자 사용 여부. Defaults to False.
|
|
150
150
|
True일 경우 모의투자 계좌 사용
|
|
151
|
+
market_nxt_on (bool, optional): NXT 시장 처리 여부. Defaults to False.
|
|
152
|
+
strategy_name (str, optional): 전략 이름. Defaults to None.
|
|
153
|
+
position classifier 데이터가 저장되는 kvstore collection 이름에 사용됨.
|
|
154
|
+
classifier_type (str, optional): "direct", "auto" 중 하나로 설정시 position classifier 사용하게 됨. Defaults to None.
|
|
155
|
+
해당 값은 분류기가 기본 포지션을 어떤 방식으로 결정할지에 대한 설정.
|
|
156
|
+
|
|
151
157
|
|
|
152
158
|
Raises:
|
|
153
159
|
KeyError: 필요한 환경변수가 설정되지 않은 경우
|
|
@@ -160,6 +166,8 @@ class KISDomesticEnvironment(TradingEnvironment):
|
|
|
160
166
|
trading_api=conn.broker_simple if not paper_trading else conn.paper_broker_simple,
|
|
161
167
|
clock=self.clock,
|
|
162
168
|
market_nxt_on=market_nxt_on,
|
|
169
|
+
strategy_name=strategy_name,
|
|
170
|
+
classifier_type=classifier_type,
|
|
163
171
|
)
|
|
164
172
|
|
|
165
173
|
|
|
@@ -291,6 +291,7 @@ class KISSimpleDomesticStock:
|
|
|
291
291
|
분봉 데이터 검색
|
|
292
292
|
|
|
293
293
|
Note:
|
|
294
|
+
- 시간외 종가, 시간외 단일가 데이터는 포함되어 있지 않습니다.
|
|
294
295
|
- 두 거래소에서 공통으로 거래정지된 종목의 시가/고가/저가/종가는 모두 동일하며, 그 외 값은 모두 0 입니다.
|
|
295
296
|
- NXT 매매체결 종목이 아니거나, NXT 거래소에서 거래가 불가능한 종목을 DataExchange.NXT 또는 DataExchange.UN으로 조회하면, 모든 값은 0 입니다.
|
|
296
297
|
|
|
@@ -307,6 +308,10 @@ class KISSimpleDomesticStock:
|
|
|
307
308
|
result = []
|
|
308
309
|
schedule = get_market_schedule(dtm.date.today(), exchange="KRX" if data_exchange == DataExchange.KRX else "NXT")
|
|
309
310
|
|
|
311
|
+
# 만약 현재 시간이 거래소 마감 시간을 지났다면, 마감 시간으로 설정
|
|
312
|
+
if request_time.time() > schedule.close_time:
|
|
313
|
+
request_time = request_datetime.replace(hour=schedule.close_time.hour, minute=schedule.close_time.minute)
|
|
314
|
+
|
|
310
315
|
while True:
|
|
311
316
|
r = self.stock_api.inquire_time_itemchartprice(
|
|
312
317
|
asset_code,
|
|
@@ -18,7 +18,7 @@ minuteCache = DiskCacheManager("minute_cache")
|
|
|
18
18
|
@minuteCache.memoize()
|
|
19
19
|
def get_all_minute_data(
|
|
20
20
|
time: datetime.datetime,
|
|
21
|
-
source: str = "
|
|
21
|
+
source: str = "kis",
|
|
22
22
|
adjusted: bool = True,
|
|
23
23
|
exchange: Union[str, DataExchange] = "KRX",
|
|
24
24
|
) -> pd.DataFrame:
|
|
@@ -31,7 +31,7 @@ def get_all_minute_data(
|
|
|
31
31
|
|
|
32
32
|
Args:
|
|
33
33
|
time (datetime.datetime): 조회할 시간
|
|
34
|
-
source (str): 데이터를 검색할 API. 'ebest' 또는 'kis'를 지정할 수 있습니다. 기본값은 '
|
|
34
|
+
source (str): 데이터를 검색할 API. 'ebest' 또는 'kis'를 지정할 수 있습니다. 기본값은 'kis'입니다.
|
|
35
35
|
adjusted (bool): 수정주가 여부. 기본값은 True.
|
|
36
36
|
exchange (Union[str, DataExchange]): 거래소. 기본값은 KRX.
|
|
37
37
|
|
|
@@ -130,7 +130,7 @@ def get_all_day_data(
|
|
|
130
130
|
date: datetime.date,
|
|
131
131
|
codes: list[str] | str,
|
|
132
132
|
period: datetime.timedelta = datetime.timedelta(minutes=1),
|
|
133
|
-
source: str = "
|
|
133
|
+
source: str = "kis",
|
|
134
134
|
adjusted: bool = True,
|
|
135
135
|
ascending: bool = True,
|
|
136
136
|
exchange: Union[str, DataExchange] = "KRX",
|
|
@@ -146,7 +146,7 @@ def get_all_day_data(
|
|
|
146
146
|
date (datetime.date): 데이터를 검색할 날짜.
|
|
147
147
|
codes (list[str]): 조회할 주식 코드들의 리스트. 최대 20개까지 지정할 수 있습니다.
|
|
148
148
|
period (datetime.timedelta, optional): 반환된 데이터의 시간 간격. 기본값은 1분입니다. 30초 이상의 값을 30초간격으로 지정할 수 있습니다.
|
|
149
|
-
source (str, optional): 데이터를 검색할 API. 'ebest' 또는 'kis'를 지정할 수 있습니다. 기본값은 '
|
|
149
|
+
source (str, optional): 데이터를 검색할 API. 'ebest' 또는 'kis'를 지정할 수 있습니다. 기본값은 'kis'입니다.
|
|
150
150
|
adjusted (bool): 수정주가 여부. 기본값은 True.
|
|
151
151
|
ascending (bool): 오름차순 여부. 기본값은 True.
|
|
152
152
|
exchange (Union[str, DataExchange]): 거래소. 기본값은 KRX. (cf. NXT의 경우 해당되지 않는 종목은 Empty DataFrame이 반환됩니다.)
|
|
@@ -107,7 +107,7 @@ class PositionClassifier:
|
|
|
107
107
|
self.logger.info(f"set_initial_position.\npositions: {cur_pos}\nauto_positions: {self.auto_positions}\ndirect_positions: {self.direct_positions}")
|
|
108
108
|
|
|
109
109
|
def set_initial_order(self):
|
|
110
|
-
cur_order = self.api.get_pending_orders()
|
|
110
|
+
cur_order = self.api.get_pending_orders(exchanges=[OrderExchange.KRX, OrderExchange.NXT, OrderExchange.SOR])
|
|
111
111
|
kv_auto_orders = self.kv_store.get(self.DEF_AUTO_ORDER_KEY) or {}
|
|
112
112
|
kv_direct_orders = self.kv_store.get(self.DEF_DIRECT_ORDER_KEY) or {}
|
|
113
113
|
kv_auto_tag_orders = self.kv_store.get(self.DEF_TAG_AUTO_ORDER_KEY) or []
|
|
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
|