pyqqq 0.12.185__tar.gz → 0.12.186__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.185 → pyqqq-0.12.186}/PKG-INFO +1 -1
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyproject.toml +1 -1
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/backtest/environment.py +7 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/logger.py +53 -19
- pyqqq-0.12.185/pyqqq/ai/daily.py +0 -46
- pyqqq-0.12.185/pyqqq/ai/domestic.py +0 -57
- pyqqq-0.12.185/pyqqq/ai/market_schedule.py +0 -22
- pyqqq-0.12.185/pyqqq/ai/minute.py +0 -52
- pyqqq-0.12.185/pyqqq/utils/__init__.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/README.md +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/__init__.py +0 -0
- {pyqqq-0.12.185/pyqqq/ai → pyqqq-0.12.186/pyqqq/backtest}/__init__.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/backtest/broker.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/backtest/logger.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/backtest/positionprovider.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/backtest/strategy.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/backtest/utils.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/backtest/wallclock.py +0 -0
- {pyqqq-0.12.185/pyqqq/backtest → pyqqq-0.12.186/pyqqq/brokerage}/__init__.py +0 -0
- {pyqqq-0.12.185/pyqqq/brokerage → pyqqq-0.12.186/pyqqq/brokerage/ebest}/__init__.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/ebest/domestic_stock.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/ebest/oauth.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/ebest/simple.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/ebest/tr_client.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/helper.py +0 -0
- {pyqqq-0.12.185/pyqqq/brokerage/ebest → pyqqq-0.12.186/pyqqq/brokerage/kis}/__init__.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/kis/domestic_stock.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/kis/oauth.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/kis/overseas_stock.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/kis/simple.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/kis/simple_overseas.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/kis/tr_client.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/multiprocess_tracker.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/brokerage/tracker.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/config.py +0 -0
- {pyqqq-0.12.185/pyqqq/brokerage/kis → pyqqq-0.12.186/pyqqq/data}/__init__.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/data/daily.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/data/domestic.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/data/index.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/data/minutes.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/data/overseas.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/data/realtime.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/data/ticks.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/data/us_stocks.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/datatypes.py +0 -0
- {pyqqq-0.12.185/pyqqq/data → pyqqq-0.12.186/pyqqq/executors}/__init__.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/executors/hook.py +0 -0
- {pyqqq-0.12.185/pyqqq/executors → pyqqq-0.12.186/pyqqq/utils}/__init__.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/api_client.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/array.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/casting.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/compute.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/copycat.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/daily_tickers.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/display.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/kvstore.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/limiter.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/local_cache.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/market_schedule.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/mock_api.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/position_classifier.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/retry.py +0 -0
- {pyqqq-0.12.185 → pyqqq-0.12.186}/pyqqq/utils/singleton.py +0 -0
|
@@ -11,6 +11,8 @@ from pyqqq.brokerage.helper import EBestConnection, KISConnection
|
|
|
11
11
|
from pyqqq.brokerage.kis.oauth import KISAuth
|
|
12
12
|
from pyqqq.brokerage.kis.simple_overseas import KISSimpleOverseasStock
|
|
13
13
|
|
|
14
|
+
from pyqqq.utils.logger import get_handlers
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
class TradingEnvironment(ABC):
|
|
16
18
|
"""트레이딩 환경을 정의하는 추상 기본 클래스입니다.
|
|
@@ -113,6 +115,11 @@ class BacktestEnvironment(TradingEnvironment):
|
|
|
113
115
|
self.clock = WallClock(live_mode=False, start_time=start_time, end_time=end_time, tzinfo=tzinfo)
|
|
114
116
|
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)
|
|
115
117
|
|
|
118
|
+
# logger 에 백테스트 시간 정보 추간
|
|
119
|
+
handlers = get_handlers()
|
|
120
|
+
for handler in handlers:
|
|
121
|
+
handler.update_format(clock=self.clock)
|
|
122
|
+
|
|
116
123
|
|
|
117
124
|
class KISDomesticEnvironment(TradingEnvironment):
|
|
118
125
|
"""한국투자증권 API를 사용한 국내주식 거래 환경입니다.
|
|
@@ -1,30 +1,59 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
|
|
3
|
-
import pyqqq.config as c
|
|
2
|
+
import logging.handlers
|
|
4
3
|
import sys
|
|
4
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import pyqqq.config as c
|
|
7
|
+
from pyqqq.backtest.wallclock import WallClock
|
|
7
8
|
|
|
9
|
+
_default_format = "%(levelname).1s %(name)s: %(message)s"
|
|
8
10
|
if not c.is_google_cloud_logging_enabled():
|
|
9
|
-
|
|
11
|
+
_default_format = "%(asctime)s " + _default_format
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CustomHandler(logging.StreamHandler):
|
|
15
|
+
custom_format: str
|
|
16
|
+
|
|
17
|
+
class ClockFormatter(logging.Formatter):
|
|
18
|
+
def __init__(self, fmt=None, style="%", clock: WallClock = None):
|
|
19
|
+
super().__init__(fmt, style)
|
|
20
|
+
self.clock = clock or WallClock(live_mode=True)
|
|
21
|
+
|
|
22
|
+
def formatTime(self, record, datefmt=None):
|
|
23
|
+
dt = self.clock.now()
|
|
24
|
+
return dt.isoformat(timespec="milliseconds")
|
|
10
25
|
|
|
11
|
-
|
|
26
|
+
def __init__(self, stream):
|
|
27
|
+
super().__init__(stream)
|
|
28
|
+
self.custom_format = _default_format
|
|
29
|
+
self.setFormatter(logging.Formatter(self.custom_format))
|
|
30
|
+
|
|
31
|
+
def emit(self, record):
|
|
32
|
+
super().emit(record)
|
|
33
|
+
|
|
34
|
+
def update_format(self, format=None, clock: WallClock = None):
|
|
35
|
+
if format:
|
|
36
|
+
self.custom_format = format
|
|
37
|
+
self.setFormatter(self.ClockFormatter(self.custom_format, clock=clock))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_stdout_h = CustomHandler(sys.stdout)
|
|
12
41
|
_stdout_h.setLevel(logging.DEBUG)
|
|
13
42
|
_stdout_h.addFilter(lambda r: r.levelno <= logging.WARNING)
|
|
14
43
|
|
|
15
|
-
_stderr_h =
|
|
44
|
+
_stderr_h = CustomHandler(sys.stderr)
|
|
16
45
|
_stderr_h.setLevel(logging.ERROR)
|
|
17
46
|
|
|
18
|
-
logging.basicConfig(
|
|
47
|
+
logging.basicConfig(handlers=[_stdout_h, _stderr_h])
|
|
19
48
|
|
|
20
49
|
|
|
21
50
|
def get_logger(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
51
|
+
name,
|
|
52
|
+
level=logging.DEBUG,
|
|
53
|
+
filename: str = None,
|
|
54
|
+
when: str = "h",
|
|
55
|
+
interval: int = 1,
|
|
56
|
+
backup_count: int = 24,
|
|
28
57
|
) -> logging.Logger:
|
|
29
58
|
"""
|
|
30
59
|
로깅을 위한 Logger 객체를 구성하고 반환합니다.
|
|
@@ -52,19 +81,17 @@ def get_logger(
|
|
|
52
81
|
logger.setLevel(level)
|
|
53
82
|
|
|
54
83
|
if not logger.handlers and filename:
|
|
55
|
-
fh = TimedRotatingFileHandler(
|
|
56
|
-
filename, when=when, backupCount=backup_count, interval=interval
|
|
57
|
-
)
|
|
84
|
+
fh = TimedRotatingFileHandler(filename, when=when, backupCount=backup_count, interval=interval)
|
|
58
85
|
fh.setLevel(level)
|
|
59
|
-
fh.setFormatter(logging.Formatter(
|
|
86
|
+
fh.setFormatter(logging.Formatter(_default_format))
|
|
60
87
|
logger.addHandler(fh)
|
|
61
88
|
|
|
62
89
|
return logger
|
|
63
90
|
|
|
64
91
|
|
|
65
92
|
def get_bare_logger(
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
name,
|
|
94
|
+
level=logging.NOTSET,
|
|
68
95
|
) -> logging.Logger:
|
|
69
96
|
"""
|
|
70
97
|
level을 지정하지 않으면 NOTSET으로 설정되는 로거를 반환하기 위해 사용됩니다.
|
|
@@ -113,3 +140,10 @@ def set_module_logger_level(level, name="pyqqq"):
|
|
|
113
140
|
for logger_name in logging.root.manager.loggerDict:
|
|
114
141
|
if logger_name == name or logger_name.startswith(f"{name}."):
|
|
115
142
|
logging.getLogger(logger_name).setLevel(level)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_handlers():
|
|
146
|
+
"""
|
|
147
|
+
CustomHandler 클래스의 인스턴스를 반환합니다.
|
|
148
|
+
"""
|
|
149
|
+
return [h for h in logging.root.handlers if isinstance(h, CustomHandler)]
|
pyqqq-0.12.185/pyqqq/ai/daily.py
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import pandas
|
|
3
|
-
from typing import Dict, List, Optional
|
|
4
|
-
import pyqqq.data.daily as daily
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_all_ohlcv_for_date(date: datetime.date, adjusted: bool = False) -> pandas.DataFrame:
|
|
8
|
-
if isinstance(date, str):
|
|
9
|
-
try:
|
|
10
|
-
date = datetime.date.fromisoformat(date)
|
|
11
|
-
except AttributeError:
|
|
12
|
-
raise TypeError("Invalid date format. Please use 'YYYY-MM-DD' format.")
|
|
13
|
-
|
|
14
|
-
df = daily.get_all_ohlcv_for_date(date, adjusted)
|
|
15
|
-
if df.empty:
|
|
16
|
-
raise ValueError(f"No OHLCV data found for {date}")
|
|
17
|
-
|
|
18
|
-
return df
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_ohlcv_by_codes_for_period(
|
|
22
|
-
codes: List[str],
|
|
23
|
-
start_date: datetime.date,
|
|
24
|
-
end_date: Optional[datetime.date] = None,
|
|
25
|
-
adjusted: bool = False,
|
|
26
|
-
ascending: bool = False,
|
|
27
|
-
) -> Dict[str, pandas.DataFrame]:
|
|
28
|
-
if isinstance(start_date, str):
|
|
29
|
-
try:
|
|
30
|
-
start_date = datetime.date.fromisoformat(start_date)
|
|
31
|
-
except AttributeError:
|
|
32
|
-
raise TypeError("Invalid start_date format. Please use 'YYYY-MM-DD' format.")
|
|
33
|
-
if isinstance(end_date, str):
|
|
34
|
-
try:
|
|
35
|
-
end_date = datetime.date.fromisoformat(end_date)
|
|
36
|
-
except AttributeError:
|
|
37
|
-
raise TypeError("Invalid end_date format. Please use 'YYYY-MM-DD' format.")
|
|
38
|
-
|
|
39
|
-
dict = daily.get_ohlcv_by_codes_for_period(codes, start_date, end_date, adjusted, ascending)
|
|
40
|
-
if not dict:
|
|
41
|
-
if end_date:
|
|
42
|
-
raise ValueError(f"No OHLCV data found for {start_date} to {end_date}")
|
|
43
|
-
else:
|
|
44
|
-
raise ValueError(f"No OHLCV data found for {start_date}")
|
|
45
|
-
|
|
46
|
-
return dict
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import pandas
|
|
3
|
-
from typing import List, Optional
|
|
4
|
-
import pyqqq.data.domestic as domestic
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_tickers(date: Optional[datetime.date] = None, market: Optional[str] = None) -> pandas.DataFrame:
|
|
8
|
-
if isinstance(date, str):
|
|
9
|
-
try:
|
|
10
|
-
date = datetime.date.fromisoformat(date)
|
|
11
|
-
except AttributeError:
|
|
12
|
-
raise TypeError("Invalid date format. Please use 'YYYY-MM-DD' format.")
|
|
13
|
-
|
|
14
|
-
df = domestic.get_tickers(date, market)
|
|
15
|
-
if df.empty:
|
|
16
|
-
if date:
|
|
17
|
-
raise ValueError(f"No tickers found for {date}")
|
|
18
|
-
else:
|
|
19
|
-
raise ValueError("No tickers found")
|
|
20
|
-
|
|
21
|
-
return df
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def get_market_cap(date: datetime.date = None) -> pandas.DataFrame:
|
|
25
|
-
if isinstance(date, str):
|
|
26
|
-
try:
|
|
27
|
-
date = datetime.date.fromisoformat(date)
|
|
28
|
-
except AttributeError:
|
|
29
|
-
raise TypeError("Invalid date format. Please use 'YYYY-MM-DD' format.")
|
|
30
|
-
|
|
31
|
-
df = domestic.get_market_cap(date)
|
|
32
|
-
if df.empty:
|
|
33
|
-
if date:
|
|
34
|
-
raise ValueError(f"No market cap data found for {date}")
|
|
35
|
-
else:
|
|
36
|
-
raise ValueError("No market cap data found")
|
|
37
|
-
|
|
38
|
-
df.rename(columns={"value": "market_cap", "shares": "outstanding_shares"}, inplace=True)
|
|
39
|
-
return df
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def get_market_cap_by_codes(codes: List[str], date: datetime.date = None) -> pandas.DataFrame:
|
|
43
|
-
if isinstance(date, str):
|
|
44
|
-
try:
|
|
45
|
-
date = datetime.date.fromisoformat(date)
|
|
46
|
-
except AttributeError:
|
|
47
|
-
raise TypeError("Invalid date format. Please use 'YYYY-MM-DD' format.")
|
|
48
|
-
|
|
49
|
-
df = domestic.get_market_cap_by_codes(codes, date)
|
|
50
|
-
if df.empty:
|
|
51
|
-
if date:
|
|
52
|
-
raise ValueError(f"No market cap data found for {date}")
|
|
53
|
-
else:
|
|
54
|
-
raise ValueError("No market cap data found")
|
|
55
|
-
|
|
56
|
-
df.rename(columns={"value": "market_cap", "shares": "outstanding_shares"}, inplace=True)
|
|
57
|
-
return df
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import pyqqq.utils.market_schedule as market_schedule
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def get_last_trading_day(date: datetime.date = None, exchange: str = "KRX") -> str:
|
|
6
|
-
if isinstance(date, str):
|
|
7
|
-
try:
|
|
8
|
-
date = datetime.date.fromisoformat(date)
|
|
9
|
-
except AttributeError:
|
|
10
|
-
raise TypeError("Invalid date format. Please use 'YYYY-MM-DD' format.")
|
|
11
|
-
|
|
12
|
-
return market_schedule.get_last_trading_day(date, exchange).isoformat()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def get_next_trading_day(date: datetime.date = None, exchange: str = "KRX") -> str:
|
|
16
|
-
if isinstance(date, str):
|
|
17
|
-
try:
|
|
18
|
-
date = datetime.date.fromisoformat(date)
|
|
19
|
-
except AttributeError:
|
|
20
|
-
raise TypeError("Invalid date format. Please use 'YYYY-MM-DD' format.")
|
|
21
|
-
|
|
22
|
-
return market_schedule.get_next_trading_day(date, exchange).isoformat()
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import pandas
|
|
3
|
-
from typing import Dict, List
|
|
4
|
-
import pyqqq.data.minutes as minutes
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_all_minute_data(time: datetime.datetime, source: str = "ebest", adjusted: bool = False) -> pandas.DataFrame:
|
|
8
|
-
if isinstance(time, str):
|
|
9
|
-
try:
|
|
10
|
-
time = datetime.datetime.fromisoformat(time)
|
|
11
|
-
except AttributeError:
|
|
12
|
-
raise TypeError("Invalid time format. Please use 'YYYY-MM-DDTHH:MM' format.")
|
|
13
|
-
|
|
14
|
-
df = minutes.get_all_minute_data(time, source, adjusted)
|
|
15
|
-
if df.empty:
|
|
16
|
-
raise ValueError(f"No minute data found for {time}")
|
|
17
|
-
|
|
18
|
-
return df
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def get_all_day_data(
|
|
22
|
-
date: datetime.date,
|
|
23
|
-
codes: List[str] | str,
|
|
24
|
-
period: datetime.timedelta = datetime.timedelta(minutes=1),
|
|
25
|
-
source: str = "ebest",
|
|
26
|
-
adjusted: bool = False,
|
|
27
|
-
ascending: bool = True,
|
|
28
|
-
) -> Dict[str, pandas.DataFrame]:
|
|
29
|
-
if isinstance(date, str):
|
|
30
|
-
try:
|
|
31
|
-
date = datetime.date.fromisoformat(date)
|
|
32
|
-
except AttributeError:
|
|
33
|
-
raise TypeError("Invalid date format. Please use 'YYYY-MM-DD' format.")
|
|
34
|
-
|
|
35
|
-
result = minutes.get_all_day_data(date, codes, period, source, adjusted, ascending)
|
|
36
|
-
|
|
37
|
-
# Ensure the result is a dict
|
|
38
|
-
if isinstance(result, pandas.DataFrame):
|
|
39
|
-
# Single DataFrame -> Convert to dict with one key
|
|
40
|
-
if isinstance(codes, str):
|
|
41
|
-
codes = [codes] # Handle single string case
|
|
42
|
-
result = {codes[0]: result}
|
|
43
|
-
|
|
44
|
-
if not result or all(df.empty for df in result.values()):
|
|
45
|
-
raise ValueError(f"No minute data found for {date}")
|
|
46
|
-
|
|
47
|
-
# Convert existing index to DatetimeIndex
|
|
48
|
-
for code, df in result.items():
|
|
49
|
-
if not isinstance(df.index, pandas.DatetimeIndex):
|
|
50
|
-
df.index = pandas.to_datetime(df.index)
|
|
51
|
-
|
|
52
|
-
return result
|
|
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
|