Skip to content

Commit

Permalink
Fixed issue with EventStudy
Browse files Browse the repository at this point in the history
  • Loading branch information
saeedamen committed Mar 21, 2021
1 parent 6d53c02 commit 6b139fd
Show file tree
Hide file tree
Showing 7 changed files with 668 additions and 461 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ In finmarketpy/examples you will find several examples, including some simple tr

# finmarketpy log

* 21 Mar 2021
* Fixed issue with EventStudy
* Refactored TradingModel when constructing weights
* 22 Feb 2021
* Fix bug when plotting IR of strategies
* 16 Feb 2021
Expand Down
935 changes: 520 additions & 415 deletions finmarketpy/backtest/backtestengine.py

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion finmarketpy/backtest/backtestrequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def __init__(self):

# output parameters for backtest (should we add returns statistics on legends, write CSVs with returns etc.)
self.__plot_start = None
self.__plot_finish = None
self.__calc_stats = True
self.__write_csv = False
self.__write_csv_pnl = False
Expand All @@ -43,6 +44,8 @@ def __init__(self):

self.__tech_params = TechParams()

self.__portfolio_weight_construction = None

# default parameters for portfolio level vol adjustment
self.__portfolio_vol_adjust = False
self.__portfolio_vol_period_shift = 0
Expand Down Expand Up @@ -101,6 +104,14 @@ def plot_start(self): return self.__plot_start

@plot_start.setter
def plot_start(self, plot_start): self.__plot_start = plot_start

@property
def plot_finish(self):
return self.__plot_finish

@plot_finish.setter
def plot_finish(self, plot_finish):
self.__plot_finish = plot_finish

@property
def calc_stats(self): return self.__calc_stats
Expand Down Expand Up @@ -139,8 +150,17 @@ def trading_field(self): return self.__trading_field

@trading_field.setter
def trading_field(self, trading_field): self.__trading_field = trading_field

@property
def portfolio_weight_construction(self):
return self.__portfolio_weight_construction

##### properties for portfolio level volatility adjustment
@portfolio_weight_construction.setter
def portfolio_weight_construction(self, portfolio_weight_construction):
self.__portfolio_weight_construction = portfolio_weight_construction

##### Properties for portfolio level volatility adjustment

@property
def portfolio_vol_adjust(self): return self.__portfolio_vol_adjust

Expand Down
122 changes: 91 additions & 31 deletions finmarketpy/economics/eventstudy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#

import math

import numpy as np
from pandas.tseries.offsets import CustomBusinessDay

from findatapy.timeseries import Calculations, Timezone, Filter, Calendar
Expand All @@ -28,8 +28,7 @@ def __init__(self):
return

def get_economic_event_ret_over_custom_event_day(self, data_frame_in, event_dates, name, event, start, end,
lagged=False,
NYC_cutoff=10):
lagged=False, NYC_cutoff=10):

filter = Filter()
event_dates = filter.filter_time_series_by_date(start, end, event_dates)
Expand All @@ -46,7 +45,7 @@ def get_economic_event_ret_over_custom_event_day(self, data_frame_in, event_date

event_dates = calendar.floor_date(event_dates)

# realised is traditionally on later day eg. 3rd Jan realised ON is 2nd-3rd Jan realised
# Realised is traditionally on later day eg. 3rd Jan realised ON is 2nd-3rd Jan realised
# so if Fed meeting is on 2nd Jan later, then we need realised labelled on 3rd (so minus a day)
# implied_vol expires on next day eg. 3rd Jan implied_vol ON is 3rd-4th Jan implied_vol

Expand All @@ -62,9 +61,32 @@ def get_economic_event_ret_over_custom_event_day(self, data_frame_in, event_date

return data_frame_events

def get_daily_moves_over_custom_event(self, data_frame_rets, ef_time_frame, vol=False,
day_start=20, days=20, day_offset=0, create_index=False,
resample=False, cumsum=True, adj_cumsum_zero_point=False,
adj_zero_point=2):

return self.get_intraday_moves_over_custom_event(data_frame_rets, ef_time_frame, vol=vol,
minute_start=day_start, mins=days, min_offset=day_offset,
create_index=create_index, resample=resample, freq='days',
cumsum=cumsum, adj_cumsum_zero_point=adj_cumsum_zero_point,
adj_zero_point=adj_zero_point)

def get_weekly_moves_over_custom_event(self, data_frame_rets, ef_time_frame, vol=False,
day_start=20, days=20, day_offset=0, create_index=False,
resample=False, cumsum=True, adj_cumsum_zero_point=False,
adj_zero_point=2):

return self.get_intraday_moves_over_custom_event(data_frame_rets, ef_time_frame, vol=vol,
minute_start=day_start, mins=days, min_offset=day_offset,
create_index=create_index, resample=resample, freq='weeks',
cumsum=cumsum, adj_cumsum_zero_point=adj_cumsum_zero_point,
adj_zero_point=adj_zero_point)

def get_intraday_moves_over_custom_event(self, data_frame_rets, ef_time_frame, vol=False,
minute_start=5, mins=3 * 60, min_offset=0, create_index=False,
resample=False, freq='minutes', cumsum=True):
resample=False, freq='minutes', cumsum=True, adj_cumsum_zero_point=False,
adj_zero_point=2):

filter = Filter()

Expand All @@ -78,45 +100,75 @@ def get_intraday_moves_over_custom_event(self, data_frame_rets, ef_time_frame, v
ann_factor = 252 * 1440
elif freq == 'days':
ef_time = ef_time_frame.index.normalize()
ef_time_start = ef_time - timedelta(days=minute_start)
ef_time_end = ef_time + timedelta(days=mins)
ef_time_start = ef_time - pandas.tseries.offsets.BusinessDay() * minute_start
ef_time_end = ef_time + pandas.tseries.offsets.BusinessDay() * mins
ann_factor = 252
elif freq == 'weeks':
ef_time = ef_time_frame.index.normalize()
ef_time_start = ef_time - pandas.tseries.offsets.Week() * minute_start
ef_time_end = ef_time + pandas.tseries.offsets.Week() * mins
ann_factor = 52

ords = range(-minute_start + min_offset, mins + min_offset)
ords = list(range(-minute_start + min_offset, mins + min_offset))
lst_ords = list(ords)

# all data needs to be equally spaced
# All data needs to be equally spaced
if resample:
# make sure time series is properly sampled at 1 min intervals
data_frame_rets = data_frame_rets.resample('1min')
data_frame_rets = data_frame_rets.fillna(value=0)
data_frame_rets = filter.remove_out_FX_out_of_hours(data_frame_rets)

data_frame_rets['Ind'] = numpy.nan
if freq == 'minutes':
data_frame_rets = data_frame_rets.resample('1min').last()
data_frame_rets = data_frame_rets.fillna(value=0)
data_frame_rets = filter.remove_out_FX_out_of_hours(data_frame_rets)
elif freq == 'daily':
data_frame_rets = data_frame_rets.resample('B').last()
data_frame_rets = data_frame_rets.fillna(value=0)
elif freq == 'daily':
data_frame_rets = data_frame_rets.resample('W').last()
data_frame_rets = data_frame_rets.fillna(value=0)

# data_frame_rets['Ind'] = numpy.nan

start_index = data_frame_rets.index.searchsorted(ef_time_start)
finish_index = data_frame_rets.index.searchsorted(ef_time_end)

# not all observation windows will be same length (eg. last one?)
# Not all observation windows will be same length (eg. last one?)

# # fill the indices which represent minutes
# # TODO vectorise this!
# for i in range(0, len(ef_time_frame.index)):
# try:
# data_frame_rets['Ind'][start_index[i]:finish_index[i]] = ords
# except:
# data_frame_rets['Ind'][start_index[i]:finish_index[i]] = ords[0:(finish_index[i] - start_index[i])]
#
# data_frame_rets['Rel'] = numpy.nan
#
# # Set the release dates
# data_frame_rets['Rel'][start_index] = ef_time # set entry points
# data_frame_rets['Rel'][finish_index + 1] = numpy.zeros(len(start_index)) # set exit points
# data_frame_rets['Rel'] = data_frame_rets['Rel'].fillna(method='pad') # fill down signals
#
# data_frame_rets = data_frame_rets[pandas.notnull(data_frame_rets['Ind'])] # get rid of other
#
# data_frame = data_frame_rets.pivot(index='Ind',
# columns='Rel', values=data_frame_rets.columns[0])

data_frame = pandas.DataFrame(index=ords, columns=ef_time_frame.index)

# fill the indices which represent minutes
# TODO vectorise this!
for i in range(0, len(ef_time_frame.index)):
try:
data_frame_rets['Ind'][start_index[i]:finish_index[i]] = ords
except:
data_frame_rets['Ind'][start_index[i]:finish_index[i]] = ords[0:(finish_index[i] - start_index[i])]
#try:
vals = data_frame_rets.iloc[start_index[i]:finish_index[i]].values

data_frame_rets['Rel'] = numpy.nan
print(vals.size)

# Set the release dates
data_frame_rets['Rel'][start_index] = ef_time # set entry points
data_frame_rets['Rel'][finish_index + 1] = numpy.zeros(len(start_index)) # set exit points
data_frame_rets['Rel'] = data_frame_rets['Rel'].fillna(method='pad') # fill down signals
if len(vals) < len(lst_ords):
extend = np.zeros((len(lst_ords) - len(vals), 1)) * np.nan
vals = np.append(vals, extend)

data_frame_rets = data_frame_rets[pandas.notnull(data_frame_rets['Ind'])] # get rid of other
data_frame[ef_time_frame.index[i]] = vals

data_frame = data_frame_rets.pivot(index='Ind',
columns='Rel', values=data_frame_rets.columns[0])
#except:
# data_frame_rets['Ind'][start_index[i]:finish_index[i]] = ords[0:(finish_index[i] - start_index[i])]

data_frame.index.names = [None]

Expand All @@ -126,11 +178,19 @@ def get_intraday_moves_over_custom_event(self, data_frame_rets, ef_time_frame, v
data_frame = calculations.create_mult_index(data_frame)
else:
if vol is True:
# annualise (if vol)
# Annualise (if vol)
data_frame = data_frame.rolling(center=False, window=5).std() * math.sqrt(ann_factor)
elif cumsum:

data_frame = data_frame.cumsum()

# Adjust DataFrame so zero point shows zero returns
if adj_cumsum_zero_point:
ind = abs(minute_start) - adj_zero_point

for i, c in enumerate(data_frame.columns):
data_frame[c] = data_frame[c] - data_frame[c].values[ind]

return data_frame

def get_surprise_against_intraday_moves_over_custom_event(
Expand Down Expand Up @@ -426,7 +486,7 @@ def get_daily_moves_over_event(self):
# TODO
pass

# return only US events etc. by dates
# Return only US events etc. by dates
def get_intraday_moves_over_event(self, data_frame_rets, cross, event_fx, event_name, start, end, vol, mins=3 * 60,
min_offset=0, create_index=False, resample=False, freq='minutes'):

Expand Down
39 changes: 29 additions & 10 deletions finmarketpy/economics/techindicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ class TechIndicator(object):
"""

def __init__(self):
self.logger = LoggerManager().getLogger(__name__)
self._techind = None
self._signal = None

Expand All @@ -52,6 +51,9 @@ def create_tech_ind(
name,
tech_params,
data_frame_non_nan_early=None):

logger = LoggerManager().getLogger(__name__)

self._signal = None
self._techind = None

Expand All @@ -66,7 +68,7 @@ def create_tech_ind(
if name == "SMA":

if (data_frame_non_nan_early is not None):
# calculate the lagged sum of the n-1 point
# Calculate the lagged sum of the n-1 point
if pd.__version__ < '0.17':
rolling_sum = pd.rolling_sum(
data_frame.shift(1).rolling,
Expand Down Expand Up @@ -120,7 +122,7 @@ def create_tech_ind(
x + " EMA" for x in data_frame.columns.values]

elif name == "ROC":

# Rate of change
if (data_frame_non_nan_early is not None):
self._techind = data_frame_early / \
data_frame.shift(tech_params.roc_period) - 1
Expand All @@ -139,6 +141,7 @@ def create_tech_ind(
x + " ROC" for x in data_frame.columns.values]

elif name == "polarity":
# If data is positive or negative
self._techind = data_frame

narray = np.where(self._techind > 0, 1, -1)
Expand All @@ -151,6 +154,7 @@ def create_tech_ind(
x + " Polarity" for x in data_frame.columns.values]

elif name == "SMA2":
# Double moving average
sma = data_frame.rolling(
window=tech_params.sma_period,
center=False).mean()
Expand All @@ -171,6 +175,8 @@ def create_tech_ind(
self._techind = pd.concat([sma, sma2], axis=1)

elif name in ['RSI']:
# Relative Strength Index

# delta = data_frame.diff()
#
# dUp, dDown = delta.copy(), delta.copy()
Expand Down Expand Up @@ -239,7 +245,7 @@ def create_tech_ind(
x + " RSI Signal" for x in data_frame.columns.values]

elif name in ["BB"]:
# calcuate Bollinger bands
# Calcuate Bollinger bands
mid = data_frame.rolling(
center=False, window=tech_params.bb_period).mean()
mid.columns = [x + " BB Mid" for x in data_frame.columns.values]
Expand All @@ -257,7 +263,7 @@ def create_tech_ind(
index=mid.index,
columns=data_frame.columns)

# calculate signals
# Calculate signals (buy/sell)
signal = data_frame.copy()

buys = signal > upper
Expand All @@ -281,7 +287,7 @@ def create_tech_ind(

self._techind = pd.concat([lower, mid, upper], axis=1)
elif name == "long-only":
# have +1 signals only
# Have +1 signals only
self._techind = data_frame # the technical indicator is just "prices"

narray = np.ones((len(data_frame.index), len(data_frame.columns)))
Expand All @@ -293,22 +299,35 @@ def create_tech_ind(
self._techind.columns = [
x + " Long Only" for x in data_frame.columns.values]

elif name == "short-only":
# Have -1 signals only
self._techind = data_frame # the technical indicator is just "prices"

narray = np.ones((len(data_frame.index), len(data_frame.columns)))

self._signal = pd.DataFrame(index=data_frame.index, data=narray)
self._signal.columns = [
x + " Short Only Signal" for x in data_frame.columns.values]

self._techind.columns = [
x + " Short Only" for x in data_frame.columns.values]

elif name == "ATR":
# get all the asset names (assume we have names 'close', 'low', 'high' in the Data)
# Get all the asset names (assume we have names 'close', 'low', 'high' in the Data)
# keep ordering of assets
asset_name = list(OrderedDict.fromkeys(
[x.split('.')[0] for x in data_frame.columns]))

df = []

# can improve the performance of this if vectorise more!
# Can improve the performance of this if vectorise more!
for a in asset_name:

close = [a + '.close']
low = [a + '.low']
high = [a + '.high']

# if we don't fill NaNs, we need to remove those rows and then
# If we don't fill NaNs, we need to remove those rows and then
# calculate the ATR
if not(tech_params.fillna):
data_frame_short = data_frame[[close[0], low[0], high[0]]]
Expand Down Expand Up @@ -425,7 +444,7 @@ def get_signal(self):
##########################################################################


class TechParams:
class TechParams(object):
"""Holds parameters for calculation of technical indicators.
"""
Expand Down
Loading

0 comments on commit 6b139fd

Please sign in to comment.