Skip to content

Commit

Permalink
Merge pull request #7 from silvavn/master
Browse files Browse the repository at this point in the history
Improved organization and removed redundancy
  • Loading branch information
tradytics committed Sep 11, 2020
2 parents 117eac3 + b09e227 commit bd01719
Show file tree
Hide file tree
Showing 7 changed files with 663 additions and 529 deletions.
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."
}
]

0 comments on commit bd01719

Please sign in to comment.