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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ from .metrics import *
2
+ from .load_data import *
@@ -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,3 @@
1
+ pandas>=1.5.0
2
+ numpy>=1.20.0
3
+ tqdm
@@ -0,0 +1 @@
1
+ topquant_ksk