topquant-ksk 0.1.0__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.
- topquant_ksk-0.1.0/PKG-INFO +16 -0
- topquant_ksk-0.1.0/pyproject.toml +37 -0
- topquant_ksk-0.1.0/setup.cfg +4 -0
- topquant_ksk-0.1.0/src/topquant_ksk/__init__.py +2 -0
- topquant_ksk-0.1.0/src/topquant_ksk/load_data.py +161 -0
- topquant_ksk-0.1.0/src/topquant_ksk/metrics.py +124 -0
- topquant_ksk-0.1.0/src/topquant_ksk.egg-info/PKG-INFO +16 -0
- topquant_ksk-0.1.0/src/topquant_ksk.egg-info/SOURCES.txt +9 -0
- topquant_ksk-0.1.0/src/topquant_ksk.egg-info/dependency_links.txt +1 -0
- topquant_ksk-0.1.0/src/topquant_ksk.egg-info/requires.txt +3 -0
- topquant_ksk-0.1.0/src/topquant_ksk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: topquant-ksk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 기관 투자자를 위한 파이썬 퀀트 투자 백테스팅 및 분석 도구
|
|
5
|
+
Author-email: Your Name <your_email@example.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/your-username/topquantksk
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/your-username/topquantksk/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: pandas>=1.5.0
|
|
15
|
+
Requires-Dist: numpy>=1.20.0
|
|
16
|
+
Requires-Dist: tqdm
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 1. 빌드 시스템 설정 (대부분의 경우 그대로 사용)
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = ["setuptools>=61.0"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
# 2. 프로젝트 핵심 정보
|
|
7
|
+
[project]
|
|
8
|
+
# pip install topquant-ksk 처럼 사용될 패키지 이름
|
|
9
|
+
name = "topquant-ksk"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name="Your Name", email="your_email@example.com" },
|
|
13
|
+
]
|
|
14
|
+
description = "기관 투자자를 위한 파이썬 퀀트 투자 백테스팅 및 분석 도구"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
requires-python = ">=3.9"
|
|
17
|
+
license = { file="LICENSE" }
|
|
18
|
+
dependencies = [
|
|
19
|
+
"pandas>=1.5.0",
|
|
20
|
+
"numpy>=1.20.0",
|
|
21
|
+
"tqdm"
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# 3. 관련 링크 정보 (PyPI 페이지에 표시됨)
|
|
31
|
+
[project.urls]
|
|
32
|
+
"Homepage" = "https://github.com/your-username/topquantksk"
|
|
33
|
+
"Bug Tracker" = "https://github.com/your-username/topquantksk/issues"
|
|
34
|
+
|
|
35
|
+
# 4. 소스코드 위치 지정
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
# ==============================================================================
|
|
6
|
+
# ## 헬퍼(Helper) 함수 (이전과 동일)
|
|
7
|
+
# ==============================================================================
|
|
8
|
+
|
|
9
|
+
def find_file_recursive(filename: str) -> str | None:
|
|
10
|
+
"""현재 디렉토리와 하위에서 파일을 재귀적으로 찾아 경로를 반환합니다."""
|
|
11
|
+
root_dir = os.getcwd()
|
|
12
|
+
for root, _, files in os.walk(root_dir):
|
|
13
|
+
if filename in files:
|
|
14
|
+
return os.path.join(root, filename)
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
def _load_file(file_path: str, sheet_name: str | None = None) -> pd.DataFrame | None:
|
|
18
|
+
"""파일 경로와 시트 이름(선택)으로 데이터프레임을 로드하는 공통 함수"""
|
|
19
|
+
try:
|
|
20
|
+
if file_path.endswith('.xlsx'):
|
|
21
|
+
return pd.read_excel(file_path, sheet_name=sheet_name, index_col=[0])
|
|
22
|
+
elif file_path.endswith('.csv'):
|
|
23
|
+
return pd.read_csv(file_path, encoding='cp949', index_col=[0], low_memory=False)
|
|
24
|
+
else:
|
|
25
|
+
print(f"지원하지 않는 파일 형식입니다: {file_path}")
|
|
26
|
+
return None
|
|
27
|
+
except Exception as e:
|
|
28
|
+
print(f"파일 로드 중 오류: {e}")
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
def _process_dataframe(df: pd.DataFrame) -> pd.DataFrame:
|
|
32
|
+
"""데이터프레임 후처리를 위한 공통 함수"""
|
|
33
|
+
idx = df.index
|
|
34
|
+
with warnings.catch_warnings():
|
|
35
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
36
|
+
non_date_elements = idx[pd.to_datetime(idx, errors='coerce').isna()]
|
|
37
|
+
df.drop(non_date_elements, inplace=True)
|
|
38
|
+
df.index = pd.to_datetime(df.index)
|
|
39
|
+
df.index.name = None
|
|
40
|
+
df.replace(',', '', regex=True, inplace=True)
|
|
41
|
+
df.dropna(how='all',axis=1,inplace=True)
|
|
42
|
+
print("float 타입으로 변환을 시도합니다...")
|
|
43
|
+
for col in df.columns:
|
|
44
|
+
try:
|
|
45
|
+
df[col] = df[col].astype(float)
|
|
46
|
+
except ValueError:
|
|
47
|
+
pass
|
|
48
|
+
return df
|
|
49
|
+
|
|
50
|
+
# ✨ 수정된 마스터 헬퍼 함수
|
|
51
|
+
def _load_and_process_data(filename: str, column_spec: list, data_type_name: str, sheet_name: str | None = None) -> pd.DataFrame | None:
|
|
52
|
+
"""파일 검색, 로드, 후처리 전체 과정을 수행하는 마스터 헬퍼 함수"""
|
|
53
|
+
file_path = find_file_recursive(filename)
|
|
54
|
+
if not file_path:
|
|
55
|
+
print(f"'{filename}' 파일을 찾을 수 없습니다. 🤷♂️")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
print(f"파일 발견! '{file_path}' 파일을 로드합니다... 📂")
|
|
59
|
+
df = _load_file(file_path, sheet_name=sheet_name)
|
|
60
|
+
if df is None:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
print(f"{data_type_name} 데이터 후처리를 시작합니다... 🛠️")
|
|
64
|
+
|
|
65
|
+
# --- ✨ 컬럼 설정 로직 수정 ✨ ---
|
|
66
|
+
if len(column_spec) == 1:
|
|
67
|
+
# column_spec의 길이가 1이면 단일 인덱스로 설정
|
|
68
|
+
df.columns = df.loc[column_spec[0]]
|
|
69
|
+
else:
|
|
70
|
+
# 길이가 1보다 크면 멀티인덱스로 설정
|
|
71
|
+
df.columns = [df.loc[name] for name in column_spec]
|
|
72
|
+
|
|
73
|
+
# 공통 후처리 로직 호출
|
|
74
|
+
df = _process_dataframe(df)
|
|
75
|
+
|
|
76
|
+
print("처리 완료! ✨")
|
|
77
|
+
return df
|
|
78
|
+
|
|
79
|
+
# ==============================================================================
|
|
80
|
+
# ## ✨ 메인(Main) 데이터 로드 함수 (수정됨) ✨
|
|
81
|
+
# ==============================================================================
|
|
82
|
+
|
|
83
|
+
def load_FactSet_TimeSeriesData(filename: str, sheet_name: str | None = 'TimeSeries') -> pd.DataFrame | None:
|
|
84
|
+
"""TimeSeries 데이터를 로드합니다. (3-level columns)"""
|
|
85
|
+
return _load_and_process_data(
|
|
86
|
+
filename=filename,
|
|
87
|
+
sheet_name=sheet_name,
|
|
88
|
+
column_spec=['Item Name', 'Symbol Name', 'Symbol'],
|
|
89
|
+
data_type_name='TimeSeries'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def load_DataGuide_TimeSeriesData(filename: str, sheet_name: str | None = 'TimeSeries') -> pd.DataFrame | None:
|
|
94
|
+
"""TimeSeries 데이터를 로드합니다. (3-level columns)"""
|
|
95
|
+
return _load_and_process_data(
|
|
96
|
+
filename=filename,
|
|
97
|
+
sheet_name=sheet_name,
|
|
98
|
+
column_spec=['Item Name', 'Symbol Name', 'Symbol'],
|
|
99
|
+
data_type_name='TimeSeries'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def load_DataGuide_IndexData(filename: str, sheet_name: str | None = 'TimeSeries') -> pd.DataFrame | None:
|
|
103
|
+
"""Index 데이터를 로드합니다. (2-level columns)"""
|
|
104
|
+
return _load_and_process_data(
|
|
105
|
+
filename=filename,
|
|
106
|
+
sheet_name=sheet_name,
|
|
107
|
+
column_spec=['Item Name', 'Symbol Name'],
|
|
108
|
+
data_type_name='Index'
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def load_DataGuide_EconomicData(filename: str, sheet_name: str | None = 'Economic') -> pd.DataFrame | None:
|
|
112
|
+
"""Economic 데이터를 로드합니다. (1-level column)"""
|
|
113
|
+
return _load_and_process_data(
|
|
114
|
+
filename=filename,
|
|
115
|
+
sheet_name=sheet_name,
|
|
116
|
+
column_spec=['Item Name'],
|
|
117
|
+
data_type_name='Economic'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def load_DataGuide_CrossSectionalData(filename: str) -> pd.DataFrame | None:
|
|
121
|
+
"""
|
|
122
|
+
지정된 파일명으로 CrossSectional 데이터를 찾아 로드하고 전처리합니다.
|
|
123
|
+
"""
|
|
124
|
+
file_path = find_file_recursive(filename)
|
|
125
|
+
|
|
126
|
+
if not file_path:
|
|
127
|
+
print(f"현재 폴더 및 하위 폴더에서 '{filename}' 파일을 찾을 수 없습니다. 🤷♂️")
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
print(f"파일 발견! '{file_path}' 파일을 로드합니다... 📂")
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
if file_path.endswith('.xlsx'):
|
|
134
|
+
df = pd.read_excel(file_path, sheet_name='CrossSectional', index_col=[1, 0])
|
|
135
|
+
elif file_path.endswith('.csv'):
|
|
136
|
+
df = pd.read_csv(file_path, encoding='cp949', index_col=[1, 0], low_memory=False)
|
|
137
|
+
else:
|
|
138
|
+
print(f"지원하지 않는 파일 형식입니다: {filename}")
|
|
139
|
+
return None
|
|
140
|
+
except Exception as e:
|
|
141
|
+
print(f"파일 로드 중 오류: {e}")
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
print("CrossSectional 데이터 후처리를 시작합니다... 🛠️")
|
|
145
|
+
header_tuple = ('Name', 'Symbol')
|
|
146
|
+
df.columns = df.loc[header_tuple]
|
|
147
|
+
header_location = df.index.get_loc(header_tuple)
|
|
148
|
+
df = df.iloc[header_location + 1:]
|
|
149
|
+
df.columns.names = ['Item Name']
|
|
150
|
+
df.index.names = ['Name', 'Symbol']
|
|
151
|
+
df.replace(',', '', regex=True, inplace=True)
|
|
152
|
+
|
|
153
|
+
print("float 타입으로 변환을 시도합니다...")
|
|
154
|
+
for col in df.columns:
|
|
155
|
+
try:
|
|
156
|
+
df[col] = df[col].astype(float)
|
|
157
|
+
except ValueError:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
print("처리 완료! ✨")
|
|
161
|
+
return df
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from tqdm import tqdm
|
|
4
|
+
def get_RiskReturnProfile(rebalencing_ret: pd.DataFrame, cash_return_daily_BenchmarkFrequency: pd.Series, BM: pd.Series | None = None):
|
|
5
|
+
"""
|
|
6
|
+
수익률 데이터를 받아 주요 성과 지표를 계산합니다.
|
|
7
|
+
IndexingError를 수정한 최종 벡터화 코드를 사용합니다.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def calculate_max_underwater_period(value_series: pd.Series) -> float:
|
|
11
|
+
"""단일 가치 시리즈에 대한 최대 손실 기간(연 단위)을 계산하는 내부 함수"""
|
|
12
|
+
if value_series.empty or value_series.isnull().all():
|
|
13
|
+
return 0.0
|
|
14
|
+
|
|
15
|
+
value_max = value_series.cummax()
|
|
16
|
+
underwater_series = value_max > value_series
|
|
17
|
+
|
|
18
|
+
if not underwater_series.any():
|
|
19
|
+
return 0.0
|
|
20
|
+
|
|
21
|
+
# 연속된 하락 기간(True)의 길이를 계산
|
|
22
|
+
underwater_groups = (underwater_series != underwater_series.shift()).cumsum()
|
|
23
|
+
underwater_lengths = underwater_series.groupby(underwater_groups).sum()
|
|
24
|
+
|
|
25
|
+
# ★★★ 오류 수정: 하락(True) 그룹만 필터링한 후 최대 기간을 찾음 ★★★
|
|
26
|
+
# 1. underwater_series가 True인 그룹 ID를 찾음
|
|
27
|
+
true_groups = underwater_groups[underwater_series]
|
|
28
|
+
# 2. 해당 그룹 ID에 해당하는 길이들 중에서 최대값을 찾음
|
|
29
|
+
max_period_days = underwater_lengths.loc[true_groups.unique()].max()
|
|
30
|
+
|
|
31
|
+
return round(max_period_days / 252, 1)
|
|
32
|
+
|
|
33
|
+
# --- 1. 전략(들)에 대한 공통 성과 지표 계산 ---
|
|
34
|
+
CAGR = (np.exp(np.log(rebalencing_ret + 1).mean() * 252) - 1).round(3) * 100
|
|
35
|
+
STD_annualized = (rebalencing_ret.std() * np.sqrt(252)).round(3) * 100
|
|
36
|
+
|
|
37
|
+
excess_ret = rebalencing_ret.subtract(cash_return_daily_BenchmarkFrequency.reindex(rebalencing_ret.index, method='ffill'), axis=0)
|
|
38
|
+
excess_ret_yearly = (np.exp(np.log(excess_ret + 1).mean() * 252) - 1)
|
|
39
|
+
Sharpe_Ratio = (excess_ret_yearly / (rebalencing_ret.std() * np.sqrt(252))).round(3)
|
|
40
|
+
|
|
41
|
+
pfl_value = (rebalencing_ret + 1).cumprod()
|
|
42
|
+
MDD = (pfl_value / pfl_value.cummax() - 1).min().round(3) * 100
|
|
43
|
+
MDD_date = (pfl_value / pfl_value.cummax() - 1).idxmin().astype(str).str[:7]
|
|
44
|
+
UnderWaterPeriod = pfl_value.apply(calculate_max_underwater_period)
|
|
45
|
+
|
|
46
|
+
# 기간별 수익률
|
|
47
|
+
ret_1M = ((rebalencing_ret.iloc[-21:] + 1).prod() - 1).round(3) * 100
|
|
48
|
+
ret_3M = ((rebalencing_ret.iloc[-21*3:] + 1).prod() - 1).round(3) * 100
|
|
49
|
+
ret_6M = ((rebalencing_ret.iloc[-21*6:] + 1).prod() - 1).round(3) * 100
|
|
50
|
+
ret_1Y = ((rebalencing_ret.iloc[-252:] + 1).prod() - 1).round(2) * 100
|
|
51
|
+
ret_3Y = ((rebalencing_ret.iloc[-252*3:] + 1).prod() - 1).round(2) * 100
|
|
52
|
+
|
|
53
|
+
metrics_list = [
|
|
54
|
+
CAGR, STD_annualized, Sharpe_Ratio, MDD, MDD_date, UnderWaterPeriod,
|
|
55
|
+
ret_1M, ret_3M, ret_6M, ret_1Y, ret_3Y
|
|
56
|
+
]
|
|
57
|
+
index_list = [
|
|
58
|
+
'CAGR(%)', 'STD_annualized(%)', 'Sharpe_Ratio', 'MDD(%)', 'MDD시점', 'UnderWaterPeriod(년)',
|
|
59
|
+
'1M Ret(%)', '3M Ret(%)', '6M Ret(%)', '1Y Ret(%)', '3Y Ret(%)'
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
matric = pd.DataFrame(metrics_list, index=index_list).T
|
|
63
|
+
|
|
64
|
+
if BM is not None:
|
|
65
|
+
aligned_ret, aligned_bm = rebalencing_ret.align(BM, join='inner', axis=0)
|
|
66
|
+
|
|
67
|
+
# BM 자체의 공통 성과 지표 계산
|
|
68
|
+
BM_CAGR = round(np.exp(np.log(aligned_bm + 1).mean() * 252) - 1, 3) * 100
|
|
69
|
+
BM_STD = round(aligned_bm.std() * np.sqrt(252), 3) * 100
|
|
70
|
+
bm_excess_ret = aligned_bm.subtract(cash_return_daily_BenchmarkFrequency.reindex(aligned_bm.index, method='ffill'))
|
|
71
|
+
bm_excess_ret_yearly = np.exp(np.log(bm_excess_ret + 1).mean() * 252) - 1
|
|
72
|
+
BM_Sharpe = round(bm_excess_ret_yearly / (aligned_bm.std() * np.sqrt(252)), 3)
|
|
73
|
+
bm_value = (aligned_bm + 1).cumprod()
|
|
74
|
+
BM_MDD = round((bm_value / bm_value.cummax() - 1).min(), 3) * 100
|
|
75
|
+
BM_MDD_date = (bm_value / bm_value.cummax() - 1).idxmin().strftime('%Y-%m')
|
|
76
|
+
BM_UnderWaterPeriod = calculate_max_underwater_period(bm_value)
|
|
77
|
+
BM_ret_1M = round((aligned_bm.iloc[-21:] + 1).prod() - 1, 3) * 100
|
|
78
|
+
BM_ret_3M = round((aligned_bm.iloc[-21*3:] + 1).prod() - 1, 3) * 100
|
|
79
|
+
BM_ret_6M = round((aligned_bm.iloc[-21*6:] + 1).prod() - 1, 3) * 100
|
|
80
|
+
BM_ret_1Y = round((aligned_bm.iloc[-252:] + 1).prod() - 1, 2) * 100
|
|
81
|
+
BM_ret_3Y = round((aligned_bm.iloc[-252*3:] + 1).prod() - 1, 2) * 100
|
|
82
|
+
|
|
83
|
+
# 전략의 BM 대비 상대 성과 지표 계산
|
|
84
|
+
excess_return_vs_bm = aligned_ret.subtract(aligned_bm, axis=0)
|
|
85
|
+
annualized_excess_return = (np.exp(np.log(excess_return_vs_bm + 1).mean() * 252) - 1)
|
|
86
|
+
tracking_error = excess_return_vs_bm.std() * np.sqrt(252)
|
|
87
|
+
information_ratio = (annualized_excess_return / tracking_error).round(3)
|
|
88
|
+
relative_value = (excess_return_vs_bm + 1).cumprod()
|
|
89
|
+
relative_drawdown = (relative_value / relative_value.cummax() - 1)
|
|
90
|
+
max_relative_drawdown = relative_drawdown.min().round(3) * 100
|
|
91
|
+
max_relative_drawdown_date = relative_drawdown.idxmin().astype(str).str[:7]
|
|
92
|
+
max_relative_underwater_duration = relative_value.apply(calculate_max_underwater_period)
|
|
93
|
+
|
|
94
|
+
# 최종 결과 테이블에 상대 성과 지표 컬럼 추가
|
|
95
|
+
matric['BM excess_return(%)']=round(annualized_excess_return*100,1)
|
|
96
|
+
matric['tracking_error(%)']=round(tracking_error*100,1)
|
|
97
|
+
matric['Information_Ratio'] = information_ratio
|
|
98
|
+
matric['BM대비최대손실(%)'] = max_relative_drawdown
|
|
99
|
+
matric['BM대비최대손실시점'] = max_relative_drawdown_date
|
|
100
|
+
matric['BM Max Underwater(년)'] = max_relative_underwater_duration
|
|
101
|
+
|
|
102
|
+
# BM 성과 행 생성 및 추가
|
|
103
|
+
bm_metrics_row = pd.Series(name='Benchmark', dtype=object)
|
|
104
|
+
bm_metrics_row['CAGR(%)'] = BM_CAGR
|
|
105
|
+
bm_metrics_row['STD_annualized(%)'] = BM_STD
|
|
106
|
+
bm_metrics_row['Sharpe_Ratio'] = BM_Sharpe
|
|
107
|
+
bm_metrics_row['MDD(%)'] = BM_MDD
|
|
108
|
+
bm_metrics_row['MDD시점'] = BM_MDD_date
|
|
109
|
+
bm_metrics_row['UnderWaterPeriod(년)'] = BM_UnderWaterPeriod
|
|
110
|
+
bm_metrics_row['1M Ret(%)'] = BM_ret_1M
|
|
111
|
+
bm_metrics_row['3M Ret(%)'] = BM_ret_3M
|
|
112
|
+
bm_metrics_row['6M Ret(%)'] = BM_ret_6M
|
|
113
|
+
bm_metrics_row['1Y Ret(%)'] = BM_ret_1Y
|
|
114
|
+
bm_metrics_row['3Y Ret(%)'] = BM_ret_3Y
|
|
115
|
+
bm_metrics_row['BM excess_return(%)'] = '-'
|
|
116
|
+
bm_metrics_row['tracking_error(%)'] = '-'
|
|
117
|
+
bm_metrics_row['Information_Ratio'] = '-'
|
|
118
|
+
bm_metrics_row['BM대비최대손실(%)'] = '-'
|
|
119
|
+
bm_metrics_row['BM대비최대손실시점'] = '-'
|
|
120
|
+
bm_metrics_row['BM Max Underwater(년)'] = '-'
|
|
121
|
+
|
|
122
|
+
matric = pd.concat([matric, bm_metrics_row.to_frame().T])
|
|
123
|
+
|
|
124
|
+
return matric
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: topquant-ksk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 기관 투자자를 위한 파이썬 퀀트 투자 백테스팅 및 분석 도구
|
|
5
|
+
Author-email: Your Name <your_email@example.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/your-username/topquantksk
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/your-username/topquantksk/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: pandas>=1.5.0
|
|
15
|
+
Requires-Dist: numpy>=1.20.0
|
|
16
|
+
Requires-Dist: tqdm
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/topquant_ksk/__init__.py
|
|
3
|
+
src/topquant_ksk/load_data.py
|
|
4
|
+
src/topquant_ksk/metrics.py
|
|
5
|
+
src/topquant_ksk.egg-info/PKG-INFO
|
|
6
|
+
src/topquant_ksk.egg-info/SOURCES.txt
|
|
7
|
+
src/topquant_ksk.egg-info/dependency_links.txt
|
|
8
|
+
src/topquant_ksk.egg-info/requires.txt
|
|
9
|
+
src/topquant_ksk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
topquant_ksk
|