811 lines
56 KiB
Python
811 lines
56 KiB
Python
import plotly.express as plt
|
|
import plotly.subplots as subplt
|
|
import plotly.graph_objects as goplt
|
|
#import matplotlib.pyplot as mpl
|
|
import numpy as np
|
|
import pandas as pd
|
|
import math
|
|
import time
|
|
from multiprocessing import Pool
|
|
from functools import partial
|
|
import itertools
|
|
|
|
def read_ticker_data(stock_settings={}, trading_settings={}):
|
|
'''
|
|
Open the file to parse the data into a dictionary of arrays.
|
|
Data is sorted under arrays with keys: "Datetime" "Open" "High" "Low" "Close"
|
|
'''
|
|
|
|
data = {
|
|
"Datetime": [],
|
|
"Open": [],
|
|
"High": [],
|
|
"Low": [],
|
|
"Close": [],
|
|
"Points": 0
|
|
}
|
|
|
|
# Which open depends on where you execute the file
|
|
#file = open(f"..//..//Raw Data//{stock_settings['Ticker']} {stock_settings['Period']} {stock_settings['Interval']} Data.csv")
|
|
file = open(f"Raw Data//{stock_settings['Ticker']} {stock_settings['Period']} {stock_settings['Interval']} Data.csv")
|
|
file_list = file.readlines()
|
|
|
|
if trading_settings["Intervals in Period"] == 0: # This is to tell us that we want to use all data available to simulate trading
|
|
trading_settings["Intervals in Period"] = len(file_list)-1-trading_settings["Start Interval"]
|
|
|
|
i = 0
|
|
header = True
|
|
for line in file_list:
|
|
if i >= trading_settings["Start Interval"] + trading_settings["Intervals in Period"]: # So that we don't graph past how far we traded
|
|
break
|
|
if header:
|
|
header = False
|
|
|
|
else:
|
|
line_elements = line.strip().split(",") # Remove the newline at the end and split
|
|
data["Datetime"].append(line_elements[0])
|
|
data["Open"].append(float(line_elements[1]))
|
|
data["High"].append(float(line_elements[2]))
|
|
data["Low"].append(float(line_elements[3]))
|
|
data["Close"].append(float(line_elements[4]))
|
|
i += 1
|
|
|
|
file.close()
|
|
|
|
data["Points"] = len(data["Datetime"])
|
|
return data
|
|
|
|
def generate_sma(reference_data, length=4, trading_settings={}):
|
|
'''Return the simple moving average data of an array of reference_data. Length is how many places to look backwards for the average.'''
|
|
sma_data = [reference_data[i] for i in range(max(trading_settings["Start Interval"], length))] # This is the array we will return of the simple moving averaged data.
|
|
# We will initialize it with the first values copied over since we can't average the first few.
|
|
#sma_data = [reference_data[i] for i in range(max(trading_settings["Start Interval"]-length, length))]
|
|
#sma_data = list(itertools.repeat(reference_data[max(trading_settings["Start Interval"], length)-1], max(trading_settings["Start Interval"]-length, length)))
|
|
for i in range(max(trading_settings["Start Interval"]-length, length), trading_settings["Start Interval"]+trading_settings["Intervals in Period"]):
|
|
sma_data.append(sum(reference_data[i-length:i]) / length)
|
|
|
|
#for i in range(trading_settings["Start Interval"]+trading_settings["Intervals in Period"], len(data)):
|
|
#sma_data.append(0)
|
|
|
|
return sma_data
|
|
|
|
def generate_offset(data, offset_percent, keys_to_use=[], trading_settings={}):
|
|
'''Create an upper and lower offset if specified. If more than one array of data us selected,
|
|
it looks at which one is higher/lower for the appropriate offset.'''
|
|
|
|
upper_offset = list(itertools.repeat(0, max(1, trading_settings["Start Interval"])))
|
|
lower_offset = list(itertools.repeat(0, max(1, trading_settings["Start Interval"])))
|
|
|
|
if len(keys_to_use) == 1: # Just one line to apply an offset
|
|
for i in range(max(1, trading_settings["Start Interval"]), trading_settings["Start Interval"]+trading_settings["Intervals in Period"]):
|
|
upper_offset.append((1+offset_percent/100)*data[keys_to_use[0]][i])
|
|
lower_offset.append((1-offset_percent/100)*data[keys_to_use[0]][i])
|
|
if len(keys_to_use) == 2: # 2 lines, therefore we need to check which is above the other
|
|
for i in range(max(1, trading_settings["Start Interval"]), trading_settings["Start Interval"]+trading_settings["Intervals in Period"]):
|
|
if data[keys_to_use[0]][i] > data[keys_to_use[1]][i]:
|
|
upper_offset.append((1+offset_percent/100)*data[keys_to_use[0]][i])
|
|
lower_offset.append((1-offset_percent/100)*data[keys_to_use[1]][i])
|
|
else:
|
|
upper_offset.append((1+offset_percent/100)*data[keys_to_use[1]][i])
|
|
lower_offset.append((1-offset_percent/100)*data[keys_to_use[0]][i])
|
|
|
|
#points = len(data["Datetime"])
|
|
#for i in range(trading_settings["Start Interval"]+trading_settings["Intervals in Period"], points):
|
|
#upper_offset.append(0)
|
|
#lower_offset.append(0)
|
|
return upper_offset, lower_offset
|
|
|
|
def crossover_events(data, keys_to_use=[], trading_settings={}):
|
|
'''Returns an array of 1 or 0 when the first line crosses over the second, and then vice versa.
|
|
It is looking at the first listed index to see how it crosses the second.'''
|
|
|
|
cross_aboves = list(itertools.repeat(0, max(1, trading_settings["Start Interval"])))
|
|
cross_belows = list(itertools.repeat(0, max(1, trading_settings["Start Interval"])))
|
|
above = data[keys_to_use[0]][0] > data[keys_to_use[1]][0] # Whether or not the first line is above the other
|
|
|
|
for i in range(max(1, trading_settings["Start Interval"]), trading_settings["Start Interval"]+trading_settings["Intervals in Period"]):
|
|
if above and data[keys_to_use[0]][i] < data[keys_to_use[1]][i]: # Crosses beneath
|
|
cross_belows.append(1)
|
|
above = False
|
|
else:
|
|
cross_belows.append(0)
|
|
if (not above) and data[keys_to_use[0]][i] > data[keys_to_use[1]][i]: # Crosses above
|
|
cross_aboves.append(1)
|
|
above = True
|
|
else:
|
|
cross_aboves.append(0)
|
|
|
|
#points = len(data["Datetime"])
|
|
#for i in range(trading_settings["Start Interval"]+trading_settings["Intervals in Period"], points):
|
|
#cross_aboves.append(0)
|
|
#cross_belows.append(1)
|
|
|
|
return cross_aboves, cross_belows
|
|
|
|
def trend_reversal_event(data, method=1, trading_settings={}, current_trend=0):
|
|
'''Returns an array of 1 or 0 when the trend reverses.
|
|
Method 1: just looking at the high and low values crossing the offset.'''
|
|
|
|
bullish_reversal = list(itertools.repeat(0, trading_settings["Start Interval"]))
|
|
bearish_reversal = list(itertools.repeat(0, trading_settings["Start Interval"]))
|
|
neutral_reversal = list(itertools.repeat(0, trading_settings["Start Interval"]))
|
|
if method == 1: # Just looking at the high and low values crossing the offset.
|
|
current_trend = current_trend # 0 for neutral (only when we just begin), 1 for bullish, -1 for bearish
|
|
for i in range(trading_settings["Start Interval"], trading_settings["Start Interval"]+trading_settings["Intervals in Period"]):
|
|
if data["High Crosses Upper Offset"][i] and data["Low Crosses Lower Offset"][i]: # In the even we have both triggers let's ignore it
|
|
bullish_reversal.append(0)
|
|
bearish_reversal.append(0)
|
|
neutral_reversal.append(0)
|
|
elif data["High Crosses Upper Offset"][i] and current_trend != 1: # Checking for bullish reversal
|
|
bullish_reversal.append(1)
|
|
bearish_reversal.append(0)
|
|
neutral_reversal.append(0)
|
|
current_trend = 1
|
|
elif data["Low Crosses Lower Offset"][i] and current_trend != -1: # Checking for bearish reversal
|
|
bullish_reversal.append(0)
|
|
bearish_reversal.append(1)
|
|
neutral_reversal.append(0)
|
|
current_trend = -1
|
|
else:
|
|
bullish_reversal.append(0)
|
|
bearish_reversal.append(0)
|
|
neutral_reversal.append(0)
|
|
|
|
#points = len(data["Datetime"])
|
|
#for i in range(trading_settings["Start Interval"]+trading_settings["Intervals in Period"], points):
|
|
#bullish_reversal.append(0)
|
|
#bearish_reversal.append(0)
|
|
#neutral_reversal.append(0)
|
|
|
|
return bullish_reversal, bearish_reversal, neutral_reversal
|
|
|
|
def walking_forward_value(data, just_final=0, trading_settings={}, trading_interval_starting_values={}):
|
|
'''Generates a single array of the value of the account. Reinvests profits by default.
|
|
Interval delay is how many intervals it took to get in position. Defaults to 1 interval delay.
|
|
Worst case by default assumes selling at interval's low and buying at intervals high. Set to 0 to trade at close of interval.
|
|
If just_final is set to 1 then there will be no arrays created, just the final value will be given.
|
|
start_interval is the index of the interval that we started trading, num_trade_intervals is how long we continued to trade.'''
|
|
if not just_final: # If we want all of the data including the arrays
|
|
current_position = trading_interval_starting_values["Current Position"] # How many shares we own. Positive for long, negative for short
|
|
cash_value = [trading_interval_starting_values["Cash Value"] for i in range(0, max(1, trading_settings["Interval Delay"], trading_settings["Start Interval"]))] # Running total to keep track of cash to buy and cash after shorting
|
|
position_value = [trading_interval_starting_values["Position Value"] for i in range(0, max(1, trading_settings["Interval Delay"], trading_settings["Start Interval"]))] # Running total of the account's position value
|
|
account_value = [cash_value[i] + position_value[i] for i in range(0, max(1, trading_settings["Interval Delay"], trading_settings["Start Interval"]))] # Running total of the account's value
|
|
if trading_settings["Reinvest"]:
|
|
for i in range(max([1, trading_settings["Interval Delay"], trading_settings["Start Interval"]]), min(len(data["Close"])-trading_settings["Interval Delay"], trading_settings["Start Interval"]+trading_settings["Intervals in Period"])):
|
|
cash_value.append(cash_value[i-1])
|
|
if data["Buy Signal"][i-trading_settings["Interval Delay"]] == 1: # We have a bullish trend reversal so we should buy long
|
|
if current_position == 0: # We are in no current position, no need to buy to close first
|
|
if trading_settings["Worst Case"]:
|
|
current_position = math.floor(cash_value[i] / data["High"][i]) # Number of shares to buy with our available money
|
|
cash_value[i] = cash_value[i] - current_position * data["High"][i] # Take away our cash for entering the long position
|
|
else: # Not worst case buying at high so we buy at open
|
|
current_position = math.floor(cash_value[i] / data["Open"][i]) # Number of shares to buy with our available money
|
|
cash_value[i] = cash_value[i] - current_position * data["Open"][i] # Take away our cash for entering the long position
|
|
if current_position < 0: # We are in a short position so we need to buy to close first
|
|
if trading_settings["Worst Case"]:
|
|
cash_value[i] = cash_value[i] + current_position * data["High"][i] # Take away our cash for closing the short position
|
|
current_position = math.floor(cash_value[i] / data["High"][i]) # Number of shares to buy with our available money
|
|
cash_value[i] = cash_value[i] - current_position * data["High"][i] # Take away our cash for entering the long position
|
|
else: # Not worst case buying at high so we buy at open
|
|
cash_value[i] = cash_value[i] + current_position * data["Open"][i] # Take away our cash for closing the short position
|
|
current_position = math.floor(cash_value[i] / data["Open"][i]) # Number of shares to buy with our available money
|
|
cash_value[i] = cash_value[i] - current_position * data["Open"][i] # Take away our cash for entering the long position
|
|
|
|
if data["Sell Signal"][i-trading_settings["Interval Delay"]] == 1: # We have a bearish trend reversal so we should sell short
|
|
if current_position == 0: # We are in no current position, no need to sell to close first
|
|
if trading_settings["Worst Case"]:
|
|
current_position = -1*math.floor(cash_value[i] / data["Low"][i])/2 # Number of shares to sell short with our available money
|
|
cash_value[i] = cash_value[i] - current_position * data["Low"][i] # Add cash for selling short
|
|
else: # Not worst case buying at high so we buy at open
|
|
current_position = -1*math.floor(cash_value[i] / data["Open"][i])/2 # Number of shares to buy with our available money
|
|
cash_value[i] = cash_value[i] - current_position * data["Open"][i] # Add cash for selling short
|
|
if current_position > 0: # We are in a long position so we need to sell to close first
|
|
if trading_settings["Worst Case"]:
|
|
cash_value[i] = cash_value[i] + current_position * data["Low"][i] # Add cash for closing the long position
|
|
current_position = -1*math.floor(cash_value[i] / data["Low"][i])/2 # Number of shares to buy with our available money
|
|
cash_value[i] = cash_value[i] - current_position * data["Low"][i] # Add cash for selling short
|
|
else: # Not worst case buying at high so we buy at open
|
|
cash_value[i] = cash_value[i] + current_position * data["Open"][i] # Add cash for closing the long position
|
|
current_position = -1*math.floor(cash_value[i] / data["Open"][i])/2 # Number of shares to buy with our available money
|
|
cash_value[i] = cash_value[i] - current_position * data["Open"][i] # Add cash for selling short
|
|
|
|
position_value.append(current_position * data["Close"][i])
|
|
account_value.append(cash_value[i] + position_value[i])
|
|
# Needed to account for the last bit of data not added
|
|
#for i in range(max(trading_settings["Interval Delay"], len(data["Close"])-trading_settings["Intervals in Period"]-trading_settings["Start Interval"])):
|
|
#account_value.append(account_value[-1])
|
|
#cash_value.append(cash_value[-1])
|
|
#position_value.append(position_value[-1])
|
|
|
|
return account_value, cash_value, position_value, current_position
|
|
else: # All we want is the final value
|
|
current_position = 0 # How many shares we own. Positive for long, negative for short
|
|
cash_value = trading_settings["Starting Value"] # Running total to keep track of cash to buy and cash after shorting
|
|
position_value = 0 # Running total of the account's position value
|
|
account_value = trading_settings["Starting Value"] # Running total of the account's value
|
|
if trading_settings["Reinvest"]:
|
|
for i in range(max([1, trading_settings["Interval Delay"], trading_settings["Start Interval"]]), min(len(data["Close"])-trading_settings["Interval Delay"], trading_settings["Start Interval"]+trading_settings["Intervals in Period"])):
|
|
if data["Buy Signal"][i-trading_settings["Interval Delay"]] == 1: # We have a bullish trend reversal so we should buy long
|
|
if current_position == 0: # We are in no current position, no need to buy to close first
|
|
if trading_settings["Worst Case"]:
|
|
current_position = math.floor(cash_value / data["High"][i]) # Number of shares to buy with our available money
|
|
cash_value = cash_value - current_position * data["High"][i] # Take away our cash for entering the long position
|
|
else: # Not worst case buying at high so we buy at close
|
|
current_position = math.floor(cash_value / data["Open"][i]) # Number of shares to buy with our available money
|
|
cash_value = cash_value - current_position * data["Open"][i] # Take away our cash for entering the long position
|
|
if current_position < 0: # We are in a short position so we need to buy to close first
|
|
if trading_settings["Worst Case"]:
|
|
cash_value = cash_value + current_position * data["High"][i] # Take away our cash for closing the short position
|
|
current_position = math.floor(cash_value / data["High"][i]) # Number of shares to buy with our available money
|
|
cash_value = cash_value - current_position * data["High"][i] # Take away our cash for entering the long position
|
|
else: # Not worst case buying at high so we buy at close
|
|
cash_value = cash_value + current_position * data["Open"][i] # Take away our cash for closing the short position
|
|
current_position = math.floor(cash_value / data["Open"][i]) # Number of shares to buy with our available money
|
|
cash_value = cash_value - current_position * data["Open"][i] # Take away our cash for entering the long position
|
|
|
|
if data["Sell Signal"][i-trading_settings["Interval Delay"]] == 1: # We have a bearish trend reversal so we should sell short
|
|
if current_position == 0: # We are in no current position, no need to sell to close first
|
|
if trading_settings["Worst Case"]:
|
|
current_position = -1*math.floor(cash_value / data["Low"][i])/2 # Number of shares to sell short with our available money
|
|
cash_value = cash_value - current_position * data["Low"][i] # Add cash for selling short
|
|
else: # Not worst case buying at high so we buy at close
|
|
current_position = -1*math.floor(cash_value / data["Open"][i])/2 # Number of shares to buy with our available money
|
|
cash_value = cash_value - current_position * data["Close"][i] # Add cash for selling short
|
|
if current_position > 0: # We are in a long position so we need to sell to close first
|
|
if trading_settings["Worst Case"]:
|
|
cash_value = cash_value + current_position * data["Low"][i] # Add cash for closing the long position
|
|
current_position = -1*math.floor(cash_value / data["Low"][i])/2 # Number of shares to buy with our available money
|
|
cash_value = cash_value - current_position * data["Low"][i] # Add cash for selling short
|
|
else: # Not worst case buying at high so we buy at close
|
|
cash_value = cash_value + current_position * data["Open"][i] # Add cash for closing the long position
|
|
current_position = -1*math.floor(cash_value / data["Open"][i])/2 # Number of shares to buy with our available money
|
|
cash_value = cash_value - current_position * data["Open"][i] # Add cash for selling short
|
|
position_value = current_position * data["Close"][i]
|
|
account_value = cash_value + position_value
|
|
return round(account_value, 3)
|
|
|
|
def plot_values(data, keys_to_plot=[], colors=[], title=""):
|
|
'''Plots the specified values in data following keys_to_plot.
|
|
Single graph where all values are overlayed on each other'''
|
|
points = data["Points"]
|
|
|
|
plot_data = {
|
|
'Time Interval': np.linspace(0, stop=points, num=points, endpoint=False),
|
|
}
|
|
|
|
for key in keys_to_plot:
|
|
plot_data[key] = data[key]
|
|
|
|
df = pd.DataFrame(plot_data)
|
|
fig = plt.line(
|
|
df,
|
|
x = 'Time Interval',
|
|
y = keys_to_plot,
|
|
color_discrete_sequence = colors,
|
|
title = title
|
|
)
|
|
fig.show()
|
|
|
|
def plot_values_stacked(data, keys_to_plot=[], colors=[], title="", x_values=[]):
|
|
'''Plots the specified values in data following keys_to_plot where each sub array is included on their own graph.
|
|
Multiple, stacked graphs where all share the x-axis.'''
|
|
|
|
# Generate the subplot titles
|
|
subplot_titles = []
|
|
for subplot in keys_to_plot:
|
|
subplot_titles.append(", ".join(subplot))
|
|
# Setup the whole plots
|
|
fig = subplt.make_subplots(rows=len(keys_to_plot), cols=1, shared_xaxes=True, vertical_spacing=0.03, x_title="Interval", y_title="Value", subplot_titles=subplot_titles)
|
|
|
|
# Check to see if we passed along specific x values
|
|
if x_values:
|
|
subplot_num = 0
|
|
for subplot in keys_to_plot:
|
|
element_num = 0
|
|
for element in subplot:
|
|
fig.add_trace(goplt.Scatter(x=x_values[subplot_num], y=data[element], name=element, marker_color=colors[subplot_num][element_num]), row=subplot_num+1, col=1)
|
|
element_num += 1
|
|
subplot_num += 1
|
|
else:
|
|
# Add the traces for everything desired to be plotted
|
|
points = data["Points"]
|
|
x_values = np.linspace(0, stop=points, num=points, endpoint=False)
|
|
|
|
subplot_num = 0
|
|
for subplot in keys_to_plot:
|
|
element_num = 0
|
|
for element in subplot:
|
|
fig.add_trace(goplt.Scatter(x=x_values, y=data[element], name=element, marker_color=colors[subplot_num][element_num]), row=subplot_num+1, col=1)
|
|
element_num += 1
|
|
subplot_num += 1
|
|
|
|
fig.update_layout(title_text=title)
|
|
fig.show()
|
|
|
|
def optimize_algo_settings(data, optimization_settings={}, current_trend=0):
|
|
'''Optimizes the parameters for the algorithm by going through every possible value and comparing it to the highest seen value after going through a specific time interval.'''
|
|
best_account_value = optimization_settings["Starting Value"] # Keep track of highest ending value
|
|
best_algo_settings = [1,1,100] # Keep track of best settings, set default so if there is no setting that satisfies the condition, then it gives up trading
|
|
best_algo_settings_absolute = [1,1,100] # This is the best possible parameters, hands down. No trade frequency checking or anything else
|
|
intervals_per_trade = 0
|
|
# Reset the sma lines, offset lines, crossover events, and trend reversals
|
|
new_test_values = { # Update the values
|
|
"Start Interval": optimization_settings["Start Interval"],
|
|
"Intervals in Period": optimization_settings["Intervals in Period"],
|
|
"Short SMA": optimization_settings["Short SMA Range"][0],
|
|
"Long SMA": optimization_settings["Long SMA Range"][0],
|
|
"Offset Percent": optimization_settings["Offset Percent Range"][0],
|
|
"Interval Delay": optimization_settings["Interval Delay"],
|
|
"Reinvest": optimization_settings["Reinvest"],
|
|
"Worst Case": optimization_settings["Worst Case"],
|
|
"Starting Value": optimization_settings["Starting Value"]
|
|
}
|
|
for i in range(optimization_settings["Short SMA Range"][0], optimization_settings["Short SMA Range"][1]+1, optimization_settings["Short SMA Step"]):
|
|
new_test_values["Short SMA"] = i
|
|
data["Short SMA"] = generate_sma(data["Close"], i, new_test_values)
|
|
for j in range(optimization_settings["Long SMA Range"][0], optimization_settings["Long SMA Range"][1]+1, optimization_settings["Long SMA Step"]):
|
|
if j <= i: # Long sma is shorter than short sma so skip
|
|
continue
|
|
if i >= j: # Short sma is longer than the long sma so skip
|
|
continue
|
|
new_test_values["Long SMA"] = j
|
|
data["Long SMA"] = generate_sma(data["Close"], j, new_test_values)
|
|
offset_percent = optimization_settings["Offset Percent Range"][0] # Store the offset_percent since it is a float and can't be used for the iterator
|
|
for k in range(0, int((optimization_settings["Offset Percent Range"][1]-optimization_settings["Offset Percent Range"][0])/optimization_settings["Offset Percent Step"])):
|
|
new_test_values["Offset Percent"] = offset_percent
|
|
data["Upper SMA Offset"], data["Lower SMA Offset"] = generate_offset(data, offset_percent, ["Short SMA", "Long SMA"], new_test_values)
|
|
|
|
data["High Crosses Upper Offset"] = crossover_events(data, ["Open", "Upper SMA Offset"], new_test_values)[0]
|
|
data["Low Crosses Lower Offset"] = crossover_events(data, ["Open", "Lower SMA Offset"], new_test_values)[1]
|
|
|
|
data["Buy Signal"] = trend_reversal_event(data, 1, new_test_values,current_trend)[0]
|
|
data["Sell Signal"] = trend_reversal_event(data, 1, new_test_values, current_trend)[1]
|
|
|
|
try:
|
|
intervals_per_trade = (optimization_settings["Intervals in Period"])/(data["Buy Signal"].count(1) + data["Sell Signal"].count(1))
|
|
except:
|
|
intervals_per_trade = 0
|
|
|
|
account_value = walking_forward_value(data, 1, new_test_values)
|
|
|
|
if account_value > best_account_value: # We found a better parameter setup
|
|
if intervals_per_trade >= optimization_settings["Minimum Intervals Per Trade"]: # With not an unreasonable amount of trades
|
|
best_account_value = account_value
|
|
best_algo_settings = [i, j, round(offset_percent, 2)]
|
|
best_algo_settings_absolute = [i, j, round(offset_percent, 2)]
|
|
else:
|
|
best_algo_settings_absolute = [i, j, round(offset_percent, 2)]
|
|
offset_percent += round(optimization_settings["Offset Percent Step"], 3)
|
|
return best_algo_settings, round(best_account_value/optimization_settings["Starting Value"]*100 - 100, 3), best_algo_settings_absolute # Return best settings and the percent increase and the hands down best parameters
|
|
|
|
def multithreadable_algo_test(algo_params=[], optimization_settings={}, data={}):
|
|
'''Optimizes the parameters for the algorithm by going through every possible value and comparing it to the highest seen value after going through a specific time interval.'''
|
|
|
|
# Reset the sma lines, offset lines, crossover events, and trend reversals
|
|
new_test_values = { # Update the values
|
|
"Start Interval": optimization_settings["Start Interval"],
|
|
"Intervals in Period": optimization_settings["Intervals in Period"],
|
|
"Short SMA": algo_params[0],
|
|
"Long SMA": algo_params[1],
|
|
"Offset Percent": algo_params[2],
|
|
"Interval Delay": optimization_settings["Interval Delay"],
|
|
"Reinvest": optimization_settings["Reinvest"],
|
|
"Worst Case": optimization_settings["Worst Case"],
|
|
"Starting Value": optimization_settings["Starting Value"]
|
|
}
|
|
|
|
data["Short SMA"] = generate_sma(data["Close"], algo_params[0], new_test_values)
|
|
data["Long SMA"] = generate_sma(data["Close"], algo_params[1], new_test_values)
|
|
|
|
data["Upper SMA Offset"], data["Lower SMA Offset"] = generate_offset(data, algo_params[2], ["Short SMA", "Long SMA"], new_test_values)
|
|
|
|
data["High Crosses Upper Offset"] = crossover_events(data, ["Open", "Upper SMA Offset"], new_test_values)[0]
|
|
data["Low Crosses Lower Offset"] = crossover_events(data, ["Open", "Lower SMA Offset"], new_test_values)[1]
|
|
|
|
data["Buy Signal"] = trend_reversal_event(data, 1, new_test_values)[0]
|
|
data["Sell Signal"] = trend_reversal_event(data, 1, new_test_values)[1]
|
|
|
|
try:
|
|
intervals_per_trade = (optimization_settings["Intervals in Period"])/(data["Buy Signal"].count(1) + data["Sell Signal"].count(1))
|
|
except:
|
|
intervals_per_trade = 0
|
|
|
|
account_value = walking_forward_value(data, 1, new_test_values)
|
|
|
|
if intervals_per_trade >= optimization_settings["Minimum Intervals Per Trade"]: # With not an unreasonable amount of trades
|
|
return account_value
|
|
else:
|
|
return algo_params[-1]
|
|
|
|
def optimize_algo_settings_multithreaded(data={}, optimization_settings={}):
|
|
account_values_per_combo = []
|
|
with Pool(optimization_settings["Pools"]) as p:
|
|
account_values_per_combo = p.map(partial(multithreadable_algo_test, optimization_settings=optimization_settings, data=data), optimization_settings["Optimization Combinations"])
|
|
|
|
best_algo_settings = optimization_settings["Optimization Combinations"][account_values_per_combo.index(max(account_values_per_combo))]
|
|
|
|
return best_algo_settings
|
|
|
|
def multithreadable_algo_test_plottable(algo_params=[], optimization_settings={}, data={}):
|
|
'''Tests each algo param passed to it through multiprocessing. Returns a dictionary for every combination of parameters to be able to plot.'''
|
|
|
|
# Reset the sma lines, offset lines, crossover events, and trend reversals
|
|
new_test_values = { # Update the values
|
|
"Start Interval": optimization_settings["Start Interval"],
|
|
"Intervals in Period": optimization_settings["Intervals in Period"],
|
|
"Short SMA": algo_params[0],
|
|
"Long SMA": algo_params[1],
|
|
"Offset Percent": algo_params[2],
|
|
"Interval Delay": optimization_settings["Interval Delay"],
|
|
"Reinvest": optimization_settings["Reinvest"],
|
|
"Worst Case": optimization_settings["Worst Case"],
|
|
"Starting Value": optimization_settings["Starting Value"]
|
|
}
|
|
|
|
data["Short SMA"] = generate_sma(data["Close"], algo_params[0], new_test_values)
|
|
data["Long SMA"] = generate_sma(data["Close"], algo_params[1], new_test_values)
|
|
|
|
data["Upper SMA Offset"], data["Lower SMA Offset"] = generate_offset(data, algo_params[2], ["Short SMA", "Long SMA"], new_test_values)
|
|
|
|
data["High Crosses Upper Offset"] = crossover_events(data, ["Open", "Upper SMA Offset"], new_test_values)[0]
|
|
data["Low Crosses Lower Offset"] = crossover_events(data, ["Open", "Lower SMA Offset"], new_test_values)[1]
|
|
|
|
data["Buy Signal"] = trend_reversal_event(data, 1, new_test_values)[0]
|
|
data["Sell Signal"] = trend_reversal_event(data, 1, new_test_values)[1]
|
|
|
|
try:
|
|
intervals_per_trade = (optimization_settings["Intervals in Period"])/(data["Buy Signal"].count(1) + data["Sell Signal"].count(1))
|
|
except:
|
|
intervals_per_trade = 0
|
|
|
|
account_value = walking_forward_value(data, 1, new_test_values)
|
|
|
|
|
|
return_dict = {
|
|
"Algo Settings": algo_params,
|
|
"Final Account Value": account_value,
|
|
"Percent Increase": ((account_value/optimization_settings["Starting Value"])-1)*100,
|
|
"Number of Trades": data["Buy Signal"].count(1) + data["Sell Signal"].count(1),
|
|
"Avg Intervals Per Trade": intervals_per_trade
|
|
}
|
|
|
|
return return_dict
|
|
|
|
def optimize_algo_settings_with_plot(data={}, optimization_settings={}):
|
|
results = []
|
|
with Pool(optimization_settings["Pools"]) as p:
|
|
results = p.map(partial(multithreadable_algo_test_plottable, optimization_settings=optimization_settings, data=data), optimization_settings["Optimization Combinations"])
|
|
|
|
|
|
# Still return the best algo setting. Also need the best algo settings for the plot
|
|
best_algo_settings = []
|
|
best_percent_increase = 0
|
|
for result in results:
|
|
if result["Percent Increase"] > best_percent_increase:
|
|
best_algo_settings = result["Algo Settings"]
|
|
best_percent_increase = result["Percent Increase"]
|
|
|
|
# Plotting
|
|
# Format first
|
|
short_sma_title = f"Changing Short SMA with LSMA({best_algo_settings[1]}) OP({best_algo_settings[2]})"
|
|
long_sma_title = f"Changing Long SMA with SSMA({best_algo_settings[0]}) OP({best_algo_settings[2]})"
|
|
offset_percent_title = f"Changing Offset Percent with SSMA({best_algo_settings[0]}) LSMA({best_algo_settings[1]})"
|
|
plottable_results = {
|
|
short_sma_title: [],
|
|
long_sma_title: [],
|
|
offset_percent_title: [],
|
|
"Points": 0
|
|
}
|
|
|
|
x_values = {
|
|
"Main X Values": [],
|
|
"Short SMA X Values": [],
|
|
"Long SMA X Values": [],
|
|
"Offset Percent X Values": [],
|
|
}
|
|
|
|
for result in results:
|
|
# Short SMA first
|
|
if result["Algo Settings"][1] == best_algo_settings[1] and result["Algo Settings"][2] == best_algo_settings[2]: # Only graph values where the other 2 params were the "best"
|
|
plottable_results[short_sma_title].append(result["Percent Increase"])
|
|
x_values["Short SMA X Values"].append(result["Algo Settings"][0])
|
|
# Long SMA
|
|
if result["Algo Settings"][0] == best_algo_settings[0] and result["Algo Settings"][2] == best_algo_settings[2]: # Only graph values where the other 2 params were the "best"
|
|
plottable_results[long_sma_title].append(result["Percent Increase"])
|
|
x_values["Long SMA X Values"].append(result["Algo Settings"][1])
|
|
# Percent offset
|
|
if result["Algo Settings"][0] == best_algo_settings[0] and result["Algo Settings"][1] == best_algo_settings[1]: # Only graph values where the other 2 params were the "best"
|
|
plottable_results[offset_percent_title].append(result["Percent Increase"])
|
|
x_values["Offset Percent X Values"].append(result["Algo Settings"][2])
|
|
|
|
plottable_results["Points"] = max(len(plottable_results[short_sma_title]), len(plottable_results[long_sma_title]), len(plottable_results[offset_percent_title]))
|
|
x_values["Main X Values"] = np.linspace(0, stop=plottable_results["Points"], num=plottable_results["Points"], endpoint=False)
|
|
|
|
title = "Optimization Graphs"
|
|
plot_values_stacked(plottable_results, [[short_sma_title, long_sma_title, offset_percent_title], [short_sma_title], [long_sma_title], [offset_percent_title]], [["blue", "orange", "black"], ["blue"], ["orange"], ["black"]], title, [x_values["Main X Values"], x_values["Short SMA X Values"], x_values["Long SMA X Values"], x_values["Offset Percent X Values"]])
|
|
return best_algo_settings
|
|
|
|
def rolling_optimization_then_trade(stock_settings={}, trading_settings={}, optimization_settings={}, rollover_trading_settings={}):
|
|
'''Uses past data to optimize the parameters, then trades using those parameters for a specified period before reoptimizing and trading for another period...'''
|
|
|
|
data = read_ticker_data(stock_settings, optimization_settings)
|
|
|
|
# Check to see if our numbers are okay and use all available data if that was chosen
|
|
if len(data["Close"]) < rollover_trading_settings["Start Interval"] + rollover_trading_settings["Intervals to Test"]:
|
|
print("Start Rollover Interval + Number of Intervals is more than the amount of data we have")
|
|
return
|
|
if rollover_trading_settings["Intervals to Test"] == 0: # We want to use all available data
|
|
rollover_trading_settings["Intervals to Test"] = len(data["Close"]) - rollover_trading_settings["Intervals to Test"]
|
|
|
|
# Here is the for loop for every individual trading period we will have depending on how long we want to train before each period and how many data points we have
|
|
total_trading_periods = math.floor((rollover_trading_settings["Intervals to Test"]-rollover_trading_settings["Training Period Width"])/rollover_trading_settings["Trading Period Width"])
|
|
training_period_start_index = rollover_trading_settings["Start Interval"] # Keep track of the start of each training period
|
|
trading_period_start_index = training_period_start_index + rollover_trading_settings["Training Period Width"] # Keep track of the start of each trading period
|
|
|
|
optimization_settings["Intervals in Period"] = rollover_trading_settings["Training Period Width"] # Just a quick update to use the same training period width
|
|
optimization_settings["Start Interval"] = training_period_start_index # Update where we want to begin the training
|
|
|
|
trading_settings["Intervals in Period"] = rollover_trading_settings["Trading Period Width"]
|
|
trading_settings["Starting Value"] = rollover_trading_settings["Starting Value"]
|
|
trading_settings["Start Interval"] = trading_period_start_index
|
|
|
|
plottable_data = { # If we want to plot then we need to keep arrays of the data
|
|
"Datetime": [i for i in range(len(data["Close"]))],
|
|
"Open": [], # Basically a duplicate of the values we used, redundant but makes it easier to keep everything in this dictionary
|
|
"Close": [],
|
|
"High": [],
|
|
"Low": [],
|
|
"Short SMA Length": ["" for i in range(trading_settings["Start Interval"])], # Where we actually get to non redundant data because we are always changing our parameters
|
|
"Long SMA Length": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Offset Percent": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Short SMA": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Long SMA": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Upper Offset": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Lower Offset": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Buy Signal": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Sell Signal": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Account Value": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Cash Value": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Position Value": ["" for i in range(trading_settings["Start Interval"])],
|
|
"Trade Period Marker": ["0" for i in range(len(data["Close"]))],
|
|
"Points": data["Points"]
|
|
}
|
|
|
|
account_value = rollover_trading_settings["Starting Value"] # Keep track of our money so we can reinvest
|
|
trading_interval_starting_values = { # This will be fed into walking forward values so we don't lose our cash and position values
|
|
"Cash Value": rollover_trading_settings["Starting Value"],
|
|
"Position Value": 0,
|
|
"Account Value": rollover_trading_settings["Starting Value"],
|
|
"Current Trend": 0, # Bullish vs bearish vs neutral
|
|
"Current Position": 0
|
|
}
|
|
if optimization_settings["Multithreaded Training"]: # Use multithreading
|
|
# Generate all of the combinations
|
|
for i in range(optimization_settings["Short SMA Range"][0], optimization_settings["Short SMA Range"][1]+1):
|
|
for j in range(optimization_settings["Long SMA Range"][0], optimization_settings["Long SMA Range"][1]+1):
|
|
if j <= i: # Long sma is shorter than short sma so skip
|
|
continue
|
|
if i >= j: # Short sma is longer than the long sma so skip
|
|
continue
|
|
offset_percent = optimization_settings["Offset Percent Range"][0] # Store the offset_percent since it is a float and can't be used for the iterator
|
|
for k in range(0, int((optimization_settings["Offset Percent Range"][1]-optimization_settings["Offset Percent Range"][0])/optimization_settings["Offset Percent Step"])):
|
|
optimization_settings["Optimization Combinations"].append([i, j, round(offset_percent, 3)])
|
|
offset_percent += round(optimization_settings["Offset Percent Step"], 3)
|
|
for i in range(total_trading_periods):
|
|
algo_time = time.time()
|
|
print("Optimizing Algorithm took ", end="")
|
|
if optimization_settings["Multithreaded Training"]: # Use multithreading
|
|
if optimization_settings["Graph Optimizations"]:
|
|
best_algo_settings = optimize_algo_settings_with_plot(data, optimization_settings)
|
|
else:
|
|
best_algo_settings = optimize_algo_settings_multithreaded(data, optimization_settings)
|
|
else: # Don't use multithreading
|
|
best_algo_settings = optimize_algo_settings(data, optimization_settings, trading_interval_starting_values["Current Trend"])[0] # Get the best algorithm settings
|
|
print("%s seconds ---" % (time.time() - algo_time))
|
|
|
|
# Regenerate our data from the new parameters so we can trade it
|
|
data["Short SMA"] = generate_sma(data["Close"], best_algo_settings[0], trading_settings)
|
|
data["Long SMA"] = generate_sma(data["Close"], best_algo_settings[1], trading_settings)
|
|
|
|
data["Upper SMA Offset"], data["Lower SMA Offset"] = generate_offset(data, best_algo_settings[2], ["Short SMA", "Long SMA"], trading_settings)
|
|
|
|
data["High Crosses Upper Offset"] = crossover_events(data, ["Open", "Upper SMA Offset"], trading_settings)[0]
|
|
data["Low Crosses Lower Offset"] = crossover_events(data, ["Open", "Lower SMA Offset"], trading_settings)[1]
|
|
data["Buy Signal"] = trend_reversal_event(data, 1, trading_settings, trading_interval_starting_values["Current Trend"])[0]
|
|
data["Sell Signal"] = trend_reversal_event(data, 1, trading_settings, trading_interval_starting_values["Current Trend"])[1]
|
|
|
|
|
|
|
|
if rollover_trading_settings["Just Final"]: # All we want is the final values, no plotting
|
|
account_value = walking_forward_value(data, 1, trading_settings, trading_interval_starting_values)
|
|
account_value = round(account_value, 3)
|
|
else: # We want to be able to plot
|
|
account_value_array, cash_value_array, position_value_array, current_position = walking_forward_value(data, 0, trading_settings, trading_interval_starting_values)
|
|
account_value = round(account_value_array[-1], 3)
|
|
|
|
# Edit the length
|
|
data["Short SMA"] = data["Short SMA"][trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
data["Long SMA"] = data["Long SMA"][trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
data["Upper SMA Offset"] = data["Upper SMA Offset"][trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
data["Lower SMA Offset"] = data["Lower SMA Offset"][trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
data["High Crosses Upper Offset"] = data["High Crosses Upper Offset"][trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
data["Low Crosses Lower Offset"] = data["Low Crosses Lower Offset"][trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
data["Buy Signal"] = data["Buy Signal"][trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
data["Sell Signal"] = data["Sell Signal"][trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
account_value_array = account_value_array[trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
cash_value_array = cash_value_array[trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
position_value_array = position_value_array[trading_settings["Start Interval"]:trading_settings["Start Interval"]+rollover_trading_settings["Trading Period Width"]]
|
|
|
|
trading_interval_starting_values = { # This will be fed into walking forward values so we don't lose our cash and position values
|
|
"Cash Value": cash_value_array[-1],
|
|
"Position Value": position_value_array[-1],
|
|
"Account Value": cash_value_array[-1] + position_value_array[-1],
|
|
"Current Position": current_position
|
|
}
|
|
if current_position == 0:
|
|
trading_interval_starting_values["Current Trend"] = 0
|
|
else:
|
|
trading_interval_starting_values["Current Trend"] = int(current_position / abs(current_position)) # Bullish vs bearish
|
|
|
|
plottable_data["Open"] = data["Open"]
|
|
plottable_data["Close"] = data["Close"]
|
|
plottable_data["High"] = data["High"]
|
|
plottable_data["Low"] = data["Low"]
|
|
plottable_data["Short SMA Length"].extend([best_algo_settings[0] for i in range(rollover_trading_settings["Trading Period Width"])])
|
|
plottable_data["Long SMA Length"].extend([best_algo_settings[1] for i in range(rollover_trading_settings["Trading Period Width"])])
|
|
plottable_data["Offset Percent"].extend([best_algo_settings[2] for i in range(rollover_trading_settings["Trading Period Width"])])
|
|
plottable_data["Short SMA"].extend(data["Short SMA"])
|
|
plottable_data["Long SMA"].extend(data["Long SMA"])
|
|
plottable_data["Upper Offset"].extend(data["Upper SMA Offset"])
|
|
plottable_data["Lower Offset"].extend(data["Lower SMA Offset"])
|
|
plottable_data["Buy Signal"].extend(data["Buy Signal"])
|
|
plottable_data["Sell Signal"].extend(data["Sell Signal"])
|
|
plottable_data["Account Value"].extend(account_value_array)
|
|
plottable_data["Cash Value"].extend(cash_value_array)
|
|
plottable_data["Position Value"].extend(position_value_array)
|
|
plottable_data["Trade Period Marker"][trading_settings["Start Interval"]] = 1
|
|
|
|
print(f'After Trading Interval {i}. Start Training Interval: {optimization_settings["Start Interval"]}, Stop Training Interval: {optimization_settings["Start Interval"] + optimization_settings["Intervals in Period"]}')
|
|
print(f'Start Trading Interval: {trading_settings["Start Interval"]}, Stop Trading Interval: {trading_settings["Start Interval"]+trading_settings["Intervals in Period"]}')
|
|
print(f'Algorithm Settings for Interval: {best_algo_settings}')
|
|
print(f'Account value: ${account_value}')
|
|
print('\n')
|
|
|
|
trading_settings["Starting Value"] = account_value # Basically so we can reinvest
|
|
optimization_settings["Start Interval"] += rollover_trading_settings["Trading Period Width"] # Update where we want to begin the training for the next training period
|
|
trading_settings["Start Interval"] += rollover_trading_settings["Trading Period Width"] # Update where we want to begin the trading for the next trading period
|
|
if rollover_trading_settings["Just Final"] == 0: # We want to return all of the data to plot
|
|
return plottable_data
|
|
|
|
def main(stock_settings={}, trading_settings={}, optimization_settings={}, rollover_trading_settings={}, action=1):
|
|
'''action: 1 is just use trading_settings to show what would have happened using those value.
|
|
2 is using optimization_settings to optimize then show what would have happend using the best values.
|
|
3 is a more realistic option using rollover_trading_settings to first train on past data then apply the parameters to data not optimized for.
|
|
'''
|
|
if action == 1:
|
|
data = read_ticker_data(stock_settings, trading_settings)
|
|
|
|
# Do some quick number checking to make sure things size up
|
|
if len(data["Close"]) < trading_settings["Start Interval"]+trading_settings["Intervals in Period"]:
|
|
print("Start Trade Interval + Number of Trade Intervals is more than the amount of data we have")
|
|
return
|
|
|
|
if trading_settings["Intervals in Period"] == 0: # This is to tell us that we want to use all data available to simulate trading
|
|
trading_settings["Intervals in Period"] = len(data["Close"])-trading_settings["Start Interval"]
|
|
|
|
data["Short SMA"] = generate_sma(data["Close"], trading_settings["Short SMA"], trading_settings)
|
|
data["Long SMA"] = generate_sma(data["Close"], trading_settings["Long SMA"], trading_settings)
|
|
|
|
data["Upper SMA Offset"], data["Lower SMA Offset"] = generate_offset(data, trading_settings["Offset Percent"], ["Short SMA", "Long SMA"], trading_settings)
|
|
|
|
data["High Crosses Upper Offset"] = crossover_events(data, ["Close", "Upper SMA Offset"], trading_settings)[0]
|
|
data["Low Crosses Lower Offset"] = crossover_events(data, ["Close", "Lower SMA Offset"], trading_settings)[1]
|
|
|
|
data["Buy Signal"] = trend_reversal_event(data, 1, trading_settings)[0]
|
|
data["Sell Signal"] = trend_reversal_event(data, 1, trading_settings)[1]
|
|
|
|
data["Account Value"], data["Cash Value"], data["Position Value"] = walking_forward_value(data, 0, trading_settings)
|
|
data["Final Account Value"] = walking_forward_value(data, 1, trading_settings)
|
|
|
|
title = f'{stock_settings["Ticker"]} {stock_settings["Period"]} {stock_settings["Interval"]} | Trading from {optimization_settings["Start Interval"]} to {optimization_settings["Intervals in Period"]-1} | SSMA({trading_settings["Short SMA"]}) LSMA({trading_settings["Long SMA"]}) OP({trading_settings["Offset Percent"]}%) ID({trading_settings["Interval Delay"]})'
|
|
plot_values_stacked(data, [["Close", "High", "Low", "Upper SMA Offset", "Lower SMA Offset"], ["Buy Signal", "Sell Signal"], ["Account Value", "Position Value", "Cash Value"]], [["black", "orange", "blue", "grey", "grey"], ["green", "red"], ["black", "blue", "grey"]], title)
|
|
print(f"Started with ${trading_settings['Starting Value']}, finished with ${data['Final Account Value']}, {round((data['Final Account Value'] / trading_settings['Starting Value']-1)*100, 3)}% increase.\n")
|
|
elif action == 2:# Here we will run an optimizer to get the best settings
|
|
data = read_ticker_data(stock_settings, optimization_settings)
|
|
|
|
# Do some quick number checking to make sure things size up
|
|
if len(data["Close"]) < optimization_settings["Start Interval"]+optimization_settings["Intervals in Period"]:
|
|
print("Start Optimization Interval + Number of Intervals is more than the amount of data we have")
|
|
return
|
|
|
|
if optimization_settings["Intervals in Period"] == 0: # This is to tell us that we want to use all data available to simulate trading
|
|
optimization_settings["Intervals in Period"] = len(data["Close"])-optimization_settings["Start Interval"]
|
|
|
|
best_algo_settings, best_account_increase_percent = optimize_algo_settings(data, optimization_settings)
|
|
|
|
data["Short SMA"] = generate_sma(data["Close"], best_algo_settings[0], optimization_settings)
|
|
data["Long SMA"] = generate_sma(data["Close"], best_algo_settings[1], optimization_settings)
|
|
|
|
data["Upper SMA Offset"], data["Lower SMA Offset"] = generate_offset(data, best_algo_settings[2], ["Short SMA", "Long SMA"], optimization_settings)
|
|
|
|
data["High Crosses Upper Offset"] = crossover_events(data, ["Close", "Upper SMA Offset"], optimization_settings)[0]
|
|
data["Low Crosses Lower Offset"] = crossover_events(data, ["Close", "Lower SMA Offset"], optimization_settings)[1]
|
|
|
|
data["Buy Signal"] = trend_reversal_event(data, 1, optimization_settings)[0]
|
|
data["Sell Signal"] = trend_reversal_event(data, 1, optimization_settings)[1]
|
|
|
|
data["Account Value"], data["Cash Value"], data["Position Value"] = walking_forward_value(data, 0, optimization_settings)
|
|
data["Final Account Value"] = walking_forward_value(data, 1, optimization_settings)
|
|
|
|
title = f'{stock_settings["Ticker"]} {stock_settings["Period"]} {stock_settings["Interval"]} | Optimizing from {optimization_settings["Start Interval"]} to {optimization_settings["Intervals in Period"]-1} | SSMA Range({optimization_settings["Short SMA Range"]}) LSMA Range({optimization_settings["Long SMA Range"]}) OP Range({optimization_settings["Offset Percent Range"]}, {optimization_settings["Offset Percent Step"]}%) | SSMA({best_algo_settings[0]}) LSMA({best_algo_settings[1]}) OP({best_algo_settings[2]}%) ID({optimization_settings["Interval Delay"]})'
|
|
|
|
plot_values_stacked(data, [["Close", "High", "Low", "Upper SMA Offset", "Lower SMA Offset"], ["Buy Signal", "Sell Signal"], ["Account Value", "Position Value", "Cash Value"]], [["black", "orange", "blue", "grey", "grey"], ["green", "red"], ["black", "blue", "grey"]], title)
|
|
|
|
print(f"Best algorithm settings: {best_algo_settings}")
|
|
print(f"{best_account_increase_percent} % increase.\n")
|
|
elif action == 3: # Rollover trading
|
|
if rollover_trading_settings["Just Final"]:
|
|
rolling_optimization_then_trade(stock_settings, trading_settings, optimization_settings, rollover_trading_settings)
|
|
else:
|
|
plottable_data = rolling_optimization_then_trade(stock_settings, trading_settings, optimization_settings, rollover_trading_settings)
|
|
title = f'{stock_settings["Ticker"]} {stock_settings["Period"]} {stock_settings["Interval"]} | Rollover Trading from {rollover_trading_settings["Start Interval"]} to {len(plottable_data["Account Value"])-1} | Training Width({rollover_trading_settings["Training Period Width"]}) Trading Width({rollover_trading_settings["Trading Period Width"]}) | WC({optimization_settings["Worst Case"]}) ID({optimization_settings["Interval Delay"]}) MIBT({optimization_settings["Minimum Intervals Per Trade"]})'
|
|
plot_values_stacked(plottable_data, [["Close", "Open", "High", "Low", "Upper Offset", "Lower Offset"], ["Short SMA Length", "Long SMA Length", "Offset Percent"], ["Buy Signal", "Sell Signal", "Trade Period Marker"], ["Account Value", "Position Value", "Cash Value"]], [["black", "white", "orange", "blue", "grey", "grey"], ["blue", "orange", "black"], ["green", "red", "black"], ["black", "blue", "grey"]], title)
|
|
|
|
######################################################################################################################################################################################################
|
|
|
|
stock_settings = {
|
|
"Ticker": "INTC",
|
|
"Period": "720d",
|
|
"Interval": "15m"
|
|
}
|
|
|
|
trading_settings = {
|
|
"Start Interval": 0,
|
|
"Intervals in Period": 0, # Use 0 to use all available data
|
|
"Short SMA": 12,
|
|
"Long SMA": 72,
|
|
"Offset Percent": 2.1,
|
|
"Interval Delay": 1,
|
|
"Reinvest": 1,
|
|
"Worst Case": 0,
|
|
"Starting Value": 1000
|
|
}
|
|
|
|
optimization_settings = {
|
|
"Start Interval": 0,
|
|
"Intervals in Period": 0, # Use 0 to use all available data
|
|
"Short SMA Range": [1, 50],
|
|
"Short SMA Step": 1,
|
|
"Long SMA Range": [2, 100],
|
|
"Long SMA Step": 1,
|
|
"Offset Percent Range": [0.5, 5],
|
|
"Offset Percent Step": 0.5,
|
|
"Optimization Combinations": [], # Used for multithreading, it is an array of every possible combination to test then report back the best.
|
|
"Interval Delay": 1,
|
|
"Reinvest": 1,
|
|
"Worst Case": 0,
|
|
"Minimum Intervals Per Trade": 112, # 7 hours per trading day, 14 30m intervals, 28 15min intervals, 84 5min intervals
|
|
"Starting Value": 10000, # Only for optimization algorithm
|
|
"Multithreaded Training": 1, # Should we use multithreading
|
|
"Pools": 40, # How many pools to use for multithreading
|
|
"Graph Optimizations": 0 # Whether or not we should graph a plot of the different optimization values
|
|
}
|
|
|
|
rollover_trading_settings = {
|
|
"Start Interval": 0,
|
|
"Intervals to Test": 0, # How many data points from our available data to use. Use 0 to use all available data
|
|
"Training Period Width": 600, # 7 hours per trading day, 1 week = 140, 1 month = 600, 6 months = 2400 ***Rough numbers
|
|
"Trading Period Width": 280,
|
|
"Starting Value": 1000, # This is used to actually trade during our trading interval. The Starting Value under optimization settings is used just for it to get the best parameters
|
|
"Just Final": 0 # If we want to be able to plot or just want the final value
|
|
}
|
|
|
|
#import cProfile
|
|
#cProfile.run('main(stock_settings, trading_settings, optimization_settings, rollover_trading_settings, 3)')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
start_time = time.time()
|
|
main(stock_settings, trading_settings, optimization_settings, rollover_trading_settings, 3)
|
|
print("--- %s seconds ---" % (time.time() - start_time))
|