Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved organization and removed redundancy #7

Merged
merged 10 commits into from
Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

*.pyc
*.png
25 changes: 25 additions & 0 deletions argchecker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class ArgChecker:
"""
Argument checker
"""

def __init__(self, args):
print("Checking arguments...")
self.check_arguments(args)

def check_arguments(self, args):
granularity_constraints_list = [1, 5, 10, 15, 30, 60, 3600]
granularity_constraints_list_string = ''.join(
str(value) + "," for value in granularity_constraints_list).strip(",")

assert not(args.data_granularity_minutes not in granularity_constraints_list), "You can only choose the following values for 'data_granularity_minutes' argument -> %s\nExiting now..." % granularity_constraints_list_string

assert not(args.is_test == 1 and args.future_bars <
2), "You want to test but the future bars are less than 2. That does not give us enough data to test the model properly. Please use a value larger than 2.\nExiting now..."

assert not(args.history_to_use != "all" and int(args.history_to_use_int) <
args.future_bars), "It is a good idea to use more history and less future bars. Please change these two values and try again.\nExiting now..."

args.market_index = str(args.market_index).upper()
if args.history_to_use != "all":
args.history_to_use = int(args.history_to_use)
195 changes: 98 additions & 97 deletions backtester.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,100 +18,101 @@


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):
"""
Percentage change
"""
return ((new - old) * 100) / old

def portfolio_weight_manager(self, weight, is_long_only):
"""
Manage portfolio weights. If portfolio is long only, set the negative weights to zero.
"""
if is_long_only == 1:
weight = max(weight, 0)
else:
weight = weight
return weight

def back_test(self, symbol_names, portfolio_weights_dictionary, portfolio_data_dictionary, historical_price_market, is_long_only, market_chart, strategy_name):
"""
Main backtest function. Takes in the portfolio weights and compares the portfolio returns with a market index of your choice.
"""

# 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))]
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))]
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_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')
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 = '--')

# 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)

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
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_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))]
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_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)
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 = '--')

# 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)
"""
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):
"""
Percentage change
"""
return ((new - old) * 100) / old

def portfolio_weight_manager(self, weight, is_long_only):
"""
Manage portfolio weights. If portfolio is long only, set the negative weights to zero.
"""
if is_long_only == 1:
weight = max(weight, 0)
else:
weight = weight
return weight

def back_test(self, symbol_names, portfolio_weights_dictionary, portfolio_data_dictionary, historical_price_market, is_long_only, market_chart, strategy_name):
"""
Main backtest function. Takes in the portfolio weights and compares the portfolio returns with a market index of your choice.
"""

# 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))]
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))]
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_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')
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 = '--')

# 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)

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_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))]
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_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)
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 = '--')

# 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)
79 changes: 79 additions & 0 deletions commands.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
[{
"comm": "--history_to_use",
"type": "str",
"default": "all",
"help": "How many bars of 1 hour do you want to use for the anomaly detection model. Either an integer or all"
},
{
"comm": "--is_load_from_dictionary",
"type": "int",
"default": "0",
"help": "Whether to load data from dictionary or get it from yahoo finance."
},
{
"comm": "--data_dictionary_path",
"type": "str",
"default": "dictionaries/data_dictionary.npy",
"help": "Data dictionary path."
},
{
"comm": "--is_save_dictionary",
"type": "int",
"default": "1",
"help": "Whether to save data in a dictionary."
},
{
"comm": "--data_granularity_minutes",
"type": "int",
"default": "15",
"help": "Minute level data granularity that you want to use. Default is 60 minute bars."
},
{
"comm": "--is_test",
"type": "int",
"default": "1",
"help": "Whether to test the tool or just predict for future. When testing you should set the future_bars to larger than 1."
},
{
"comm": "--future_bars",
"type": "int",
"default": "30",
"help": "How many bars to keep for testing purposes."
},
{
"comm": "--apply_noise_filtering",
"type": "int",
"default": "1",
"help": "Whether to apply the random matrix theory to filter out the eigen values."
},
{
"comm": "--only_long",
"type": "int",
"default": "1",
"help": "Whether to only long the stocks or do both long and short."
},
{
"comm": "--market_index",
"type": "str",
"default": "SPY",
"help": "Which index to use for comparisons."
},
{
"comm": "--eigen_portfolio_number",
"type": "int",
"default": "2",
"help": "Which eigen portfolio to choose. By default the 2nd one is choosen as it gives the most risk and reward."
},
{
"comm": "--stocks_file_path",
"type": "str",
"default": "stocks/stocks.txt",
"help": "Stocks file that contains the list of stocks you want to build your portfolio with."
},
{
"comm": "--save_plot",
"type": "bool",
"default": "False",
"help": "Save plot instead of rendering it immediately."
}
]