plotguy 2.3.1__tar.gz → 2.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plotguy-2.3.1 → plotguy-2.3.2}/PKG-INFO +1 -1
- plotguy-2.3.2/plotguy/__init__.py +1 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy.egg-info/PKG-INFO +1 -1
- {plotguy-2.3.1 → plotguy-2.3.2}/setup.py +1 -1
- plotguy-2.3.1/plotguy/__init__.py +0 -563
- {plotguy-2.3.1 → plotguy-2.3.2}/README.md +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy/aggregate.py +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy/components.py +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy/equity_curves.py +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy/signals.py +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy.egg-info/SOURCES.txt +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy.egg-info/dependency_links.txt +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy.egg-info/requires.txt +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/plotguy.egg-info/top_level.txt +0 -0
- {plotguy-2.3.1 → plotguy-2.3.2}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .Database import *
|
|
@@ -1,563 +0,0 @@
|
|
|
1
|
-
import warnings
|
|
2
|
-
warnings.simplefilter("ignore", UserWarning)
|
|
3
|
-
#warnings.simplefilter("ignore", RuntimeWarning)
|
|
4
|
-
|
|
5
|
-
import datetime
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
import copy
|
|
9
|
-
import multiprocessing as mp
|
|
10
|
-
import time
|
|
11
|
-
import zlib
|
|
12
|
-
import requests
|
|
13
|
-
import pandas as pd
|
|
14
|
-
import numpy as np
|
|
15
|
-
import io
|
|
16
|
-
import re
|
|
17
|
-
|
|
18
|
-
from .equity_curves import *
|
|
19
|
-
from .signals import *
|
|
20
|
-
from .aggregate import *
|
|
21
|
-
from .components import *
|
|
22
|
-
import itertools
|
|
23
|
-
from bs4 import BeautifulSoup
|
|
24
|
-
|
|
25
|
-
def get_all_para_combination(para_dict, backtest_attribute, df_dict, sec_profile, manager_list, count_down=False):
|
|
26
|
-
|
|
27
|
-
key_to_keep = ['start_date',
|
|
28
|
-
'end_date',
|
|
29
|
-
'freq',
|
|
30
|
-
'market',
|
|
31
|
-
'sectype',
|
|
32
|
-
'initial_capital',
|
|
33
|
-
'data_folder',
|
|
34
|
-
'equity_curve_folder',
|
|
35
|
-
'secondary_data_folder',
|
|
36
|
-
'backtest_name',
|
|
37
|
-
'default_market_price',
|
|
38
|
-
'summary_mode']
|
|
39
|
-
|
|
40
|
-
for k in backtest_attribute.copy().keys():
|
|
41
|
-
if k not in key_to_keep:
|
|
42
|
-
del backtest_attribute[k]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
risk_free_rate = get_risk_free_rate(backtest_attribute['start_date'], backtest_attribute['end_date'])
|
|
46
|
-
|
|
47
|
-
freq = backtest_attribute['freq']
|
|
48
|
-
|
|
49
|
-
intraday = True if freq != '1D' else False
|
|
50
|
-
if intraday:
|
|
51
|
-
if not 'summary_mode' in backtest_attribute:
|
|
52
|
-
backtest_attribute['summary_mode'] = True
|
|
53
|
-
backtest_attribute['intraday'] = True
|
|
54
|
-
else:
|
|
55
|
-
backtest_attribute['summary_mode'] = False
|
|
56
|
-
backtest_attribute['intraday'] = False
|
|
57
|
-
|
|
58
|
-
backtest_attribute['risk_free_rate'] = risk_free_rate
|
|
59
|
-
|
|
60
|
-
para_keys = para_dict.keys()
|
|
61
|
-
para_values = para_dict.values()
|
|
62
|
-
para_list = list(itertools.product(*para_values))
|
|
63
|
-
para_keys_str = '|'.join(para_keys)
|
|
64
|
-
|
|
65
|
-
print('number of combination:', len(para_list))
|
|
66
|
-
|
|
67
|
-
all_combinations = list(itertools.product(*para_values))
|
|
68
|
-
all_para_combination = {}
|
|
69
|
-
|
|
70
|
-
for combination in all_combinations:
|
|
71
|
-
para_combination = dict(zip(para_keys, combination))
|
|
72
|
-
para_combination.update(backtest_attribute)
|
|
73
|
-
para_combination['manager_list'] = manager_list
|
|
74
|
-
|
|
75
|
-
concatenated_values = ''.join(map(str, para_combination.values())).encode('utf-8')
|
|
76
|
-
ref_code = zlib.crc32(concatenated_values)
|
|
77
|
-
|
|
78
|
-
code = para_combination['code']
|
|
79
|
-
para_combination['df'] = df_dict[code]
|
|
80
|
-
para_combination['sec_profile'] = sec_profile
|
|
81
|
-
para_combination['para_keys_str'] = para_keys_str
|
|
82
|
-
|
|
83
|
-
all_para_combination[ref_code] = para_combination
|
|
84
|
-
|
|
85
|
-
if count_down:
|
|
86
|
-
print('5 second countdown before running backtest()')
|
|
87
|
-
for i in range(5):
|
|
88
|
-
print(5 - i)
|
|
89
|
-
time.sleep(1)
|
|
90
|
-
print(0)
|
|
91
|
-
|
|
92
|
-
print(datetime.datetime.now().strftime('%H:%M:%S'), 'start running backtest()')
|
|
93
|
-
|
|
94
|
-
return all_para_combination
|
|
95
|
-
|
|
96
|
-
def generate_or_read_backtest_result(read_only, mp_mode, number_of_core, manager_list
|
|
97
|
-
,all_para_combination, backtest):
|
|
98
|
-
|
|
99
|
-
if read_only and os.path.isfile('backtest_result.parquet'):
|
|
100
|
-
backtest_result_df = pd.read_parquet('backtest_result.parquet')
|
|
101
|
-
else:
|
|
102
|
-
t1 = datetime.datetime.now()
|
|
103
|
-
if mp_mode:
|
|
104
|
-
pool = mp.Pool(processes=number_of_core)
|
|
105
|
-
pool.map(backtest, all_para_combination.items())
|
|
106
|
-
pool.close()
|
|
107
|
-
else:
|
|
108
|
-
for para_combination_item in all_para_combination.items():
|
|
109
|
-
backtest(para_combination_item)
|
|
110
|
-
|
|
111
|
-
backtest_result_df = pd.DataFrame(list(manager_list))
|
|
112
|
-
backtest_result_df = backtest_result_df.set_index('ref_code')
|
|
113
|
-
|
|
114
|
-
if os.path.isfile('backtest_result.parquet'):
|
|
115
|
-
old_backtest_result_df = pd.read_parquet('backtest_result.parquet')
|
|
116
|
-
backtest_result_df = pd.concat([backtest_result_df, old_backtest_result_df])
|
|
117
|
-
backtest_result_df = backtest_result_df[~backtest_result_df.index.duplicated(keep='last')]
|
|
118
|
-
|
|
119
|
-
backtest_result_df.to_parquet('backtest_result.parquet')
|
|
120
|
-
|
|
121
|
-
print('backtest time used:', (datetime.datetime.now() - t1).seconds, 'seconds')
|
|
122
|
-
|
|
123
|
-
backtest_result_df = backtest_result_df[backtest_result_df.index.isin(all_para_combination.keys())]
|
|
124
|
-
|
|
125
|
-
return backtest_result_df
|
|
126
|
-
|
|
127
|
-
def get_source_data_path(data_folder, code, freq):
|
|
128
|
-
file_path = os.path.join(data_folder, code + '_' + freq + '.parquet')
|
|
129
|
-
return file_path
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def save_backtest_result(df, para_combination_item):
|
|
133
|
-
|
|
134
|
-
ref_code, para_combination = para_combination_item
|
|
135
|
-
|
|
136
|
-
equity_curve_folder = para_combination['equity_curve_folder']
|
|
137
|
-
manager_list = para_combination['manager_list']
|
|
138
|
-
risk_free_rate = para_combination['risk_free_rate']
|
|
139
|
-
default_market_price = para_combination['default_market_price']
|
|
140
|
-
intraday = para_combination['intraday']
|
|
141
|
-
long_short = para_combination['long_short']
|
|
142
|
-
|
|
143
|
-
total_commission = df['commission'].sum()
|
|
144
|
-
|
|
145
|
-
df = df[[default_market_price, 'action', 'trd_side', 'equity_value']]
|
|
146
|
-
df = df.rename(columns={default_market_price: 'price'})
|
|
147
|
-
|
|
148
|
-
eqiuty_curve_save_path = os.path.join('', equity_curve_folder, f'{ref_code}.parquet')
|
|
149
|
-
##############################################################
|
|
150
|
-
|
|
151
|
-
if default_market_price == 'close':
|
|
152
|
-
agg_price = 'last'
|
|
153
|
-
elif default_market_price == 'open':
|
|
154
|
-
agg_price = 'first'
|
|
155
|
-
### for intraday ###
|
|
156
|
-
if intraday:
|
|
157
|
-
df_daily = df.resample('D').agg({'price': agg_price})
|
|
158
|
-
#df_daily = df.resample('D').agg({'price': agg_price, 'equity_value': agg_price})
|
|
159
|
-
df_daily = df_daily[df_daily['price'].notna()]
|
|
160
|
-
else:
|
|
161
|
-
df_daily = df
|
|
162
|
-
|
|
163
|
-
########### price result ###############
|
|
164
|
-
df_daily['running_max'] = df_daily['price'].cummax()
|
|
165
|
-
df_daily['dd_dollar'] = df_daily['running_max'] - df_daily['price']
|
|
166
|
-
df_daily['dd_pct'] = df_daily['dd_dollar'] / df_daily['running_max'] * 100
|
|
167
|
-
price_mdd_dollar = df_daily['dd_dollar'].max()
|
|
168
|
-
price_mdd_pct = df_daily['dd_pct'].max()
|
|
169
|
-
|
|
170
|
-
price_pct_series = df_daily['price'].pct_change().dropna()
|
|
171
|
-
price_net_profit = df_daily.at[df_daily.index[-1], 'price'] - df_daily.at[df_daily.index[0], 'price']
|
|
172
|
-
holding_period_day = (df_daily.index[-1].date() - df_daily.index[0].date()).days
|
|
173
|
-
|
|
174
|
-
price_return_on_capital = price_net_profit / df_daily.at[df_daily.index[0], 'price']
|
|
175
|
-
price_annualized_return = (np.sign(1 + price_return_on_capital) * np.abs(1 + price_return_on_capital)) ** (
|
|
176
|
-
365 / holding_period_day) - 1
|
|
177
|
-
price_annualized_std = price_pct_series.std() * math.sqrt(365)
|
|
178
|
-
price_annualized_sr = (
|
|
179
|
-
price_annualized_return - risk_free_rate / 100) / price_annualized_std if price_annualized_std != 0 else 0
|
|
180
|
-
price_net_profit_to_mdd = price_net_profit / price_mdd_dollar if price_mdd_dollar != 0 else 0
|
|
181
|
-
|
|
182
|
-
price_return_on_capital = round(100 * price_return_on_capital, 2)
|
|
183
|
-
price_annualized_return = round(100 * price_annualized_return, 2)
|
|
184
|
-
price_annualized_std = round(100 * price_annualized_std, 2)
|
|
185
|
-
price_annualized_sr = round(price_annualized_sr, 2)
|
|
186
|
-
price_net_profit_to_mdd = round(100 * price_net_profit_to_mdd, 2)
|
|
187
|
-
|
|
188
|
-
last_action = df['action'].iloc[-1]
|
|
189
|
-
if last_action == 'open':
|
|
190
|
-
df = df.drop(df.index[-1])
|
|
191
|
-
df_count = df[(df['trd_side'] == 'BUY') | (df['trd_side'] == 'SELL_SHORT')].copy()
|
|
192
|
-
|
|
193
|
-
if len(df_count) == 0:
|
|
194
|
-
num_of_trade = 0
|
|
195
|
-
equity_net_profit = 0
|
|
196
|
-
equity_return_on_capital = 0
|
|
197
|
-
equity_annualized_return = 0
|
|
198
|
-
equity_annualized_std = 0
|
|
199
|
-
equity_annualized_sr = 0
|
|
200
|
-
equity_net_profit_to_mdd = np.inf
|
|
201
|
-
equity_mdd_dollar = 0
|
|
202
|
-
equity_mdd_pct = 0
|
|
203
|
-
|
|
204
|
-
num_of_win = 0
|
|
205
|
-
num_of_trade = 0
|
|
206
|
-
win_rate = 0
|
|
207
|
-
yearly_stats_string = ''
|
|
208
|
-
|
|
209
|
-
cov_return = 0
|
|
210
|
-
cov_count = 0
|
|
211
|
-
total_commission = 0
|
|
212
|
-
|
|
213
|
-
start_year = df.index[0].year
|
|
214
|
-
end_year = df.index[-1].year
|
|
215
|
-
|
|
216
|
-
yearly_stats_string = ''
|
|
217
|
-
for year in range(start_year, end_year + 1):
|
|
218
|
-
yearly_stats_string += f'{year},year_trade_count:0.00,year_win_rate:00.00,year_return:-00.00|'
|
|
219
|
-
|
|
220
|
-
yearly_stats_string = yearly_stats_string[:-1]
|
|
221
|
-
correlation = 0
|
|
222
|
-
|
|
223
|
-
elif len(df_count) > 0:
|
|
224
|
-
|
|
225
|
-
########### by year count, win rate and return ###############
|
|
226
|
-
start_year = df.index[0].year
|
|
227
|
-
end_year = df.index[-1].year
|
|
228
|
-
|
|
229
|
-
num_of_trade = len(df_count)
|
|
230
|
-
df_count = pd.concat([df_count, df[df['trd_side'] != ''].tail(1)])
|
|
231
|
-
df_count['realized_pnl'] = df_count['equity_value'] - df_count['equity_value'].shift(1)
|
|
232
|
-
df_count['win_trade'] = df_count['realized_pnl'] >= 0
|
|
233
|
-
|
|
234
|
-
num_of_win = df_count['win_trade'].sum()
|
|
235
|
-
win_rate = round(100 * num_of_win / num_of_trade, 2)
|
|
236
|
-
|
|
237
|
-
yearly_stats = df_count.groupby(df_count.index.year).agg(
|
|
238
|
-
year_pnl=('realized_pnl', 'sum'),
|
|
239
|
-
year_win_count=('win_trade', 'sum'),
|
|
240
|
-
year_trade_count=('realized_pnl', 'count'),
|
|
241
|
-
year_start_equity_value=('equity_value', 'first'))
|
|
242
|
-
yearly_stats['year_return'] = 100 * (yearly_stats['year_pnl'] / yearly_stats['year_start_equity_value'])
|
|
243
|
-
yearly_stats['year_win_rate'] = 100 * (yearly_stats['year_win_count'] / yearly_stats['year_trade_count'])
|
|
244
|
-
|
|
245
|
-
all_years = range(start_year, end_year + 1)
|
|
246
|
-
current_years = yearly_stats.index
|
|
247
|
-
missing_years = [year for year in all_years if year not in current_years]
|
|
248
|
-
|
|
249
|
-
#if year_start_equity_value = df.at[df.index[0], 'equity_value'
|
|
250
|
-
missing_rows = pd.DataFrame(
|
|
251
|
-
{ "year_pnl": 0,
|
|
252
|
-
"year_win_count": 0,
|
|
253
|
-
"year_trade_count": 0,
|
|
254
|
-
"year_start_equity_value": 0,
|
|
255
|
-
"year_return": 0,
|
|
256
|
-
"year_win_rate": 0,
|
|
257
|
-
}, index=missing_years)
|
|
258
|
-
missing_rows.index.name = "datetime"
|
|
259
|
-
|
|
260
|
-
yearly_stats = pd.concat([yearly_stats, missing_rows]).sort_index()
|
|
261
|
-
|
|
262
|
-
cov_return = yearly_stats['year_return'].std() / yearly_stats['year_return'].mean() if yearly_stats['year_return'].mean() != 0 else 0
|
|
263
|
-
cov_count = yearly_stats['year_trade_count'].std() / yearly_stats['year_trade_count'].mean() if yearly_stats['year_trade_count'].mean() != 0 else 0
|
|
264
|
-
|
|
265
|
-
yearly_stats = yearly_stats.applymap(lambda x: f'{x:.2f}')
|
|
266
|
-
formatted_rows = yearly_stats.apply(
|
|
267
|
-
lambda
|
|
268
|
-
row: f"{row.name},{'year_trade_count'}:{row['year_trade_count']},{'year_win_rate'}:{row['year_win_rate']},{'year_return'}:{row['year_return']}",
|
|
269
|
-
axis=1)
|
|
270
|
-
|
|
271
|
-
yearly_stats_string = "|".join(formatted_rows)
|
|
272
|
-
|
|
273
|
-
######## resample to daily #############
|
|
274
|
-
if 'summary_mode' in para_combination:
|
|
275
|
-
if para_combination['summary_mode']:
|
|
276
|
-
eqiuty_curve_non_summary_save_path = os.path.join('', equity_curve_folder, f'{ref_code}_non-summary-intraday.parquet')
|
|
277
|
-
df.to_parquet(eqiuty_curve_non_summary_save_path)
|
|
278
|
-
|
|
279
|
-
########### equity value result ###############
|
|
280
|
-
|
|
281
|
-
### for intraday ###
|
|
282
|
-
if intraday:
|
|
283
|
-
df = df_count.resample('D').agg({'equity_value': 'last'})
|
|
284
|
-
df = pd.concat([df_daily, df], axis=1)
|
|
285
|
-
df = df[df['price'].notna()]
|
|
286
|
-
df['equity_value'] = df['equity_value'].ffill()
|
|
287
|
-
df['equity_value'] = df['equity_value'].bfill()
|
|
288
|
-
|
|
289
|
-
df['equity_value'] = df['equity_value'].astype(np.int32)
|
|
290
|
-
######################
|
|
291
|
-
|
|
292
|
-
df['running_max'] = df['equity_value'].cummax()
|
|
293
|
-
df['dd_dollar'] = df['running_max'] - df['equity_value']
|
|
294
|
-
df['dd_pct'] = df['dd_dollar'] / df['running_max'] * 100
|
|
295
|
-
equity_mdd_dollar = df['dd_dollar'].max()
|
|
296
|
-
equity_mdd_pct = df['dd_pct'].max()
|
|
297
|
-
|
|
298
|
-
holding_period_day = (df.index[-1].date() - df.index[0].date()).days
|
|
299
|
-
equity_pct_series = df['equity_value'].pct_change().dropna()
|
|
300
|
-
equity_net_profit = df.at[df.index[-1], 'equity_value'] - df.at[df.index[0], 'equity_value']
|
|
301
|
-
|
|
302
|
-
equity_return_on_capital = equity_net_profit / df.at[df.index[0], 'equity_value']
|
|
303
|
-
if 1 + equity_return_on_capital > 0:
|
|
304
|
-
equity_annualized_return = (np.sign(1 + equity_return_on_capital) * np.abs(1 + equity_return_on_capital)) ** (365 / holding_period_day) - 1
|
|
305
|
-
else:
|
|
306
|
-
equity_annualized_return = -1
|
|
307
|
-
|
|
308
|
-
equity_annualized_std = equity_pct_series.std() * math.sqrt(365)
|
|
309
|
-
equity_annualized_sr = (
|
|
310
|
-
equity_annualized_return - risk_free_rate / 100) / equity_annualized_std if equity_annualized_std != 0 else 0
|
|
311
|
-
equity_net_profit_to_mdd = equity_net_profit / equity_mdd_dollar if equity_mdd_dollar != 0 else 0
|
|
312
|
-
|
|
313
|
-
equity_return_on_capital = round(100 * equity_return_on_capital, 2)
|
|
314
|
-
equity_annualized_return = round(100 * equity_annualized_return, 2)
|
|
315
|
-
equity_annualized_std = round(100 * equity_annualized_std, 2)
|
|
316
|
-
equity_annualized_sr = round(equity_annualized_sr, 2)
|
|
317
|
-
equity_net_profit_to_mdd = round(100 * equity_net_profit_to_mdd, 2)
|
|
318
|
-
|
|
319
|
-
df['equity_return'] = df['equity_value'].pct_change() * 100
|
|
320
|
-
df['price_return'] = df['price'].pct_change() * 100
|
|
321
|
-
|
|
322
|
-
correlation = df['equity_return'].corr(df['price_return'])
|
|
323
|
-
|
|
324
|
-
if long_short == 'short':
|
|
325
|
-
return_on_capital_diff = equity_annualized_return - (-1) * price_annualized_return
|
|
326
|
-
correlation *= -1
|
|
327
|
-
else:
|
|
328
|
-
### consider both long and long_short ###
|
|
329
|
-
return_on_capital_diff = equity_annualized_return - price_annualized_return
|
|
330
|
-
|
|
331
|
-
if intraday:
|
|
332
|
-
df = df[['price', 'equity_value']]
|
|
333
|
-
elif not intraday:
|
|
334
|
-
df = df[['price', 'action', 'trd_side', 'equity_value']]
|
|
335
|
-
df.to_parquet(eqiuty_curve_save_path)
|
|
336
|
-
print('backtest is runnung...', eqiuty_curve_save_path)
|
|
337
|
-
|
|
338
|
-
####################################
|
|
339
|
-
del para_combination['df']
|
|
340
|
-
del para_combination['sec_profile']
|
|
341
|
-
del para_combination['manager_list']
|
|
342
|
-
|
|
343
|
-
cov_return = round(cov_return, 2)
|
|
344
|
-
cov_count = round(cov_count, 2)
|
|
345
|
-
total_commission = int(round(total_commission))
|
|
346
|
-
correlation = round(correlation, 3)
|
|
347
|
-
|
|
348
|
-
backtest_result_dict = {
|
|
349
|
-
'ref_code': ref_code,
|
|
350
|
-
'num_of_trade': num_of_trade,
|
|
351
|
-
|
|
352
|
-
'equity_net_profit': equity_net_profit,
|
|
353
|
-
'equity_return_on_capital': equity_return_on_capital,
|
|
354
|
-
'equity_annualized_return': equity_annualized_return,
|
|
355
|
-
'equity_annualized_std': equity_annualized_std,
|
|
356
|
-
'equity_annualized_sr': equity_annualized_sr,
|
|
357
|
-
'equity_net_profit_to_mdd': equity_net_profit_to_mdd,
|
|
358
|
-
'equity_mdd_dollar': equity_mdd_dollar,
|
|
359
|
-
'equity_mdd_pct': equity_mdd_pct,
|
|
360
|
-
|
|
361
|
-
'price_net_profit' : price_net_profit,
|
|
362
|
-
'price_return_on_capital': price_return_on_capital,
|
|
363
|
-
'price_annualized_return': price_annualized_return,
|
|
364
|
-
'price_annualized_std': price_annualized_std,
|
|
365
|
-
'price_annualized_sr': price_annualized_sr,
|
|
366
|
-
'price_net_profit_to_mdd': price_net_profit_to_mdd,
|
|
367
|
-
'price_mdd_dollar': price_mdd_dollar,
|
|
368
|
-
'price_mdd_pct': price_mdd_pct,
|
|
369
|
-
|
|
370
|
-
'return_on_capital_diff': return_on_capital_diff,
|
|
371
|
-
|
|
372
|
-
'num_of_win': num_of_win,
|
|
373
|
-
'num_of_trade': num_of_trade,
|
|
374
|
-
'win_rate': win_rate,
|
|
375
|
-
'yearly_stats_string': yearly_stats_string,
|
|
376
|
-
'cov_return': cov_return,
|
|
377
|
-
'cov_count': cov_count,
|
|
378
|
-
'correlation': correlation,
|
|
379
|
-
'total_commission': total_commission
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
para_combination.update(backtest_result_dict)
|
|
383
|
-
manager_list.append(para_combination)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
########################################################################################################
|
|
387
|
-
########################################################################################################
|
|
388
|
-
########################################################################################################
|
|
389
|
-
|
|
390
|
-
def get_risk_free_rate(start_date, end_date):
|
|
391
|
-
|
|
392
|
-
if '-' in start_date:
|
|
393
|
-
start_date_year = datetime.datetime.strptime(start_date, '%Y-%m-%d').year
|
|
394
|
-
end_date_year = datetime.datetime.strptime(end_date, '%Y-%m-%d').year
|
|
395
|
-
else:
|
|
396
|
-
start_date_year = datetime.datetime.strptime(start_date, '%Y%m%d').year
|
|
397
|
-
end_date_year = datetime.datetime.strptime(end_date, '%Y%m%d').year
|
|
398
|
-
|
|
399
|
-
try:
|
|
400
|
-
if end_date_year == start_date_year and end_date_year == datetime.datetime.now().year:
|
|
401
|
-
risk_free_rate = get_latest_fed_fund_rate()
|
|
402
|
-
else:
|
|
403
|
-
risk_free_rate = get_geometric_mean_of_yearly_rate(start_date_year, end_date_year)
|
|
404
|
-
except:
|
|
405
|
-
try:
|
|
406
|
-
risk_free_rate = get_geometric_mean_of_yearly_rate_backup(start_date_year, end_date_year)
|
|
407
|
-
except:
|
|
408
|
-
risk_free_rate = 2 # if network error, set rate to 2 %
|
|
409
|
-
print('Network error. Risk free rate: {:.2f} %'.format(risk_free_rate))
|
|
410
|
-
|
|
411
|
-
return risk_free_rate
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
# def plot_signal_analysis(py_filename, output_folder, start_date, end_date, para_dict, signal_settings):
|
|
415
|
-
# app = signals.Signals(py_filename, output_folder, start_date, end_date, para_dict, generate_backtest_output_path,
|
|
416
|
-
# signal_settings)
|
|
417
|
-
#
|
|
418
|
-
# return app
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
def plot(mode, backtest_result_df = None, number_of_curves=20, filter_list= []):
|
|
422
|
-
|
|
423
|
-
if mode == 'equity_curves':
|
|
424
|
-
if len(filter_list) > 0: filter_list = convert_filter_list(filter_list)
|
|
425
|
-
app = equity_curves.Plot(backtest_result_df, number_of_curves, filter_list)
|
|
426
|
-
|
|
427
|
-
if mode == 'aggregate':
|
|
428
|
-
app = aggregate.Aggregate()
|
|
429
|
-
|
|
430
|
-
return app
|
|
431
|
-
|
|
432
|
-
def convert_filter_list(filter_list):
|
|
433
|
-
'''
|
|
434
|
-
filter string as follows
|
|
435
|
-
equity_return_on_capital eg: 50% --> 50
|
|
436
|
-
equity_annualized_return eg: 50% --> 50
|
|
437
|
-
return_on_capital_diff eg: 50% --> 50
|
|
438
|
-
equity_annualized_sr eg: 0.9
|
|
439
|
-
equity_mdd_pct eg: 50% --> 50
|
|
440
|
-
num_of_trade eg: 10
|
|
441
|
-
cov_count eg: 1
|
|
442
|
-
cov_return eg: 1
|
|
443
|
-
correlation eg: 0.5
|
|
444
|
-
win_rate eg: 50% --> 50
|
|
445
|
-
'''
|
|
446
|
-
|
|
447
|
-
for i in range(len(filter_list)):
|
|
448
|
-
filter = filter_list[i]
|
|
449
|
-
pattern = r'([a-zA-Z_]+)([<>=]+)(-?\d+\.?\d*)'
|
|
450
|
-
match = re.match(pattern, filter)
|
|
451
|
-
|
|
452
|
-
filter_list[i] = [match.group(1), match.group(2), match.group(3)]
|
|
453
|
-
|
|
454
|
-
return filter_list
|
|
455
|
-
|
|
456
|
-
def get_latest_fed_fund_rate():
|
|
457
|
-
url = "https://fred.stlouisfed.org/series/FEDFUNDS"
|
|
458
|
-
page = requests.get(url)
|
|
459
|
-
soup = BeautifulSoup(page.content, "html.parser")
|
|
460
|
-
|
|
461
|
-
fed_funds_rate = soup.find("span", class_="series-meta-observation-value").text
|
|
462
|
-
print("Latest Federal Funds Rate:", fed_funds_rate, '%')
|
|
463
|
-
# fed_funds_rate = float(fed_funds_rate) / 100
|
|
464
|
-
fed_funds_rate = round(float(fed_funds_rate), 2)
|
|
465
|
-
return fed_funds_rate
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
def get_geometric_mean_of_yearly_rate(start_year, end_year):
|
|
469
|
-
url = "https://fred.stlouisfed.org/graph/fredgraph.csv?id=DTB3"
|
|
470
|
-
response = requests.get(url, timeout=10)
|
|
471
|
-
data = response.text.split("\n")[:-1]
|
|
472
|
-
data = [row.split(",") for row in data]
|
|
473
|
-
df = pd.DataFrame(data[1:], columns=data[0])
|
|
474
|
-
df.columns = ["date", "risk_free_rate"]
|
|
475
|
-
df["date"] = pd.to_datetime(df["date"])
|
|
476
|
-
df["risk_free_rate"] = pd.to_numeric(df["risk_free_rate"], errors='coerce')
|
|
477
|
-
df.dropna(subset=['risk_free_rate'], inplace=True)
|
|
478
|
-
print('Got Federal Funds Rate from stlouisfed.org')
|
|
479
|
-
|
|
480
|
-
risk_free_rate_history_yearly = df.resample("A", on="date").mean()
|
|
481
|
-
risk_free_rate_history_yearly = risk_free_rate_history_yearly.round(3)
|
|
482
|
-
|
|
483
|
-
# show only start between start_year and end_year
|
|
484
|
-
risk_free_rate_history_yearly = risk_free_rate_history_yearly[
|
|
485
|
-
risk_free_rate_history_yearly.index.year >= start_year]
|
|
486
|
-
risk_free_rate_history_yearly = risk_free_rate_history_yearly[risk_free_rate_history_yearly.index.year <= end_year]
|
|
487
|
-
|
|
488
|
-
fed_fund_rate_geometric_mean = np.exp(np.log(risk_free_rate_history_yearly["risk_free_rate"]).mean())
|
|
489
|
-
fed_fund_rate_geometric_mean = round(fed_fund_rate_geometric_mean, 2)
|
|
490
|
-
print("Federal Funds Rate Geometric mean from {} to {}: {} %".format(start_year, end_year,
|
|
491
|
-
fed_fund_rate_geometric_mean))
|
|
492
|
-
|
|
493
|
-
return fed_fund_rate_geometric_mean
|
|
494
|
-
|
|
495
|
-
def get_geometric_mean_of_yearly_rate_backup(start_year, end_year):
|
|
496
|
-
url = '/get_fred'
|
|
497
|
-
my_headers = {'token': '3f52e8c4-8c49-4c9a-8c57-1e4e9f6b0a40'}
|
|
498
|
-
my_main_url = "http://120.26.40.125:8080"
|
|
499
|
-
response = requests.get(f'{my_main_url}{url}', headers=my_headers)
|
|
500
|
-
data_str = response.content.decode('utf-8')
|
|
501
|
-
data_io = io.StringIO(data_str)
|
|
502
|
-
df = pd.read_csv(data_io)
|
|
503
|
-
df = df.rename(columns={'value' : 'risk_free_rate'})
|
|
504
|
-
df["date"] = pd.to_datetime(df["date"])
|
|
505
|
-
df.dropna(subset=['risk_free_rate'], inplace=True)
|
|
506
|
-
print('Got Federal Funds Rate from backup source')
|
|
507
|
-
|
|
508
|
-
risk_free_rate_history_yearly = df.resample("A", on="date").mean()
|
|
509
|
-
risk_free_rate_history_yearly = risk_free_rate_history_yearly.round(3)
|
|
510
|
-
|
|
511
|
-
# show only start between start_year and end_year
|
|
512
|
-
risk_free_rate_history_yearly = risk_free_rate_history_yearly[
|
|
513
|
-
risk_free_rate_history_yearly.index.year >= start_year]
|
|
514
|
-
risk_free_rate_history_yearly = risk_free_rate_history_yearly[risk_free_rate_history_yearly.index.year <= end_year]
|
|
515
|
-
|
|
516
|
-
fed_fund_rate_geometric_mean = np.exp(np.log(risk_free_rate_history_yearly["risk_free_rate"]).mean())
|
|
517
|
-
fed_fund_rate_geometric_mean = round(fed_fund_rate_geometric_mean, 2)
|
|
518
|
-
print("Federal Funds Rate Geometric mean from {} to {}: {} %".format(start_year, end_year,
|
|
519
|
-
fed_fund_rate_geometric_mean))
|
|
520
|
-
|
|
521
|
-
return fed_fund_rate_geometric_mean
|
|
522
|
-
|
|
523
|
-
def su_table_converter():
|
|
524
|
-
all_str_dir = []
|
|
525
|
-
for folder_name in os.listdir():
|
|
526
|
-
if os.path.isfile(os.path.join(folder_name, 'saved_strategies.parquet')):
|
|
527
|
-
all_str_dir.append(folder_name)
|
|
528
|
-
|
|
529
|
-
su_dict = {'backtest_name': [], 'code': [], 'freq': [], 'para_items_str': [], 'initial_capital': [],
|
|
530
|
-
'equity_mdd_pct': []}
|
|
531
|
-
|
|
532
|
-
for str_dir in all_str_dir:
|
|
533
|
-
saved_strategies_path = os.path.join(str_dir, 'saved_strategies.parquet')
|
|
534
|
-
saved_strategies_df = pd.read_parquet(saved_strategies_path)
|
|
535
|
-
saved_strategies_df = saved_strategies_df.reset_index(drop=True)
|
|
536
|
-
|
|
537
|
-
if len(saved_strategies_df.columns) > 1:
|
|
538
|
-
for i, row in saved_strategies_df.iterrows():
|
|
539
|
-
backtest_name = row['backtest_name']
|
|
540
|
-
freq = row['freq']
|
|
541
|
-
para_keys_str = row['para_keys_str']
|
|
542
|
-
equity_mdd_pct = row['equity_mdd_pct']
|
|
543
|
-
initial_capital = row['initial_capital']
|
|
544
|
-
|
|
545
|
-
para_items_str = ''
|
|
546
|
-
para_keys_str_list = para_keys_str.split('|')
|
|
547
|
-
for para_key in para_keys_str_list:
|
|
548
|
-
para_value = str(saved_strategies_df.at[i, para_key])
|
|
549
|
-
if para_key == 'code':
|
|
550
|
-
code = para_value
|
|
551
|
-
else:
|
|
552
|
-
para_items_str += para_key + '=' + para_value + '|'
|
|
553
|
-
para_items_str = para_items_str[:-1]
|
|
554
|
-
|
|
555
|
-
su_dict['backtest_name'].append(backtest_name)
|
|
556
|
-
su_dict['code'].append(code)
|
|
557
|
-
su_dict['freq'].append(freq)
|
|
558
|
-
su_dict['para_items_str'].append(para_items_str)
|
|
559
|
-
su_dict['equity_mdd_pct'].append(equity_mdd_pct)
|
|
560
|
-
su_dict['initial_capital'].append(initial_capital)
|
|
561
|
-
|
|
562
|
-
su_table = pd.DataFrame(su_dict)
|
|
563
|
-
return su_table
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|