Skip to content

Instantly share code, notes, and snippets.

@partrita
Created October 25, 2024 01:18
Show Gist options
  • Save partrita/470ce741d450f16ecb61f94894f904c0 to your computer and use it in GitHub Desktop.
Save partrita/470ce741d450f16ecb61f94894f904c0 to your computer and use it in GitHub Desktop.
주식투자 백테스팅 코드
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