Created
October 25, 2024 01:18
-
-
Save partrita/470ce741d450f16ecb61f94894f904c0 to your computer and use it in GitHub Desktop.
주식투자 백테스팅 코드
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import FinanceDataReader as fdr | |
import pandas as pd | |
import numpy as np | |
import matplotlib.pyplot as plt | |
from datetime import datetime, timedelta | |
from typing import List, Dict, Any, Tuple | |
plt.rcParams['font.family'] = 'Malgun Gothic' | |
plt.rcParams['axes.unicode_minus'] = False | |
def get_stock_data(ticker: str, start_date: datetime, end_date: datetime) -> pd.DataFrame: | |
return fdr.DataReader(ticker, start_date, end_date) | |
def dollar_cost_averaging(df: pd.DataFrame, investment_amount: float, frequency: str) -> pd.DataFrame: | |
df = df.resample(frequency).last() | |
df['Shares'] = investment_amount / df['Close'] | |
df['Total_Shares'] = df['Shares'].cumsum() | |
df['Total_Investment'] = investment_amount * np.arange(1, len(df) + 1) | |
df['Portfolio_Value'] = df['Total_Shares'] * df['Close'] | |
df['Average_Cost'] = df['Total_Investment'] / df['Total_Shares'] | |
return df | |
def value_averaging(df: pd.DataFrame, target_growth: float, initial_investment: float, frequency: str) -> pd.DataFrame: | |
df = df.resample(frequency).last() | |
target_value = initial_investment | |
total_shares = 0.0 | |
total_investment = 0.0 | |
investments: List[Dict[str, Any]] = [] | |
for index, row in df.iterrows(): | |
target_value += target_growth | |
current_value = total_shares * row['Close'] | |
investment_needed = target_value - current_value | |
if investment_needed > 0: | |
shares_to_buy = investment_needed / row['Close'] | |
total_shares += shares_to_buy | |
total_investment += investment_needed | |
elif investment_needed < 0: | |
shares_to_sell = min(abs(investment_needed) / row['Close'], total_shares) | |
total_shares -= shares_to_sell | |
total_investment -= shares_to_sell * row['Close'] | |
investments.append({ | |
'Date': index, | |
'Target_Value': target_value, | |
'Current_Value': current_value, | |
'Investment_Needed': investment_needed, | |
'Total_Shares': total_shares, | |
'Total_Investment': total_investment, | |
'Portfolio_Value': total_shares * row['Close'], | |
'Average_Cost': total_investment / total_shares if total_shares > 0 else 0 | |
}) | |
return pd.DataFrame(investments).set_index('Date') | |
def calculate_annual_roi(df: pd.DataFrame) -> float: | |
years = (df.index[-1] - df.index[0]).days / 365.25 | |
total_roi = (df['Portfolio_Value'].iloc[-1] - df['Total_Investment'].iloc[-1]) / df['Total_Investment'].iloc[-1] | |
return (1 + total_roi) ** (1 / years) - 1 | |
def backtest_strategies(ticker: str, ticker_name: str, start_date: datetime, end_date: datetime, | |
investment_amount: float, frequencies: List[str], target_growth: float) -> Tuple[pd.DataFrame, pd.DataFrame]: | |
stock_data = get_stock_data(ticker, start_date, end_date) | |
results: List[Dict[str, Any]] = [] | |
avg_cost_data: Dict[str, pd.Series] = {} | |
for freq in frequencies: | |
dca_df = dollar_cost_averaging(stock_data.copy(), investment_amount, freq) | |
va_df = value_averaging(stock_data.copy(), target_growth, investment_amount, freq) | |
dca_annual_roi = calculate_annual_roi(dca_df) | |
va_annual_roi = calculate_annual_roi(va_df) | |
results.extend([ | |
{'Strategy': f'DCA {freq}', 'Annual ROI (%)': dca_annual_roi * 100}, | |
{'Strategy': f'VA {freq}', 'Annual ROI (%)': va_annual_roi * 100} | |
]) | |
avg_cost_data[f'DCA {freq}'] = dca_df['Average_Cost'] | |
avg_cost_data[f'VA {freq}'] = va_df['Average_Cost'] | |
results_df = pd.DataFrame(results).sort_values('Annual ROI (%)', ascending=True) | |
avg_cost_df = pd.DataFrame(avg_cost_data) | |
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(6, 8), gridspec_kw={'height_ratios': [2, 2, 1]}) | |
fig.suptitle('Stock Price, Investment Strategy Comparison, and Average Cost', fontsize=16) | |
ax1.plot(stock_data.index, stock_data['Close'], label='Stock Price', color='gray') | |
ax1.set_title(ticker_name) | |
ax1.set_ylabel('Price') | |
ax1.legend() | |
ax1.grid(True) | |
for strategy in avg_cost_df.columns: | |
ax2.plot(avg_cost_df.index, avg_cost_df[strategy], label=strategy) | |
ax2.set_title('Average Cost per Share Over Time') | |
ax2.set_xlabel('Date') | |
ax2.set_ylabel('Average Cost') | |
ax2.legend() | |
ax2.grid(True) | |
ax3.bar(results_df['Strategy'], results_df['Annual ROI (%)']) | |
ax3.set_title('Annual ROI Comparison of Different Investment Strategies') | |
ax3.set_xlabel('') | |
ax3.set_ylabel('Annual ROI (%)') | |
ax3.tick_params(axis='x', rotation=45) | |
ax3.grid(False) | |
plt.tight_layout() | |
plt.show() | |
return results_df, avg_cost_df | |
if __name__ == "__main__": | |
ticker = "379810" # KODEX 미국나스닥100TR | |
ticker_name = "KODEX 미국나스닥100TR" | |
start_date = datetime.now() - timedelta(days=365*5) # 5년 전 | |
end_date = datetime.now() | |
investment_amount = 500_000 # 매 투자시 500000 투자 | |
frequencies = ['ME'] # 월간(ME), 분기별 투자(QE) | |
target_growth_per_period = 500_000 # 각 기간마다 목표 성장 금액 | |
results_table, avg_cost_table = backtest_strategies(ticker, ticker_name, start_date, end_date, investment_amount, frequencies, target_growth_per_period) | |
print("Results Table:") | |
print(results_table) | |
print("\nAverage Cost Table:") | |
print(avg_cost_table) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment