Skip to content

Commit

Permalink
Vectorized operations
Browse files Browse the repository at this point in the history
Migrated operations to vectorized mode
now we use ndarrays & pandas instead of lists (improves speed so we can use more stocks)
  • Loading branch information
silvavn committed Sep 11, 2020
1 parent 4a4ceeb commit 72c609a
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 166 deletions.
71 changes: 40 additions & 31 deletions backtester.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ class BackTester:
"""
Backtester module that does both backward and forward testing for our portfolios.
"""

def __init__(self):
print("\n--# Backtester has been initialized")

def calculate_percentage_change(self, old, new):
def price_delta(self, prices):
"""
Percentage change
"""
return ((new - old) * 100) / old

return ((prices - prices.shift()) * 100 / prices.shift())[1:]

def portfolio_weight_manager(self, weight, is_long_only):
"""
Expand All @@ -46,73 +48,80 @@ def back_test(self, symbol_names, portfolio_weights_dictionary, portfolio_data_d
"""

# Get market returns during the backtesting time
historical_price_market = list(historical_price_market["Close"])
market_returns = [self.calculate_percentage_change(historical_price_market[i - 1], historical_price_market[i]) for i in range(1, len(historical_price_market))]
historical_prices = historical_price_market["Close"]
market_returns = self.price_delta(historical_prices)
market_returns_cumulative = np.cumsum(market_returns)

# Get invidiual returns for each stock in our portfolio
normal_returns_matrix = []
for symbol in symbol_names:
symbol_historical_prices = list(portfolio_data_dictionary[symbol]["historical_prices"]["Close"])
symbol_historical_returns = [self.calculate_percentage_change(symbol_historical_prices[i - 1], symbol_historical_prices[i]) for i in range(1, len(symbol_historical_prices))]
symbol_historical_prices = portfolio_data_dictionary[symbol]["historical"]["Close"]
symbol_historical_returns = self.price_delta(
symbol_historical_prices)
normal_returns_matrix.append(symbol_historical_returns)

# Get portfolio returns
normal_returns_matrix = np.array(normal_returns_matrix).transpose()
portfolio_weights_vector = np.array([self.portfolio_weight_manager(portfolio_weights_dictionary[symbol], is_long_only) for symbol in portfolio_weights_dictionary]).transpose()
portfolio_returns = np.dot(normal_returns_matrix, portfolio_weights_vector)
portfolio_weights_vector = np.array([self.portfolio_weight_manager(
portfolio_weights_dictionary[symbol], is_long_only) for symbol in portfolio_weights_dictionary]).transpose()
portfolio_returns = np.dot(
normal_returns_matrix, portfolio_weights_vector)
portfolio_returns_cumulative = np.cumsum(portfolio_returns)

# Plot returns
x = np.arange(len(portfolio_returns_cumulative))
plt.plot(x, portfolio_returns_cumulative, linewidth = 2.0, label = strategy_name)
plt.axhline(y = 0, linestyle = 'dotted', alpha = 0.3, color = 'black')
plt.plot(x, portfolio_returns_cumulative,
linewidth=2.0, label=strategy_name)
plt.axhline(y=0, linestyle='dotted', alpha=0.3, color='black')
if market_chart:
x = np.arange(len(market_returns_cumulative))
plt.plot(x, market_returns_cumulative, linewidth = 2.0, color = '#282828', label = 'Market Index', linestyle = '--')
plt.plot(x, market_returns_cumulative, linewidth=2.0,
color='#282828', label='Market Index', linestyle='--')

# Plotting styles
plt.title("Backtest Results", fontsize = 14)
plt.xlabel("Bars (Time Sorted)", fontsize = 14)
plt.ylabel("Cumulative Percentage Return", fontsize = 14)
plt.xticks(fontsize = 14)
plt.yticks(fontsize = 14)
plt.title("Backtest Results", fontsize=14)
plt.xlabel("Bars (Time Sorted)", fontsize=14)
plt.ylabel("Cumulative Percentage Return", fontsize=14)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)

def future_test(self, symbol_names, portfolio_weights_dictionary, portfolio_data_dictionary, future_price_market, is_long_only, market_chart, strategy_name):
"""
Main future test function. If future data is available i.e is_test is set to 1 and future_bars set to > 0, this takes in the portfolio weights and compares the portfolio returns with a market index of your choice in the future.
"""

# Get future prices
print(future_price_market)
future_price_market = [item[4] for item in list(future_price_market)]
market_returns = [self.calculate_percentage_change(future_price_market[i - 1], future_price_market[i]) for i in range(1, len(future_price_market))]
market_returns = self.price_delta(future_price_market["Close"])
market_returns_cumulative = np.cumsum(market_returns)

# Get invidiual returns for each stock in our portfolio
normal_returns_matrix = []
for symbol in symbol_names:
symbol_historical_prices = [item[4] for item in list(portfolio_data_dictionary[symbol]["future_prices"])]
symbol_historical_returns = [self.calculate_percentage_change(symbol_historical_prices[i - 1], symbol_historical_prices[i]) for i in range(1, len(symbol_historical_prices))]
symbol_historical_prices = portfolio_data_dictionary[symbol]["future"]["Close"]
symbol_historical_returns = self.price_delta(symbol_historical_prices)
normal_returns_matrix.append(symbol_historical_returns)

# Get portfolio returns
normal_returns_matrix = np.array(normal_returns_matrix).transpose()
portfolio_weights_vector = np.array([self.portfolio_weight_manager(portfolio_weights_dictionary[symbol], is_long_only) for symbol in portfolio_weights_dictionary]).transpose()
portfolio_returns = np.dot(normal_returns_matrix, portfolio_weights_vector)
portfolio_weights_vector = np.array([self.portfolio_weight_manager(
portfolio_weights_dictionary[symbol], is_long_only) for symbol in portfolio_weights_dictionary]).transpose()
portfolio_returns = np.dot(
normal_returns_matrix, portfolio_weights_vector)
portfolio_returns_cumulative = np.cumsum(portfolio_returns)

# Plot
x = np.arange(len(portfolio_returns_cumulative))
plt.axhline(y = 0, linestyle = 'dotted', alpha = 0.3, color = 'black')
plt.plot(x, portfolio_returns_cumulative, linewidth = 2.0, label = strategy_name)
plt.axhline(y=0, linestyle='dotted', alpha=0.3, color='black')
plt.plot(x, portfolio_returns_cumulative,
linewidth=2.0, label=strategy_name)
if market_chart:
x = np.arange(len(market_returns_cumulative))
plt.plot(x, market_returns_cumulative, linewidth = 2.0, color = '#282828', label = 'Market Index', linestyle = '--')
plt.plot(x, market_returns_cumulative, linewidth=2.0,
color='#282828', label='Market Index', linestyle='--')

# Plotting styles
plt.title("Future Test Results", fontsize = 14)
plt.xlabel("Bars (Time Sorted)", fontsize = 14)
plt.ylabel("Cumulative Percentage Return", fontsize = 14)
plt.xticks(fontsize = 14)
plt.yticks(fontsize = 14)
plt.title("Future Test Results", fontsize=14)
plt.xlabel("Bars (Time Sorted)", fontsize=14)
plt.ylabel("Cumulative Percentage Return", fontsize=14)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
36 changes: 20 additions & 16 deletions eiten.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@

class Eiten:
def __init__(self, args):
plt.style.use('seaborn-white')
plt.rc('grid', linestyle="dotted", color='#a0a0a0')
plt.rcParams['axes.edgecolor'] = "#04383F"
plt.rcParams['figure.figsize'] = (12, 6)


print("\n--* Eiten has been initialized...")
self.args = args
Expand All @@ -36,11 +33,11 @@ def __init__(self, args):

print('\n')

def calculate_percentage_change(self, old, new):
def price_delta(self, prices):
"""
Calculate percentage change
"""
return ((new - old) * 100) / old
return ((prices - prices.shift()) * 100 / prices.shift())[1:]

def create_returns(self, historical_price_info):
"""
Expand All @@ -51,18 +48,16 @@ def create_returns(self, historical_price_info):
returns_matrix = []
returns_matrix_percentages = []
predicted_return_vectors = []

for i in range(0, len(historical_price_info)):
close_prices = list(historical_price_info[i]["Close"])
log_returns = [math.log(close_prices[i] / close_prices[i - 1])
for i in range(1, len(close_prices))]
percentage_returns = [self.calculate_percentage_change(
close_prices[i - 1], close_prices[i]) for i in range(1, len(close_prices))]
close_prices = historical_price_info[i]["Close"]
log_returns = np.log((close_prices / close_prices.shift())[1:])
percentage_returns = self.price_delta(close_prices)

total_data = len(close_prices)
total_data = close_prices.shape[0]

# Expected returns in future. We can either use historical returns as future returns on try to simulate future returns and take the mean. For simulation, you can modify the functions in simulator to use here.
future_expected_returns = np.mean([(self.calculate_percentage_change(close_prices[i - 1], close_prices[i])) / (
total_data - i) for i in range(1, len(close_prices))]) # More focus on recent returns
future_expected_returns = np.mean((self.price_delta(close_prices)) / (total_data - i)) # More focus on recent returns

# Add to matrices
returns_matrix.append(log_returns)
Expand Down Expand Up @@ -92,8 +87,8 @@ def load_data(self):
historical_price_info, future_prices = [], []
for symbol in symbol_names:
historical_price_info.append(
self.data_dictionary[symbol]["historical_prices"])
future_prices.append(self.data_dictionary[symbol]["future_prices"])
self.data_dictionary[symbol]["historical"])
future_prices.append(self.data_dictionary[symbol]["future"])

# Get return matrices and vectors
predicted_return_vectors, returns_matrix, returns_matrix_percentages = self.create_returns(
Expand Down Expand Up @@ -238,6 +233,10 @@ def draw_plot(self, filename="output/graph.png"):
Draw plots
"""
# Styling for plots
plt.style.use('seaborn-white')
plt.rc('grid', linestyle="dotted", color='#a0a0a0')
plt.rcParams['axes.edgecolor'] = "#04383F"
plt.rcParams['figure.figsize'] = (12, 6)

plt.grid()
plt.legend(fontsize=14)
Expand All @@ -248,6 +247,11 @@ def draw_plot(self, filename="output/graph.png"):
plt.show()

def print_and_plot_portfolio_weights(self, weights_dictionary: dict, strategy, plot_num: int) -> None:
plt.style.use('seaborn-white')
plt.rc('grid', linestyle="dotted", color='#a0a0a0')
plt.rcParams['axes.edgecolor'] = "#04383F"
plt.rcParams['figure.figsize'] = (12, 6)

print("\n-------- Weights for %s --------" % strategy)
symbols = list(sorted(weights_dictionary.keys()))
symbol_weights = []
Expand Down

0 comments on commit 72c609a

Please sign in to comment.