Блог им. AlekseyManin
Решил в Python протестировать стратегию «Купил и Держи». Причем захотелось посмотреть какой будет доход если инвестировать ежемесячно равные суммы в течении определенного периода.
Немного об интерпретации результатов:
1. Дивиденды не учитываются. Учитывается только курсовой рост.
2. Доход по стратегии показан напротив года начала инвестирования, хотя фактически он соответствует дате начала инвестирования + заданный период.
3. На картинках QQQ, $100 ежемесячно в течении 10 лет

Если кому интересно, вот ссылка на pastebin
Error: [31m The Parser function of type «linkTool» is not defined. Define your custom parser functions as: [34mhttps://github.com/pavittarx/editorjs-html#extend-for-custom-blocks [0mОй, что-то ссылка покраснела (
Ну тогда код. Можете сами тикер поменять (тянет данные с Yahoo), даты, сумму… Ну думаю разберетесь, там всё просто.
<code>from datetime import date
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
class Bar:
def __init__(self):
self.year = 0
self.month = 0
self.day = 0
self.open = 0
self.high = 0
self.low = 0
self.close = 0
def run(df, start_date, end_date, increment, date_increment, fees):
""" Основная функция """
df = df[start_date:end_date]
cur_bar = Bar()
prev_bar = Bar()
increment_is_completed = False # Приращение депо в этом месяце (False-не было, True-было)
depo = 0 # Брокерский счет в валюте
sum_depo = 0 # Общая сумма инвестирования (переведено на брокерский счет)
portfolio = 0 # Количество бумаг в портфеле
# Перебор строк DF, как приход нового бара
for row in df.itertuples(): # Перебор строк DF (аналог появления нового бара)
# Обновляем информацию текущего бара
cur_bar.day = date.timetuple(row[0])[2]
cur_bar.month = date.timetuple(row[0])[1]
cur_bar.year = date.timetuple(row[0])[0]
cur_bar.open = row[1]
cur_bar.high = row[2]
cur_bar.low = row[3]
cur_bar.close = row[4]
if cur_bar.month != prev_bar.month: # Если месяц предыдущего бара не равен месяцу текущего бара
increment_is_completed = False # Признак инвестирования в текущем месяце в False
elif cur_bar.month == prev_bar.month and not increment_is_completed and cur_bar.day >= date_increment:
depo += increment # Увеличение брокерского счета на сумму ежемесячных инвестиций
sum_depo += increment # Увеличение общей суммы инвестиций
increment_is_completed = True # Признак инвестирования в текущем месяце в True
while cur_bar.open + cur_bar.open * fees < depo: # Если сумма на счете позволяет купить фин. инструмент
depo -= cur_bar.open + cur_bar.open * fees # Списываем с брокерского счета сумму на покупку
portfolio += 1 # Увеличиваем количество бумаг в портфеле
# Обновление информации предыдущего бара
prev_bar.day = cur_bar.day
prev_bar.month = cur_bar.month
prev_bar.year = cur_bar.year
prev_bar.open = cur_bar.open
prev_bar.high = cur_bar.high
prev_bar.low = cur_bar.low
prev_bar.close = cur_bar.close
return sum_depo, portfolio * cur_bar.close, depo, portfolio, cur_bar.close
if __name__ == '__main__':
# BRK-B IJH SPY AAPL QQQ
ticker = 'QQQ' # Тикер финансового инструмента как он отображается на Yahoo Finance
increment: int = 100 # Сумма ежемесячного инвестирования
date_increment: int = 15 # Дата пополнения(число месяца)
year_invest: int = 10 # Количество лет инвестирования
fees = 0.0006 # 0.05% комиссия брокера ВТБ + 0.01% комиссия биржи
df_ticker = yf.download(ticker) # Загрузка данных с Yahoo Finance
df_ticker = df_ticker.drop(columns=['Adj Close', 'Volume']) # Удаляем ненужные колонки
df_rez_ticker = pd.DataFrame()
for year in range(1993, 2022):
start_date: date = date(year, 1, 1) # Дата старта инвестирования(год, месяц, число)
end_date = date(start_date.year + year_invest, start_date.month, start_date.day)
rez = run(df_ticker, start_date, end_date, increment, date_increment, fees)
dohod = rez[1] - rez[0] + rez[2]
# print(f'Год начала инвестирования: {year}\n'
# f'Всего инвестировано: {rez[0]}\n'
# f'Конечная стоимость портфеля: {rez[1]:.2f}\n'
# f'Остаток денег на брокерском счете: {rez[2]:.2f}\n'
# f'Количество бумаг в портфеле: {rez[3]}\n'
# f'Текущая стоимость одной бумаги: {rez[4]:.2f}\n'
# f'Доход: {dohod:.2f}\n'
# f'---------------------------------------------')
# new_row = {'Год начала': year, 'Инвестировано': rez[0], 'Стоим портфеля': rez[1], 'Деньги': rez[2],
# 'Бумаги': rez[3], 'Стоимость бумаги': rez[4], 'Доход': dohod}
new_row = {'Год начала': str(year),
'Инвестировано': round(rez[0], 2),
'Стоим портфеля': round(rez[1], 2),
'Доход': round(dohod, 2)
}
# append row to the dataframe
df_rez_ticker = df_rez_ticker.append(new_row, ignore_index=True)
pd.set_option('display.max_columns', None) # Сброс ограничений на число столбцов
print(df_rez_ticker)
# Построение графика
index = df_rez_ticker['Год начала']
values = df_rez_ticker['Доход']
plt.title(f'Доход за {year_invest} лет ежемесячного инвестирования по ${increment} в инструмент {ticker}')
plt.bar(index, values, label='Доход')
plt.xticks(index, df_rez_ticker['Год начала'])
plt.xlabel("Год начала инвестирования")
plt.ylabel("Доход в $")
plt.legend(loc=2)
plt.show()
</code>



