Yahoo Finance のヒストリカルデータを SP500_daily.csv という名前でダウンロードしてお使いください
$ ls
lm.py SP500_daily.csv
$ python lm.py
Yahoo Finance のヒストリカルデータを SP500_daily.csv という名前でダウンロードしてお使いください
$ ls
lm.py SP500_daily.csv
$ python lm.py
import queue | |
import pandas as pd | |
import matplotlib.pyplot as plt | |
def yahoo_finance_open_close(filename): | |
df = pd.read_csv(filename) | |
series = [] | |
for _, item in df.iterrows(): | |
series.append({'open': item['Open'], 'close': item['Close']}) | |
return series | |
class Tsumitate: | |
def __init__(self, per_day): | |
self.per_day = per_day | |
def run(self, chart, ignore_fee=False): | |
fee_rate = 0.00495 # SBI証券の手数料率 0.495% (消費税込) | |
if ignore_fee: | |
fee_rate = 0.0 | |
fee_max = 22 # SBI証券の上限手数料 $22 (消費税込) | |
value_series = [] | |
n_stocks = 0.0 | |
cash = 0.0 | |
for day in chart: | |
cash += self.per_day | |
# 買付時手数料を考慮 | |
exec_amount = cash - min(cash * fee_rate, fee_max) | |
fee = min(exec_amount * fee_rate, fee_max) | |
n_stocks += exec_amount / day['open'] | |
cash -= exec_amount + fee | |
value_series.append(n_stocks * day['close'] + cash) | |
return value_series | |
class LM: | |
def __init__(self, per_day, ma_len): | |
self.per_day = per_day # 一日に積み立てる額 | |
self.n_stocks = 0.0 # 口数 (端数計算面倒なので float にしてしまう) | |
self.avg_cost = 0.0 # 平均取得金額 | |
self.cash = 0 # 現金 | |
self.ma = queue.Queue(maxsize=ma_len) # ma_len は移動平均線の長さ | |
# レバレッジ基準 (以下ベース) となるチャートと、レバレッジをかけたチャートを与えて、 | |
# 資産額の推移を返す | |
def run(self, base_chart, leveraged_chart): | |
value_series = [] | |
order = 'buy' # buy | sell. 最初は buy (積み立て含む) | |
for base, leveraged in zip(base_chart, leveraged_chart): | |
# 最初に現金に積み立て額を追加 | |
self.cash += self.per_day | |
# レバ始値で売買 | |
self.exec_order(leveraged['open'], order) | |
# 次の売買はベース終値で判断 | |
order = self.next_order(base['close']) | |
# 現在の資産額はレバ終値で判断 | |
value_series.append(self.value(leveraged['close'])) | |
return value_series | |
# 次回の売買をどうするかを返す | |
def next_order(self, price): | |
if self.ma.full(): | |
self.ma.get() | |
self.ma.put(price) | |
# 移動平均線を上回れば買い、下回れば売り | |
if price >= sum(list(self.ma.queue)) / self.ma.maxsize: | |
return 'buy' | |
else: | |
return 'sell' | |
else: | |
# とりあえず日数がたまるまでは積み立て | |
self.ma.put(price) | |
return 'buy' | |
# 注文を実行 | |
# order='buy' : 全力買い | |
# order='sell' : 全力売り | |
def exec_order(self, price, order): | |
tax_rate = 0.20315 # ドル計算だけどとりあえず日本の譲渡益税率 20.315% | |
fee_rate = 0.00495 # SBI証券の手数料率 0.495% (消費税込) | |
fee_max = 22 # SBI証券の上限手数料 $22 (消費税込) | |
if order == 'buy': | |
# 手数料を考慮して買う (絶対現金がマイナスにならない程度の雑な計算) | |
exec_amount = self.cash - min(self.cash * fee_rate, fee_max) | |
# 端数処理面倒なので口数は float。誤差は気にしない | |
quantity = exec_amount / price | |
# 必ず self.cash > exec_amount になるので現金は足りる | |
fee = min(exec_amount * fee_rate, fee_max) | |
# 平均取得金額の更新 | |
self.avg_cost = (self.avg_cost * self.n_stocks + | |
price * quantity) / (self.n_stocks + quantity) | |
self.n_stocks += quantity | |
self.cash -= exec_amount + fee | |
elif order == 'sell': | |
# 売却益の計算 | |
exec_amount = self.n_stocks * price # 約定金額 | |
tax = max((exec_amount - self.n_stocks * self.avg_cost) | |
* tax_rate, 0) # 利益が出ていなかったら税金は0 | |
fee = min(exec_amount * fee_rate, fee_max) | |
self.cash += exec_amount - tax - fee | |
self.n_stocks = 0 # 全売却 | |
self.avg_cost = 0 # 全売却 | |
else: | |
return | |
def value(self, unit): | |
return self.n_stocks * unit + self.cash | |
def to_leveraged(base_chart, x=3): | |
chart = [] | |
prev = base_chart[0] # 数を合わせるため、レバレッジ版の初日は1倍にしてしまう | |
for base in base_chart: | |
# 前日の終値からの騰落率の x 倍 (レバレッジの計算方法これであってる?) | |
open = base['open'] * (1 + (base['open'] / prev['close'] - 1) * x) | |
close = base['close'] * (1 + (base['close'] / prev['close'] - 1) * x) | |
chart.append({'open': open, 'close': close}) | |
return chart | |
base_series = yahoo_finance_open_close('./SP500_daily.csv')[-200 * 10:] | |
leveraged_series = to_leveraged(base_series) | |
cash_series = [{'open': 1, 'close': 1} for _ in range(len(base_series))] | |
# 毎日10ドル積み立て | |
lm50_chart = LM(per_day=10, ma_len=50).run(base_series, leveraged_series) | |
lm100_chart = LM(per_day=10, ma_len=100).run(base_series, leveraged_series) | |
lm200_chart = LM(per_day=10, ma_len=200).run(base_series, leveraged_series) | |
lm400_chart = LM(per_day=10, ma_len=400).run(base_series, leveraged_series) | |
cash_chart = Tsumitate(per_day=10).run(cash_series, ignore_fee=True) | |
base_chart = Tsumitate(per_day=10).run(base_series) | |
lv_chart = Tsumitate(per_day=10).run(leveraged_series) | |
plt.plot(lm50_chart, label='L-M (50)') | |
plt.plot(lm100_chart, label='L-M (100)') | |
plt.plot(lm200_chart, label='L-M (200)') | |
plt.plot(lm400_chart, label='L-M (400)') | |
plt.plot(cash_chart, label='Cash') | |
plt.plot(base_chart, label='Buy and Hold (base)') | |
plt.plot(lv_chart, label='Buy and Hold (leveraged)') | |
plt.legend() | |
plt.show() |