-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimulate.py
133 lines (105 loc) · 3.65 KB
/
simulate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from __future__ import annotations
import argparse
import yfinance as yf
import yaml
import pandas as pd
import os
from dataclasses import dataclass
from typing import Callable
@dataclass
class Holding:
symbol: str
price: float
amount: float
@dataclass
class Asset:
symbol: str
percent: int
@dataclass
class Configuration:
name: str
frequency: str
assets : list[Asset]
strategy: Callable[[Portfolio, pd.DataFrame, pd.DataFrame], pd.Series]
@dataclass
class Portfolio:
cash: float
holdings : list[Holding]
def get_symbol_prices(symbol, period):
ticker = yf.Ticker(symbol)
df = ticker.history(period)["Close"]
df.name = symbol
return df
def get_asset_prices(symbol_list):
data = []
for ticker in symbol_list:
history = get_symbol_prices(ticker, "max")
data.append(history)
# Using inner join to reduce index to only existing dates for every asset
df = pd.concat(data, join="inner", axis = 1)
# Assure only business days are in the index
df = df.asfreq('B')
return df.dropna()
def calc_momentum_score(asset_prices):
df3 = asset_prices.pct_change(90)
df6 = asset_prices.pct_change(180)
df12 = asset_prices.pct_change(360)
mom = (df3 + df6 + df12) / 3
return mom.dropna()
def trend_3612(portfolio, asset_prices, scores):
max_asset_index = scores.nlargest(3).index
date = scores.name
closes = asset_prices.loc[date]
# sell all
if len(portfolio.holdings) > 0:
for asset in portfolio.holdings:
portfolio.cash += asset.amount * closes[asset.symbol]
portfolio.holdings.clear()
portfolio_value = portfolio.cash
invest = portfolio.cash / 3
# buy new
for asset in max_asset_index:
portfolio.cash -= invest
a = Holding(asset,
closes[asset],
invest / closes[asset])
portfolio.holdings.append(a)
return pd.Series([max_asset_index.to_list(), portfolio_value], index=["Assets", "Portfolio Value"])
def simulate(portfolio, config, asset_prices, momentum_score, freq):
score = momentum_score.groupby(pd.Grouper(freq=freq)).tail(1)
tmp = score.apply(lambda scores: config.strategy(portfolio, asset_prices, scores), axis = 1)
return tmp
#read_strategy_configuration
#construct_strategy
def read_configuration(filename):
default_calculations = { }
default_calculations["trend_3612"] = trend_3612
f = open(filename)
yaml_conf = yaml.safe_load(f)
strat = yaml_conf["strategy"]
freq = yaml_conf["frequency"]
assets = []
for symbol, values in yaml_conf["assets"].items():
if "percent" in values:
percent = values["percent"]
else:
percent = 0
assets.append(Asset(symbol, percent))
return Configuration(strat, freq, assets, default_calculations[strat])
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Simulates momentum strategy using given portfolio")
parser.add_argument("filenames", nargs="+", help="filename(s) to yaml configurations of strategies")
args = parser.parse_args()
try:
for filename in args.filenames:
config = read_configuration(filename)
symbols = [asset.symbol for asset in config.assets]
prices = get_asset_prices(symbols)
mom = calc_momentum_score(prices)
p = Portfolio(10000, [])
tmp = simulate(p, config, prices, mom, config.frequency)
name = os.path.splitext(filename)[0]
with open(f'simulate_{name}.txt', 'w') as f:
f.write(tmp.to_csv())
except (FileNotFoundError) as e:
print(e)