pyqqq 0.12.181__tar.gz → 0.12.183__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.
- {pyqqq-0.12.181 → pyqqq-0.12.183}/PKG-INFO +1 -1
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyproject.toml +3 -1
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/backtest/broker.py +1 -1
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/kis/simple.py +9 -4
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/minutes.py +16 -1
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/realtime.py +16 -3
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/limiter.py +4 -2
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/market_schedule.py +13 -3
- {pyqqq-0.12.181 → pyqqq-0.12.183}/README.md +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/ai/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/ai/daily.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/ai/domestic.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/ai/market_schedule.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/ai/minute.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/backtest/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/backtest/environment.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/backtest/logger.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/backtest/positionprovider.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/backtest/strategy.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/backtest/utils.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/backtest/wallclock.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/ebest/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/ebest/domestic_stock.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/ebest/oauth.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/ebest/simple.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/ebest/tr_client.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/helper.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/kis/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/kis/domestic_stock.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/kis/oauth.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/kis/overseas_stock.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/kis/simple_overseas.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/kis/tr_client.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/multiprocess_tracker.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/brokerage/tracker.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/config.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/daily.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/domestic.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/index.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/overseas.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/ticks.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/data/us_stocks.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/datatypes.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/executors/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/executors/hook.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/__init__.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/api_client.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/array.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/casting.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/compute.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/copycat.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/daily_tickers.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/display.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/kvstore.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/local_cache.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/logger.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/mock_api.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/position_classifier.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/retry.py +0 -0
- {pyqqq-0.12.181 → pyqqq-0.12.183}/pyqqq/utils/singleton.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pyqqq"
|
|
3
|
-
version = "0.12.
|
|
3
|
+
version = "0.12.183"
|
|
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"
|
|
@@ -30,6 +30,8 @@ ipykernel = "^6.29.4"
|
|
|
30
30
|
pydata-sphinx-theme = "^0.15.2"
|
|
31
31
|
sphinx-copybutton = "^0.5.2"
|
|
32
32
|
sphinx-togglebutton = "^0.3.2"
|
|
33
|
+
yappi = "^1.6.10"
|
|
34
|
+
snakeviz = "^2.2.2"
|
|
33
35
|
|
|
34
36
|
[tool.black]
|
|
35
37
|
line-length = 240
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import datetime as dtm
|
|
1
3
|
from decimal import Decimal
|
|
4
|
+
from typing import AsyncGenerator, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
2
8
|
from pyqqq.brokerage.kis.domestic_stock import KISDomesticStock
|
|
3
9
|
from pyqqq.brokerage.kis.oauth import KISAuth
|
|
4
10
|
from pyqqq.data.realtime import get_all_last_trades
|
|
5
11
|
from pyqqq.datatypes import *
|
|
12
|
+
from pyqqq.utils.limiter import CallLimiter
|
|
6
13
|
from pyqqq.utils.logger import get_logger
|
|
7
14
|
from pyqqq.utils.market_schedule import get_market_schedule, is_full_day_closed
|
|
8
15
|
from pyqqq.utils.mock_api import with_mock
|
|
9
|
-
from typing import AsyncGenerator, Dict, List, Optional
|
|
10
|
-
import asyncio
|
|
11
|
-
import datetime as dtm
|
|
12
|
-
import pandas as pd
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class KISStockPosition(StockPosition):
|
|
@@ -336,6 +339,8 @@ class KISSimpleDomesticStock:
|
|
|
336
339
|
request_time = request_datetime.replace(hour=schedule.close_time.hour, minute=schedule.close_time.minute)
|
|
337
340
|
|
|
338
341
|
while True:
|
|
342
|
+
CallLimiter().wait_limit_rate(20, scope="kis/inquire_time_itemchartprice")
|
|
343
|
+
|
|
339
344
|
r = self.stock_api.inquire_time_itemchartprice(
|
|
340
345
|
asset_code,
|
|
341
346
|
request_time.time(),
|
|
@@ -29,6 +29,9 @@ def get_all_minute_data(
|
|
|
29
29
|
|
|
30
30
|
NXT 거래소 데이터의 조회 가능 시작일은 데이터 소스에 따라 다릅니다. kis는 2025년 3월 4일부터, ebest는 2025년 5월 12일부터 데이터를 조회할 수 있습니다.
|
|
31
31
|
|
|
32
|
+
Note:
|
|
33
|
+
- KRX, NXT 거래소의 분봉 데이터를 조회할 수 있습니다. UN 거래소는 지원되지 않습니다.
|
|
34
|
+
|
|
32
35
|
Args:
|
|
33
36
|
time (datetime.datetime): 조회할 시간
|
|
34
37
|
source (str): 데이터를 검색할 API. 'ebest' 또는 'kis'를 지정할 수 있습니다. 기본값은 'kis'입니다.
|
|
@@ -63,6 +66,9 @@ def get_all_minute_data(
|
|
|
63
66
|
- mdvolumetm (int): 시간별매도체결량
|
|
64
67
|
- msvolumetm (int): 시간별매수체결량
|
|
65
68
|
|
|
69
|
+
Raises:
|
|
70
|
+
ValueError: 지원하지 않는 거래소 코드가 전달된 경우.
|
|
71
|
+
|
|
66
72
|
Examples:
|
|
67
73
|
>>> df = get_all_minute_data(datetime.datetime(2024, 5, 2, 15, 30))
|
|
68
74
|
>>> print(df)
|
|
@@ -77,7 +83,10 @@ def get_all_minute_data(
|
|
|
77
83
|
[5 rows x 23 columns]
|
|
78
84
|
"""
|
|
79
85
|
tz = pytz.timezone("Asia/Seoul")
|
|
86
|
+
|
|
80
87
|
exchange = DataExchange.validate(exchange)
|
|
88
|
+
if exchange == DataExchange.UN:
|
|
89
|
+
raise ValueError("UN 거래소는 지원되지 않습니다.")
|
|
81
90
|
|
|
82
91
|
url = f"{c.PYQQQ_API_URL}/domestic-stock/ohlcv/minutes/all/{time.date()}/{time.strftime('%H%M')}"
|
|
83
92
|
params = {
|
|
@@ -142,6 +151,9 @@ def get_all_day_data(
|
|
|
142
151
|
|
|
143
152
|
NXT 거래소 데이터의 조회 가능 시작일은 데이터 소스에 따라 다릅니다. kis는 2025년 3월 4일부터, ebest는 2025년 5월 12일부터 데이터를 조회할 수 있습니다.
|
|
144
153
|
|
|
154
|
+
Note:
|
|
155
|
+
- KRX, NXT 거래소의 분봉 데이터를 조회할 수 있습니다. UN 거래소는 지원되지 않습니다.
|
|
156
|
+
|
|
145
157
|
Args:
|
|
146
158
|
date (datetime.date): 데이터를 검색할 날짜.
|
|
147
159
|
codes (list[str]): 조회할 주식 코드들의 리스트. 최대 20개까지 지정할 수 있습니다.
|
|
@@ -195,6 +207,7 @@ def get_all_day_data(
|
|
|
195
207
|
2024-04-26 09:04:00 77400 77600 77400 77500 3268502 2 1200 1.57
|
|
196
208
|
"""
|
|
197
209
|
assert isinstance(date, datetime.date), "date must be a datetime.date object"
|
|
210
|
+
assert type(date) is datetime.date, "date must be a datetime.date object"
|
|
198
211
|
assert isinstance(codes, list) or isinstance(codes, str), "codes must be a list of strings or single code"
|
|
199
212
|
|
|
200
213
|
if isinstance(codes, list):
|
|
@@ -210,7 +223,9 @@ def get_all_day_data(
|
|
|
210
223
|
target_codes = codes if isinstance(codes, list) else [codes]
|
|
211
224
|
|
|
212
225
|
exchange = DataExchange.validate(exchange)
|
|
213
|
-
if exchange == DataExchange.
|
|
226
|
+
if exchange == DataExchange.UN:
|
|
227
|
+
raise ValueError("UN 거래소는 지원되지 않습니다.")
|
|
228
|
+
elif exchange == DataExchange.NXT or source == "kis":
|
|
214
229
|
url = f"{c.PYQQQ_API_URL}/domestic-stock/ohlcv/minutes/{date}"
|
|
215
230
|
else:
|
|
216
231
|
url = f"{c.PYQQQ_API_URL}/domestic-stock/ohlcv/half-minutes/{date}"
|
|
@@ -3,11 +3,12 @@ import datetime as dtm
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
-
from typing import List
|
|
6
|
+
from typing import List, Union
|
|
7
7
|
|
|
8
8
|
import websockets
|
|
9
9
|
|
|
10
10
|
import pyqqq.config as c
|
|
11
|
+
from pyqqq.datatypes import DataExchange
|
|
11
12
|
from pyqqq.utils.api_client import raise_for_status, send_request
|
|
12
13
|
from pyqqq.utils.logger import get_logger
|
|
13
14
|
from pyqqq.utils.singleton import singleton
|
|
@@ -15,10 +16,16 @@ from pyqqq.utils.singleton import singleton
|
|
|
15
16
|
logger = get_logger(__name__)
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def get_all_last_trades(
|
|
19
|
+
def get_all_last_trades(
|
|
20
|
+
codes: List[str] = None,
|
|
21
|
+
exchange: Union[str, DataExchange] = "KRX",
|
|
22
|
+
):
|
|
19
23
|
"""
|
|
20
24
|
모든 종목의 최근 체결 정보를 반환합니다.
|
|
21
25
|
|
|
26
|
+
Note:
|
|
27
|
+
- KRX, UN 거래소의 최근 체결 정보를 조회할 수 있습니다. NXT 거래소는 지원되지 않습니다.
|
|
28
|
+
|
|
22
29
|
Args:
|
|
23
30
|
codes (List[str], optional): 종목 코드 리스트. 모든 종목의 체결 정보를 반환하려면 None을 전달합니다.
|
|
24
31
|
exchange (str): 거래소. 기본값은 "KRX"입니다. 지원하는 거래소는 "KRX", "UN" 입니다.
|
|
@@ -50,9 +57,15 @@ def get_all_last_trades(codes: List[str] = None, exchange: str = "KRX"):
|
|
|
50
57
|
- exchname (str): 거래소명
|
|
51
58
|
- date (str): 체결일자
|
|
52
59
|
- update_time (str): 업데이트 시간
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: 지원하지 않는 거래소 코드가 전달된 경우.
|
|
53
63
|
"""
|
|
64
|
+
exchange = DataExchange.validate(exchange)
|
|
65
|
+
if exchange == DataExchange.NXT:
|
|
66
|
+
raise ValueError("NXT 거래소는 지원되지 않습니다.")
|
|
54
67
|
|
|
55
|
-
params = {"exchange": exchange}
|
|
68
|
+
params = {"exchange": exchange.value}
|
|
56
69
|
if codes:
|
|
57
70
|
params["codes"] = ",".join(codes) if isinstance(codes, list) else codes
|
|
58
71
|
|
|
@@ -3,7 +3,7 @@ import time
|
|
|
3
3
|
|
|
4
4
|
_MAX_CALLS = 5
|
|
5
5
|
_PERIOD = 1
|
|
6
|
-
_SCOPE =
|
|
6
|
+
_SCOPE = "default"
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class CallLimiter:
|
|
@@ -13,12 +13,13 @@ class CallLimiter:
|
|
|
13
13
|
이 클래스는 API 호출 빈도를 제한하여 너무 많은 요청으로 인해 서비스 제한을 받는 것을 방지합니다.
|
|
14
14
|
이 클래스의 인스턴스는 전역에서 단 하나만 존재하며, 다양한 'scope'에 대한 호출 제한 윈도우를 관리합니다.
|
|
15
15
|
"""
|
|
16
|
+
|
|
16
17
|
_instance = None
|
|
17
18
|
|
|
18
19
|
def __new__(cls):
|
|
19
20
|
if cls._instance is None:
|
|
20
21
|
cls._instance = super(CallLimiter, cls).__new__(cls)
|
|
21
|
-
cls._instance.windows = {
|
|
22
|
+
cls._instance.windows = {"default": []}
|
|
22
23
|
|
|
23
24
|
return cls._instance
|
|
24
25
|
|
|
@@ -63,6 +64,7 @@ def limit_calls(max_calls: int = _MAX_CALLS, period: float = _PERIOD, scope: str
|
|
|
63
64
|
period (float): 기간(초) 동안의 호출 횟수를 제한합니다.
|
|
64
65
|
scope (str): 호출 제한을 적용할 스코프 이름입니다.
|
|
65
66
|
"""
|
|
67
|
+
|
|
66
68
|
def decorator(func):
|
|
67
69
|
def wrapper(*args, **kwargs):
|
|
68
70
|
CallLimiter().wait_limit_rate(max_calls=max_calls, period=period, scope=scope)
|
|
@@ -137,11 +137,21 @@ def is_trading_time(
|
|
|
137
137
|
Returns:
|
|
138
138
|
bool: 거래 시간 여부
|
|
139
139
|
"""
|
|
140
|
+
if now is None:
|
|
141
|
+
now = datetime.datetime.now()
|
|
142
|
+
|
|
140
143
|
exchange = _validate_exchange(exchange)
|
|
141
144
|
if exchange == Exchange.NXT and now.date() < datetime.date(2025, 3, 4):
|
|
142
145
|
raise ValueError("NXT 거래소는 2025년 3월 4일 부터 운영되었습니다. 이전 날짜는 지원하지 않습니다.")
|
|
143
146
|
|
|
144
|
-
|
|
147
|
+
schedule = get_market_schedule(now.date(), exchange)
|
|
148
|
+
if schedule.full_day_closed:
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
if now.time() < schedule.open_time or now.time() > schedule.close_time:
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
return True
|
|
145
155
|
|
|
146
156
|
|
|
147
157
|
def get_market_schedule(
|
|
@@ -170,7 +180,7 @@ def get_market_schedule(
|
|
|
170
180
|
return _get_krx_schedule(date)
|
|
171
181
|
|
|
172
182
|
|
|
173
|
-
@ttl_cache(maxsize=
|
|
183
|
+
@ttl_cache(maxsize=10, ttl=3600)
|
|
174
184
|
def _get_nyse_schedule(date: datetime.date) -> MarketSchedule:
|
|
175
185
|
"""NYSE 시장 스케줄을 조회합니다."""
|
|
176
186
|
cal = mcal.get_calendar("NYSE")
|
|
@@ -338,7 +348,7 @@ def get_trading_day_with_offset(from_date: Optional[datetime.date] = None, offse
|
|
|
338
348
|
return offset_date
|
|
339
349
|
|
|
340
350
|
|
|
341
|
-
@ttl_cache(maxsize=
|
|
351
|
+
@ttl_cache(maxsize=10, ttl=3600)
|
|
342
352
|
def _fetch_market_scheldue(date: datetime.date, exchange: str) -> requests.Response | None:
|
|
343
353
|
url = f"{c.PYQQQ_API_URL}/domestic-stock/market-schedules/{exchange}"
|
|
344
354
|
params = {"date": date}
|
|
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
|