Initial Commit

This commit is contained in:
judsonupchurch 2024-08-14 14:10:18 -05:00
commit 3be2d2191d
28 changed files with 3611 additions and 0 deletions

View File

@ -0,0 +1,51 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.5 Chrome/126.0.6478.183 Electron/31.3.0 Safari/537.36" version="24.7.5">
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
<mxGraphModel dx="792" dy="1188" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-2" value="" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-3" target="WIyWlLk6GJQsqaUBKTNV-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-3" value="Lamp doesn&#39;t work" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="160" y="80" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-4" value="Yes" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-6" target="WIyWlLk6GJQsqaUBKTNV-10" edge="1">
<mxGeometry y="20" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-5" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-6" target="WIyWlLk6GJQsqaUBKTNV-7" edge="1">
<mxGeometry y="10" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-6" value="Lamp&lt;br&gt;plugged in?" style="rhombus;whiteSpace=wrap;html=1;shadow=0;fontFamily=Helvetica;fontSize=12;align=center;strokeWidth=1;spacing=6;spacingTop=-4;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="170" y="170" width="100" height="80" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-7" value="Plug in lamp" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="320" y="190" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-8" value="No" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-10" target="WIyWlLk6GJQsqaUBKTNV-11" edge="1">
<mxGeometry x="0.3333" y="20" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-9" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-10" target="WIyWlLk6GJQsqaUBKTNV-12" edge="1">
<mxGeometry y="10" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-10" value="Bulb&lt;br&gt;burned out?" style="rhombus;whiteSpace=wrap;html=1;shadow=0;fontFamily=Helvetica;fontSize=12;align=center;strokeWidth=1;spacing=6;spacingTop=-4;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="170" y="290" width="100" height="80" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-11" value="Repair Lamp" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="160" y="430" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-12" value="Replace Bulb" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="320" y="310" width="120" height="40" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

208
Account_Information.py Normal file
View File

@ -0,0 +1,208 @@
from copy import deepcopy
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Orders import GenericLimitOrder, BuyOrder, SellOrder, OrderFilledResponse
from datetime import datetime
class AccountInformation():
def __init__(self):
self._open_orders: list['GenericLimitOrder'] = []
self._filled_orders: list['GenericLimitOrder'] = []
self._cancelled_orders: list['GenericLimitOrder'] = []
# For each symbol, have a StockPosition() object
self._cumulative_stock_positions: dict[str, CumulativeStockPosition] = {}
self._total_cash: float = 0.0
self._settled_cash: float = 0.0
self._unsettled_cash: float = 0.0
self._needed_cash: float = 0.0
# General functions
def __str__(self) -> str:
return (f"Total Cash: {self._total_cash}\n"
f"Settled Cash: {self._settled_cash}\n"
f"Unsettled Cash: {self._unsettled_cash}\n"
f"Open Orders: {len(self._open_orders)}\n"
f"Filled Orders: {len(self._filled_orders)}\n"
f"Cancelled Orders: {len(self._cancelled_orders)}\n")
# CASH functions ############################################################
def deposit_cash(self, amount: float) -> float:
'''Returns the current cash amount after deposit'''
self._total_cash += amount
return self._total_cash
def calc_needed_cash(self) -> None:
'''Calcs the cash needed to execute the open orders'''
pass
@property
def needed_cash(self) -> float:
self.calc_needed_cash()
return self._needed_cash
@property
def total_cash(self) -> float:
return self._total_cash
@property
def settled_cash(self) -> float:
return self._settled_cash
@property
def unsettled_cash(self) -> float:
return self._unsettled_cash
# ORDER functions #########################################################
@property
def open_orders(self) -> list['GenericLimitOrder']:
return self._open_orders
def add_open_order(self, order: 'GenericLimitOrder') -> None:
'''Adds the order to the list of open orders'''
self._open_orders.append(order)
def fill_open_order(self, order_id: int) -> int:
'''Moves the order specified by order_id from open_orders to filled_orders
Returns 0 on success. 1 if not'''
for order in self._open_orders:
if order.id == order_id:
self._filled_orders.append(order)
self._open_orders.remove(order)
return 0 # Success
return 1 # Order not found
def cancel_open_order(self, order_id: int) -> int:
'''Moves the order specified by order_id from open_orders to cancelled_orders
Returns 0 on success, 1 if not'''
for order in self._open_orders:
if order.id == order_id:
self._cancelled_orders.append(order)
self._open_orders.remove(order)
return 0 # Success
return 1 # Order not found
# STOCK POSITION functions ###################################################
def add_position_from_filled_response(self, response: 'OrderFilledResponse', method: str = "fifo") -> int:
'''Given a response, determine how it plays into the CumulativeStockPosition.
First calculate the current position (long or short). If the response is adding to the position,
just append a position. If it is taking away from the position (making it more neutral), determine which
previous filled response to take away from.
returns the number of shares closed'''
symbol = response.symbol
if symbol not in self._cumulative_stock_positions:
self._cumulative_stock_positions[symbol] = CumulativeStockPosition(symbol)
quantity_closed = self._cumulative_stock_positions[symbol].add_position_from_filled_response(response, method)
return quantity_closed
class CumulativeStockPosition():
'''Holds the current cumulative position regarding a single stock.
This will handle which shares are sold (earliest acquired, cheapest, etc)'''
def __init__(self, symbol: str):
self._symbol = symbol
# Keep track of the invidual positions for this stock
# Only the responses that actively contribute to the cumulative position
self._single_stock_positions: list[SingleStockPosition] = []
self._quantity: float = 0.0 # Total number of shares we are long or short
self._cost_basis: float = 0.0
self._cost_per_share: float = 0.0
def calculate_cumulative_position(self) -> None:
'''Calculate the total quantity, cost basis, and cost per
share of the current single positions'''
self._quantity = sum(position.quantity for position in self._single_stock_positions)
self._cost_basis = sum(position.quantity * position.cost_per_share for position in self._single_stock_positions)
self._cost_per_share = self._cost_basis / self._quantity if self._quantity != 0 else 0.0
def close_position(self, new_position: 'SingleStockPosition', method: str = "fifo") -> float:
'''fifo = close the first placed position
lifo = close the more position
cheapest = close cheapest first,
priciest = close most expensive first
max_loss = close the stock to maximize losses
max_gain = close the stock to maximize gains
returns the number of shares closed'''
quantity_to_close = abs(new_position.quantity)
quantity_closed = 0
while quantity_closed < quantity_to_close and self._single_stock_positions:
if method == "fifo":
position_to_use = self._single_stock_positions[0]
elif method == "lifo":
position_to_use = self._single_stock_positions[-1]
elif method == "cheapest":
position_to_use = min(self._single_stock_positions, key=lambda x: x.cost_per_share)
elif method == "priciest":
position_to_use = max(self._single_stock_positions, key=lambda x: x.cost_per_share)
elif method == "max_loss":
position_to_use = min(self._single_stock_positions, key=lambda x: x.cost_per_share - new_position.cost_per_share)
elif method == "max_gain":
position_to_use = max(self._single_stock_positions, key=lambda x: x.cost_per_share - new_position.cost_per_share)
else:
raise ValueError("Incorrectly specified method for closing position")
if quantity_to_close < position_to_use.quantity:
position_to_use.quantity -= quantity_to_close
quantity_closed += quantity_to_close
break
else:
quantity_to_close -= position_to_use.quantity
quantity_closed += position_to_use.quantity
self._single_stock_positions.remove(position_to_use)
if quantity_closed < abs(new_position.quantity):
new_position.quantity = -quantity_to_close
self._single_stock_positions.append(new_position)
return quantity_closed
def make_single_stock_position(self, response: 'OrderFilledResponse') -> 'SingleStockPosition':
single_stock_position = SingleStockPosition(
symbol = response.symbol,
matching_order_id = response.matching_order_id,
datetime_filled = response.datetime_filled,
quantity = response.quantity,
cost_per_share = response.cost_per_share
)
return single_stock_position
def add_position_from_filled_response(self, response: 'OrderFilledResponse', method: str = "fifo") -> int:
'''Given a response, determine how it plays into the CumulativeStockPosition.
First calculate the current position (long or short). If the response is adding to the position,
just append a position. If it is taking away from the position (making it more neutral), determine which
previous filled response to take away from.
returns the number of shares closed'''
# Make a SingleStockPosition from the filled response
new_position = self.make_single_stock_position(response)
quantity_closed = 0
# Determine if it adds to the current position or takes away
if self._quantity * new_position.quantity >= 0: # Same sign means the response is adding
self._single_stock_positions.append(new_position)
else: # The response is taking away
quantity_closed = self.close_position(new_position, method)
# Recalculate current position
self.calculate_cumulative_position()
return quantity_closed
class SingleStockPosition():
def __init__(self, symbol: str, matching_order_id: int, datetime_filled: 'datetime', quantity: float, cost_per_share: float):
self.symbol = symbol
self.matching_order_id = matching_order_id
self.datetime_filled = datetime_filled
self.quantity = quantity
self.cost_per_share = cost_per_share

115
Brokerage_Simulation.py Normal file
View File

@ -0,0 +1,115 @@
'''
The brokerage simulator needs to simulate what IBKR does for the user.
1. Submitting "orders" to the market
2. Determining when orders would have been filled
3. Keeping track of account information such as positions, cash balances, etc
4. This brokerage simulation is not responsible for feeding data to the algorithm
It does need to get data from the
'''
from datetime import datetime
from ibapi.contract import Contract
from Account_Information import AccountInformation
from Orders import GenericLimitOrder, BuyOrder, SellOrder, OrderFilledResponse
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Common import TickData
from strategies.Strategy_Template import StrategyTemplate
class BrokerageSimulation():
def __init__(self):
self._account_information = AccountInformation()
self._current_tick_data: 'TickData' = None
self._strategy: 'StrategyTemplate' = None
self._current_order_id: int = 0
# Internal functions
def calculate_commission(self, order: GenericLimitOrder) -> float:
return 0.0
def make_filled_response(self, order: GenericLimitOrder) -> OrderFilledResponse:
'''Execute an order. Return an OrderFilledResponse for the order.
Assume the stock filled at the limit price of the order placed'''
order_filled_response = OrderFilledResponse(
symbol = order.symbol,
matching_order_id = order.id,
datetime_filled = TickData.open_datetime,
quantity = order.quantity,
price = order.limit_price,
commission = self.calculate_commission(order)
)
return order_filled_response
# General functions
def print_account_information(self) -> None:
current_datetime = self._current_tick_data.open_datetime
print(f"Account information on {current_datetime}")
print(self._account_information)
print()
def get_current_account_info(self) -> AccountInformation:
return self._account_information
# Functions for the ModelSimulator to interact with
def attach_strategy(self, strategy: 'StrategyTemplate') -> None:
self._strategy = strategy
# Add the internal account_information so that the strategy has access to it without constantly pulling
self._strategy.attach_account_information(self._account_information)
def set_current_tick_data(self, tick_data: 'TickData') -> None:
self._current_tick_data = tick_data
def check_open_orders(self) -> list[OrderFilledResponse]:
'''For every open order, check if it can be filled. If so, fill it.
Send the list to the attached strategy.
Return a list of OrderFilledResponse for every order filled
'''
filled_responses: list[OrderFilledResponse] = []
open_orders = self._account_information.open_orders
for order in open_orders:
if order.is_fillable(tick_data=self._current_tick_data):
order_id = order.id
# Generate the filled response to send to the strategy
filled_response = self.make_filled_response(order)
filled_responses.append(filled_response)
# Move the order from open_orders to filled_orders
status = self._account_information.fill_open_order(order_id=order_id)
# Add the OrderFilledResponse to the AccountInformation._cumulative_stock_positions[symbol]
self._account_information.add_position_from_filled_response(filled_response)
# Send the filled_responses to the strategy so it knows the order was filled
return filled_responses
# Functions for the strategy to interact with
def deposit_cash(self, amount: float) -> None:
self._account_information.deposit_cash(amount)
def submit_order(self, order: GenericLimitOrder) -> OrderFilledResponse:
'''Adds an order to open_orders'''
order_id = self._current_order_id
self._current_order_id += 1
order.change_id(order_id)
self._account_information.add_open_order(order)
def cancel_order(self, order_id: int) -> int:
'''Cancels a current order that is open based on the order_id.
Returns 0 on success, 1 on order_id cannot be found, 2 on the order_id has already been filled
'''
pass

56
Common.py Normal file
View File

@ -0,0 +1,56 @@
from datetime import datetime
class TickData():
def __init__(self, symbol: str, open_datetime: 'datetime', open: float, high: float, low: float, close: float, volume: int, count: int, source: str):
self._symbol = symbol
self._open_datetime = open_datetime
self._open = open
self._high = high
self._low = low
self._close = close
self._volume = volume
self._count = count
self._source = source
def __str__(self) -> str:
return (f"Symbol: {self._symbol}\n"
f"Open Datetime: {self._open_datetime}\n"
f"Open: {self._open}\n"
f"High: {self._high}\n"
f"Low: {self._low}\n"
f"Close: {self._close}\n"
f"Volume: {self._volume}\n"
f"Count: {self._count}\n"
f"Source: {self._source}")
@property
def open_datetime(self) -> 'datetime':
return self._open_datetime
@property
def open(self) -> float:
return self._open
@property
def high(self) -> float:
return self._high
@property
def low(self) -> float:
return self._low
@property
def close(self) -> float:
return self._close
@property
def volume(self) -> int:
return self._volume
@property
def count(self) -> int:
return self._count
def main():
tick = TickData(0,0,0,0,0)
tick._close = 5
print(tick.close)
if __name__ == "__main__":
main()

181
Historical_Data_Accessor.py Normal file
View File

@ -0,0 +1,181 @@
'''
This file describes how the brokerage simulation and algorithm models can access data from MongoDB's historical data records
'''
from pymongo import MongoClient
from pymongo.collection import Collection
from datetime import datetime
from pytz import timezone
from ibapi.contract import Contract
from Common import TickData
MONGODB_IP = "10.0.0.34"
MONGODB_PORT = 27017
MONGODB_HISTORICAL_DATA_DB = "Historical_Stock_Data"
MONGODB_STOCK_INFORMATION_DB = "Stock_Information"
MONGODB_GENERAL_INFORMATION_COLLECTION = "General_Information"
'''
TODO
1.
'''
class HistoricalDataAccessor():
'''The accessor houses the mongo connection and provides a convenient way to access a data for any stock'''
def __init__(self):
self._status = 0
if self.connect_to_mongo() != 0:
self._status = 1
@property
def status(self) -> int:
return self._status
def connect_to_mongo(self) -> int:
'''Connects to mongodb as specified with ip and port'''
try:
self._client = MongoClient(MONGODB_IP, MONGODB_PORT)
return 0
except Exception as exc:
print(f"Exception occurred trying to connect to MongoDB: {exc}")
return 1
def get_stock_information(self, symbol: str) -> dict:
'''Gets the stock information for a specific stock
Returns dict of stock information on success, None if not'''
try:
self.information_db = self._client[MONGODB_STOCK_INFORMATION_DB]
if MONGODB_GENERAL_INFORMATION_COLLECTION not in self.information_db.list_collection_names():
print(f"{MONGODB_GENERAL_INFORMATION_COLLECTION} not in MongoDB {MONGODB_STOCK_INFORMATION_DB} database")
return None
else:
self.information_collection = self.information_db[MONGODB_GENERAL_INFORMATION_COLLECTION]
query = {"Symbol": symbol}
projection = {
"_id": 1, # Include every field unless specified otherwise
"Historical_Stock_Data.Verification": 0 # Do not include this field
}
stock_information = self.information_collection.find_one(query, projection)
return stock_information
except Exception as exc:
print(f"Exception occurred trying to get stock information for {symbol}: {exc}")
return None
def connect_to_historical_data_collection(self, symbol:str) -> Collection:
'''Connects to the collection for a specific stock.
Returns Collection on success, None if not'''
try:
historical_db = self._client[MONGODB_HISTORICAL_DATA_DB]
if symbol not in historical_db.list_collection_names():
print(f"{symbol} not in MongoDB {MONGODB_HISTORICAL_DATA_DB} database")
return None
else:
collection = historical_db[symbol]
return collection
except Exception as exc:
print(f"Exception occurred trying to connect to historical data collection for {symbol}: {exc}")
return None
def get_single_datapoint(self, symbol: str, datetime_to_get: datetime) -> dict:
'''Gets a single datapoint with the specific datetime.
Returns datapoint document if successful. If not, returns None'''
try:
historical_collection = self.connect_to_historical_data_collection(symbol)
query = {
"Date": datetime_to_get
}
doc = historical_collection.find_one(query)
return doc
except Exception as exc:
print(f"Exception occurred trying to get single datapoint from {MONGODB_HISTORICAL_DATA_DB}.{symbol} with Date = {datetime_to_get}: {exc}")
return None
def get_range_datapoints(self, symbol: str, datetime_first: datetime, datetime_last: datetime, proj:dict={}, extra_query:dict={}) -> list[dict]:
'''Gets a list of documents from first to last inclusive.
Returns list of documents if successful. If not, returns None'''
try:
historical_collection = self.connect_to_historical_data_collection(symbol)
query = {
"Date": {"$gte": datetime_first, "$lte": datetime_last},
}
query.update(extra_query)
if proj != {}:
docs = list(historical_collection.find(query, proj))
else:
docs = list(historical_collection.find(query))
return docs
except Exception as exc:
print(f"Exception occurred trying to get range of datapoins from {MONGODB_HISTORICAL_DATA_DB}.{symbol} with dates between {datetime_first} and {datetime_last}: {exc}")
return None
def is_datapoint_available(self, symbol: str, datetime_to_check: datetime) -> bool:
'''Checks if a datapoint is available for the datetime provided'''
doc = self.get_single_datapoint(symbol, datetime_to_check)
if doc is not None:
return True
else:
return False
def convert_datapoint_to_tick_data(self, symbol: str, datapoint: dict) -> TickData:
tick_data = TickData(
symbol = symbol,
open_datetime = datapoint.get("Date", None),
open = datapoint.get("Open", 0),
high = datapoint.get("High", 0),
low = datapoint.get("Low", 0),
close = datapoint.get("Close", 0),
volume = datapoint.get("Volume", 0),
count = datapoint.get("Count", 0),
source = datapoint.get("Source", None)
)
return tick_data
def get_single_tick_data(self, symbol: str, datetime_to_get: datetime) -> TickData:
datapoint = self.get_single_datapoint(symbol, datetime_to_get)
# Convert the dictionaty to the TickData
tick_data = self.convert_datapoint_to_tick_data(symbol, datapoint)
return tick_data
def get_range_tick_data(self, symbol: str, datetime_first: datetime, datetime_last: datetime, proj:dict={}, extra_query:dict={}) -> list[TickData]:
datapoints = self.get_range_datapoints(symbol, datetime_first, datetime_last, proj, extra_query)
tick_data_list: list[TickData] = []
for datapoint in datapoints:
tick_data = self.convert_datapoint_to_tick_data(symbol, datapoint)
tick_data_list.append(tick_data)
return tick_data_list
def main():
accessor = HistoricalDataAccessor()
# print(f"Status = {accessor.get_status()}")
# print(accessor.stock_information)
symbol = "AMD"
EASTERN = timezone('US/Eastern')
datetime_first = EASTERN.localize(datetime(year=2020, month=1, day=8, hour=12, minute=30))
# doc = accessor.get_single_datapoint(symbol, datetime_first)
# print(doc)
tick_data = accessor.get_single_tick_data(symbol, datetime_first)
print(tick_data)
# datetime_last = EASTERN.localize(datetime(year=2020, month=1, day=8, hour=13, minute=30))
# docs = accessor.get_range_datapoints(datetime_first, datetime_last)
# print(docs)
# datetime_last = EASTERN.localize(datetime(year=2020, month=1, day=8, hour=13, minute=30))
# tick_datas = accessor.get_range_tick_data(symbol, datetime_first, datetime_last)
# for i in tick_datas:
# print(i)
if __name__ == "__main__":
main()

75
Logic Diagram.drawio Normal file
View File

@ -0,0 +1,75 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/24.7.5 Chrome/126.0.6478.183 Electron/31.3.0 Safari/537.36" version="24.7.5">
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
<mxGraphModel dx="1185" dy="684" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-2" value="" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-3" target="WIyWlLk6GJQsqaUBKTNV-6" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-3" value="Lamp doesn&#39;t work" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="150" y="430" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-4" value="Yes" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-6" target="WIyWlLk6GJQsqaUBKTNV-10" edge="1">
<mxGeometry y="20" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-5" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-6" target="WIyWlLk6GJQsqaUBKTNV-7" edge="1">
<mxGeometry y="10" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-6" value="Lamp&lt;br&gt;plugged in?" style="rhombus;whiteSpace=wrap;html=1;shadow=0;fontFamily=Helvetica;fontSize=12;align=center;strokeWidth=1;spacing=6;spacingTop=-4;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="160" y="520" width="100" height="80" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-7" value="Plug in lamp" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="310" y="540" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-8" value="No" style="rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;edgeStyle=orthogonalEdgeStyle;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-10" target="WIyWlLk6GJQsqaUBKTNV-11" edge="1">
<mxGeometry x="0.3333" y="20" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-9" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;jettySize=auto;orthogonalLoop=1;fontSize=11;endArrow=block;endFill=0;endSize=8;strokeWidth=1;shadow=0;labelBackgroundColor=none;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-10" target="WIyWlLk6GJQsqaUBKTNV-12" edge="1">
<mxGeometry y="10" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-10" value="Bulb&lt;br&gt;burned out?" style="rhombus;whiteSpace=wrap;html=1;shadow=0;fontFamily=Helvetica;fontSize=12;align=center;strokeWidth=1;spacing=6;spacingTop=-4;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="160" y="640" width="100" height="80" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-11" value="Repair Lamp" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="150" y="780" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-12" value="Replace Bulb" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="310" y="660" width="120" height="40" as="geometry" />
</mxCell>
<mxCell id="AMCs8WrKC__wpCfdSFZW-0" value="&lt;br&gt;&lt;b&gt;ModelSimulation&lt;/b&gt;" style="swimlane;fontStyle=0;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=55;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;marginBottom=0;html=1;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="160" width="200" height="183" as="geometry" />
</mxCell>
<mxCell id="AMCs8WrKC__wpCfdSFZW-1" value="attributes" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;" vertex="1" parent="AMCs8WrKC__wpCfdSFZW-0">
<mxGeometry y="55" width="200" height="20" as="geometry" />
</mxCell>
<mxCell id="AMCs8WrKC__wpCfdSFZW-2" value="attribute1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;" vertex="1" parent="AMCs8WrKC__wpCfdSFZW-0">
<mxGeometry y="75" width="200" height="20" as="geometry" />
</mxCell>
<mxCell id="AMCs8WrKC__wpCfdSFZW-3" value="inherited attribute2" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontColor=#808080;whiteSpace=wrap;" vertex="1" parent="AMCs8WrKC__wpCfdSFZW-0">
<mxGeometry y="95" width="200" height="20" as="geometry" />
</mxCell>
<mxCell id="AMCs8WrKC__wpCfdSFZW-4" value="..." style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;" vertex="1" parent="AMCs8WrKC__wpCfdSFZW-0">
<mxGeometry y="115" width="200" height="20" as="geometry" />
</mxCell>
<mxCell id="AMCs8WrKC__wpCfdSFZW-5" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;" vertex="1" parent="AMCs8WrKC__wpCfdSFZW-0">
<mxGeometry y="135" width="200" height="8" as="geometry" />
</mxCell>
<mxCell id="AMCs8WrKC__wpCfdSFZW-6" value="operations" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;" vertex="1" parent="AMCs8WrKC__wpCfdSFZW-0">
<mxGeometry y="143" width="200" height="20" as="geometry" />
</mxCell>
<mxCell id="AMCs8WrKC__wpCfdSFZW-7" value="operation1" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;" vertex="1" parent="AMCs8WrKC__wpCfdSFZW-0">
<mxGeometry y="163" width="200" height="20" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

137
Model_Simulation.py Normal file
View File

@ -0,0 +1,137 @@
'''
This file houses the main simulation code that is going to test the algorithms.
It needs to be able to manage the data available to the algorithm,
help the brokerage simulator with stock data requests, and iterate through time.
'''
from datetime import datetime, timedelta
from pytz import timezone
EASTERN = timezone('US/Eastern')
from Historical_Data_Accessor import HistoricalDataAccessor
from Orders import GenericLimitOrder, BuyOrder, SellOrder, OrderFilledResponse
from Common import TickData
from Brokerage_Simulation import BrokerageSimulation, AccountInformation
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from strategies.Strategy_Template import StrategyTemplate
from strategies.Random_Strategy import RandomStrategy
class ModelSimulation():
'''Simulation to test a model over historical data. Only tests a model with 1 stock symbol as provided.'''
def __init__(self, symbol: str, datetime_start: datetime, datetime_end: datetime, datetime_delta: timedelta, strategy: 'StrategyTemplate', starting_cash: float):
'''Specify the datetime to start the simulation and end it'''
self._symbol = symbol
self._datetime_start = datetime_start
self._datetime_end = datetime_end
self._datetime_delta = datetime_delta
self._strategy = strategy
self._status = 0
self._historical_data_accessor: HistoricalDataAccessor = HistoricalDataAccessor()
if self._historical_data_accessor.status != 0: # Something went wrong
self._status = 1
if self.check_datetimes() == False: # Not valid datetimes
self._status = 1
# Make a BrokerageSimulation instance
self._brokerage_simulation: BrokerageSimulation = BrokerageSimulation()
self._brokerage_simulation.deposit_cash(starting_cash)
self._brokerage_simulation.attach_strategy(self._strategy)
self._strategy.attach_brokerage_simulation(self._brokerage_simulation)
@property
def status(self) -> int:
return self._status
def check_datetimes(self) -> bool:
'''Check the start and end datetimes to make sure we have datapoints for them.
Also, make sure start is before end.
Return True for good datetimes and data available, false for bad datetimes or no data'''
# Check if there is a datetime provided. If not, set it to eastern
if self._datetime_start.tzinfo is None:
self._datetime_start = EASTERN.localize(self._datetime_start)
if self._datetime_end.tzinfo is None:
self._datetime_end = EASTERN.localize(self._datetime_end)
# Remove the seconds and microseconds
self._datetime_start = self._datetime_start.replace(second=0, microsecond=0)
self._datetime_end = self._datetime_end.replace(second=0, microsecond=0)
# Check start is before end
if self._datetime_start >= self._datetime_end:
return False
# Request if data is available using the data accessor
if (self._historical_data_accessor.is_datapoint_available(self._symbol, self._datetime_start) == False
or self._historical_data_accessor.is_datapoint_available(self._symbol, self._datetime_end) == False):
return False
# Datetimes are correctly ordered and we have data for them
return True
def run(self) -> None:
'''Main function to run'''
current_datetime = self._datetime_start
while current_datetime <= self._datetime_end:
# Pull the current datetime's data from HistoricalDataAccessor
new_data = self._historical_data_accessor.get_single_tick_data(self._symbol, current_datetime)
# Give the strategy and broker simulator the data for this datetime
self._brokerage_simulation.set_current_tick_data(new_data)
# Have the broker simulator check open orders for execution
# If there are executed orders, relay the respose to the strategy
filled_responses = self._brokerage_simulation.check_open_orders()
# Set the new datetime for the strategy
self._strategy.set_current_datetime(current_datetime)
# Have the strategy send new buy/sell orders to the broker simulator for this datetime
new_orders = self._strategy.react_to_market()
# Add data regarding the account and brokerage simulator to an array for data analysis at the end
# Include things like account values, open orders, executed orders, etc
# The strategy should do its own data storage for important parts about its analysis
# TODO
self._brokerage_simulation.print_account_information()
# Iterate to next datetime
current_datetime += self._datetime_delta
# With the iterations done, do some calculations on the performance of the model
# Average monthly ROI, minimums, maximums, how it beats the underlying performance, etc
# Save the data to files
# Output general information from the sim
def main():
datetime_first = EASTERN.localize(datetime(year=2020, month=1, day=8, hour=12, minute=30))
datetime_last = EASTERN.localize(datetime(year=2020, month=1, day=8, hour=13, minute=30))
symbol = "AMD"
strategy = RandomStrategy(symbol)
model_simulation = ModelSimulation(
symbol=symbol,
datetime_start=datetime_first,
datetime_end=datetime_last,
datetime_delta=timedelta(minutes=1),
strategy=strategy,
starting_cash=10_000
)
model_simulation.run()
if __name__ == "__main__":
main()

82
Orders.py Normal file
View File

@ -0,0 +1,82 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from Common import TickData
from datetime import datetime
class GenericLimitOrder(ABC):
def __init__(self, symbol: str, datetime_placed: 'datetime', quantity: float, limit_price: float, order_id: int = None):
self._symbol = symbol
self._datetime_placed = datetime_placed
self._quantity = quantity
self._limit_price = limit_price
self._needed_cost = quantity * limit_price
self._id = order_id # Starts as None until the BrokerageSimulator returns the order_id
@property
def symbol(self) -> str:
return self._symbol
@property
def datetime_placed(self) -> 'datetime':
return self._datetime_placed
@property
def quantity(self) -> float:
return self._quantity
@property
def limit_price(self) -> float:
return self._limit_price
@property
def needed_cost(self) -> float:
return self._needed_cost
@property
def id(self) -> int:
return self._id
def change_id(self, new_id: int) -> None:
'''Change the id after creation. Only allowed if the current id is None'''
if self._id is not None:
raise ValueError("Trying to change id of order that already has id")
else:
self._id = new_id
@abstractmethod
def is_fillable(self, tick_data: 'TickData') -> bool:
'''Return whether or not the order should be filled based on self.limit_price and the low and high price of the tick
Overload this operator'''
pass
class BuyOrder(GenericLimitOrder):
'''Buy order'''
def __init__(self, symbol: str, datetime_placed: 'datetime', quantity: float, limit_price: float, order_id: int):
super().__init__(symbol, datetime_placed, quantity, limit_price, order_id)
def is_fillable(self, tick_data: 'TickData') -> bool:
'''Return whether or not the order should be filled based on self.limit_price and the low and high price of the tick'''
if tick_data.low <= self._limit_price:
return True
else:
return False
class SellOrder(GenericLimitOrder):
'''Sell order'''
def __init__(self, symbol: str, datetime_placed: 'datetime', quantity: float, limit_price: float, order_id: int):
super().__init__(symbol, datetime_placed, quantity, limit_price, order_id)
def is_fillable(self, tick_data: 'TickData') -> bool:
'''Return whether or not the order should be filled based on self.limit_price and the low and high price of the tick'''
if tick_data.high >= self._limit_price:
return True
else:
return False
class OrderFilledResponse():
def __init__(self, symbol: str, matching_order_id: int, datetime_filled: 'datetime', quantity: float, price: float, commission: float):
self.symbol = symbol
self.matching_order_id = matching_order_id
self.datetime_filled = datetime_filled
self.quantity = quantity
self.price = price
self.commission = commission
self.total_cost = quantity * price + commission
self.cost_per_share = self.total_cost / self.quantity

16
Todo Normal file
View File

@ -0,0 +1,16 @@
Account_Information.py
CumulativeStockPosition
Custom print function using __str__
Brokerage_Simulation.py
BrokerageSimulation
Random_Strategy.py
RandomStrategy
Implement checking the current position using self._account_information._cumulative_stock_positions
Model_Simulation.py
ModelSimulation
Add data tracking

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,48 @@
from Orders import BuyOrder, SellOrder
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from datetime import datetime
from Common import TickData
from Brokerage_Simulation import BrokerageSimulation
from Account_Information import AccountInformation
from Orders import GenericLimitOrder, OrderFilledResponse
from strategies.Strategy_Template import StrategyTemplate
import random
class RandomStrategy(StrategyTemplate):
def __init__(self, symbol: str):
super().__init__(symbol)
self.status: int = 0 # 0 for no position, 1 for long, -1 for short
def react_to_market(self) -> list['GenericLimitOrder']:
positive_trend = random.choice([True, False])
order: GenericLimitOrder = None
if self.status == 0: # No current position
if positive_trend:
# Make a buy order
order: BuyOrder = None
elif self.status == 1: # Already long
if positive_trend:
pass
else:
# Make a sell order to close position
pass
elif self.status == -1: # We are short
if positive_trend:
# Make a buy order to close position
pass
else:
pass
return [order]

View File

@ -0,0 +1,56 @@
from abc import ABC, abstractmethod
from Account_Information import AccountInformation
from Orders import GenericLimitOrder, BuyOrder, SellOrder, OrderFilledResponse
from Historical_Data_Accessor import HistoricalDataAccessor
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from datetime import datetime
from Common import TickData
from Brokerage_Simulation import BrokerageSimulation
class StrategyTemplate(ABC):
def __init__(self, symbol: str):
self._symbol = symbol
self._brokerage_simulation: 'BrokerageSimulation' = None
self._status = 0
self._historical_data_accessor: HistoricalDataAccessor = HistoricalDataAccessor()
if self._historical_data_accessor.status != 0: # Something went wrong
self._status = 1
self._current_datetime: datetime = None
self._account_information: AccountInformation = None
def attach_account_information(self, account_information: 'AccountInformation') -> None:
'''Link an AccountInformation() object to the Strategy.
The Brokerage Simulation holds the true account information and makes the changes.'''
self._account_information = account_information
def attach_brokerage_simulation(self, brokerage_simulation: 'BrokerageSimulation') -> None:
self._brokerage_simulation = brokerage_simulation
def set_current_datetime(self, new_datetime: 'datetime') -> None:
self._current_datetime = new_datetime
@abstractmethod
def react_to_market(self) -> list['GenericLimitOrder']:
'''Main function that performs the analysis and creates orders based on market conditions.
Using the _current_datetime, perform whatever past data analysis needed.
Generate buy or sell orders based on the analysis as the strategy sees fit.
Must be overridden'''
# Make buy or sell orders
# Submit orders with self._brokerage_simulation.submit_order()
# Return the list of orders
pass

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,201 @@
import datetime as dt
import plotly.express as plt
import plotly.subplots as subplt
import plotly.graph_objects as goplt
import numpy as np
import pandas as pd
import math
import time
from multiprocessing import Pool
from functools import partial
import itertools
import statistics as stat
def read_ticker_data(ticker_settings={}):
'''Reads in the ticker data from a csv file'''
data = {
"Datetime": [],
"Epoch": [],
"Open": [],
"High": [],
"Low": [],
"Close": [],
"Points": 0
}
file = open(f"..\..\Raw Data\{ticker_settings['Ticker']} {ticker_settings['Period']} {ticker_settings['Interval']} Data.csv")
file_list = file.readlines()
i = 0
header = True
for line in file_list:
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["Epoch"].append(int(dt.datetime.strptime(line_elements[0], "%Y-%m-%d %H:%M:%S").timestamp()))
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(sma_settings={}, raw_data=[], reference_data_container=[]):
'''Generates the SMA of the raw data provided. Appends it to a data array passed by reference.'''
for i in range(sma_settings["Start Index"], sma_settings["Stop Index"]):
if (sma_settings["Length"] != 0):
reference_data_container.append(round(sum(raw_data[i-sma_settings["Length"]:i]) / sma_settings["Length"], 3))
else:
reference_data_container.append(round(raw_data[i], 3))
def calculate_volatility(volatility_settings={}, raw_data=[], reference_data_container=[]):
'''Calculate the volatility of a given range of values'''
for i in range(volatility_settings["Start Index"], volatility_settings["Stop Index"]):
if (volatility_settings["Method"] == "stdev"):
reference_data_container.append(round(stat.pstdev(raw_data[i-volatility_settings["Length"]:i]) / raw_data[i] * 100, 3))
def generate_offset(offset_settings={}, raw_data=[], reference_data_container=[]):
'''Create an offset data array of a given dataset'''
for i in range(offset_settings["Start Index"], offset_settings["Stop Index"]):
reference_data_container.append(round(raw_data[i]*(1+offset_settings["Percent"]/100), 3))
def detect_crossover(crossover_settings={}, raw_data=[], reference_data_container=[]):
'''Look for indexes where two data arrays crossed over each other.
1 for first crosses over second, -1 for second crossing over first.'''
above = raw_data[0][crossover_settings["Start Index"]-1] > raw_data[1][crossover_settings["Start Index"]-1] # Get the initial state
for i in range(crossover_settings["Start Index"], crossover_settings["Stop Index"]):
if (raw_data[1][i] > raw_data[0][i] and above): # First crosses below second
reference_data_container.append(-1)
elif (raw_data[0][i] > raw_data[1][i] and not above): # First crosses above second
reference_data_container.append(1)
else:
reference_data_container.append(0)
#def detect_trend_reversal(reversal_settings={}, raw_data=[], reference_data_container=[]):
#def generate_action_events(action_settings={}, raw_data=[], reference_data_container=[]):
#def walking_forward_trader(walking_trader_settings={}, raw_data=[]):
#def single_model_optimization(model_optimization_settings={}, raw_data=[]):
#def rollover_trading_sim(rollover_settings={}, raw_data=[]):
def main(ticker_settings={}, parameter_ranges={}):
raw_ticker_data = read_ticker_data(ticker_settings)
# Let's generate the dictionary that will contain everything we calculated to save computation
previously_calculated_data = {}
# Get the SMA stuff
previously_calculated_data["SMA"] = {}
for length in range(parameter_ranges["Short SMA"][0], parameter_ranges["Long SMA"][1] + parameter_ranges["Short SMA"][2], parameter_ranges["Short SMA"][2]):
previously_calculated_data["SMA"][length] = {
"Close": list(itertools.repeat(0, length)) # SMA using length 5 of Close
}
# Get the volatility stuff
previously_calculated_data["Volatility"] = {}
for length in range(parameter_ranges["Volatility Length"][0], parameter_ranges["Volatility Length"][1] + parameter_ranges["Volatility Length"][2], parameter_ranges["Volatility Length"][2]):
previously_calculated_data["Volatility"][length] = {
"Close": list(itertools.repeat(0, length)) # SMA using length 5 of Close
}
# Get the percent offset stuff
total_percents = int((parameter_ranges["Percent Offset"][1] - parameter_ranges["Percent Offset"][0]) / parameter_ranges["Percent Offset"][2])
percent = parameter_ranges["Percent Offset"][0]
previously_calculated_data["Offset"] = {}
for i in range(total_percents):
previously_calculated_data["Offset"][percent] = {"SMA": {}}
for length in range(parameter_ranges["Short SMA"][0], parameter_ranges["Long SMA"][1] + parameter_ranges["Short SMA"][2], parameter_ranges["Short SMA"][2]):
previously_calculated_data["Offset"][percent]["SMA"][length] = {
"Close": list(itertools.repeat(0, length))
}
percent += round(parameter_ranges["Percent Offset"][2], 3)
# Get the volatility + pecent offset stuff
total_vol_scalars = int((parameter_ranges["Volatility Scalar"][1] - parameter_ranges["Volatility Scalar"][0]) / parameter_ranges["Volatility Scalar"][2])
vol_scalar = parameter_ranges["Volatility Scalar"][0]
previously_calculated_data["Volatility+Percent Offset"] = {}
for i in range(total_vol_scalars):
previously_calculated_data["Volatility+Percent Offset"][vol_scalar] = {"Volatility": {}}
for vol_length in range(parameter_ranges["Volatility Length"][0], parameter_ranges["Volatility Length"][1] + parameter_ranges["Volatility Length"][2], parameter_ranges["Volatility Length"][2]):
previously_calculated_data["Volatility+Percent Offset"][vol_scalar]["Volatility"][vol_length] = {"Offset": {}}
total_percents = int((parameter_ranges["Percent Offset"][1] - parameter_ranges["Percent Offset"][0]) / parameter_ranges["Percent Offset"][2])
percent = parameter_ranges["Percent Offset"][0]
for j in range(total_percents):
previously_calculated_data["Volatility+Percent Offset"][vol_scalar]["Volatility"][vol_length]["Offset"][percent] = {"SMA": {}}
for sma_length in range(parameter_ranges["Short SMA"][0], parameter_ranges["Long SMA"][1] + parameter_ranges["Short SMA"][2], parameter_ranges["Short SMA"][2]):
previously_calculated_data["Volatility+Percent Offset"][vol_scalar]["Volatility"][vol_length]["Offset"][percent]["SMA"][sma_length] = {
"Close": list(itertools.repeat(0, max(vol_length, sma_length)))
}
percent += round(parameter_ranges["Percent Offset"][2], 3)
vol_scalar += parameter_ranges["Volatility Scalar"][2]
sma_settings = {
"Length": 5,
"Start Index": 5, # Inclusive
"Stop Index": 8000 # Non inclusive
}
generate_sma(sma_settings, raw_ticker_data["Close"], previously_calculated_data["SMA"][sma_settings["Length"]]["Close"])
sma_settings = {
"Length": 3,
"Start Index": 3, # Inclusive
"Stop Index": 8000 # Non inclusive
}
generate_sma(sma_settings, raw_ticker_data["Close"], previously_calculated_data["SMA"][sma_settings["Length"]]["Close"])
volatility_settings = {
"Length": 30,
"Start Index": 30,
"Stop Index": 8000,
"Method": "stdev" # stdev, mae (mean absolute error), how much the last period covered the range of a larger period
}
calculate_volatility(volatility_settings, raw_ticker_data["Close"], previously_calculated_data["Volatility"][volatility_settings["Length"]]["Close"])
offset_settings = {
"Percent": 3,
"Start Index": 5,
"Stop Index": 8000,
}
print(previously_calculated_data["Offset"].keys())
generate_offset(offset_settings, previously_calculated_data["SMA"][5]["Close"], previously_calculated_data["Offset"][offset_settings["Percent"]]["SMA"][5]["Close"])
crossover_settings = {
"Start Index": 5,
"Stop Index": 8000
}
sma3_crosses_sma_5 = list(itertools.repeat(0, crossover_settings["Start Index"]))
detect_crossover(crossover_settings, [previously_calculated_data["SMA"][3]["Close"], previously_calculated_data["SMA"][5]["Close"]], sma3_crosses_sma_5)
print(sma3_crosses_sma_5)
ticker_settings = {
"Ticker": "RKLB",
"Period": "720d",
"Interval": "15m"
}
parameter_ranges = {
"Short SMA": [1, 20, 1],
"Long SMA": [2, 25, 1],
"Percent Offset": [0.25, 5, 0.25],
"Volatility Length": [30, 3000, 30],
"Volatility Scalar": [0, 5, 0.25]
}
if __name__ == "__main__":
start_time = time.time()
main(ticker_settings, parameter_ranges)
print("--- %s seconds ---" % (time.time() - start_time))

View File

@ -0,0 +1,810 @@
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))

View File

@ -0,0 +1,810 @@
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": "NVDA",
"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, 15],
"Short SMA Step": 1,
"Long SMA Range": [2, 75],
"Long SMA Step": 1,
"Offset Percent Range": [0.25, 5],
"Offset Percent Step": 0.25,
"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))

View File

@ -0,0 +1,309 @@
Optimizing Algorithm took 2.112065076828003 seconds ---
After Trading Interval 0. Start Training Interval: 0, Stop Training Interval: 600
Start Trading Interval: 600, Stop Trading Interval: 880
Algorithm Settings for Interval: [2, 3, 1.0]
Account value: $1069.906
Optimizing Algorithm took 2.173543691635132 seconds ---
After Trading Interval 1. Start Training Interval: 280, Stop Training Interval: 880
Start Trading Interval: 880, Stop Trading Interval: 1160
Algorithm Settings for Interval: [15, 41, 0.5]
Account value: $1089.985
Optimizing Algorithm took 2.2092628479003906 seconds ---
After Trading Interval 2. Start Training Interval: 560, Stop Training Interval: 1160
Start Trading Interval: 1160, Stop Trading Interval: 1440
Algorithm Settings for Interval: [10, 47, 1.0]
Account value: $1073.168
Optimizing Algorithm took 2.3272018432617188 seconds ---
After Trading Interval 3. Start Training Interval: 840, Stop Training Interval: 1440
Start Trading Interval: 1440, Stop Trading Interval: 1720
Algorithm Settings for Interval: [2, 15, 1.75]
Account value: $1120.812
Optimizing Algorithm took 2.31429386138916 seconds ---
After Trading Interval 4. Start Training Interval: 1120, Stop Training Interval: 1720
Start Trading Interval: 1720, Stop Trading Interval: 2000
Algorithm Settings for Interval: [4, 18, 1.25]
Account value: $1160.904
Optimizing Algorithm took 2.4123475551605225 seconds ---
After Trading Interval 5. Start Training Interval: 1400, Stop Training Interval: 2000
Start Trading Interval: 2000, Stop Trading Interval: 2280
Algorithm Settings for Interval: [2, 6, 1.25]
Account value: $1135.381
Optimizing Algorithm took 2.4362597465515137 seconds ---
After Trading Interval 6. Start Training Interval: 1680, Stop Training Interval: 2280
Start Trading Interval: 2280, Stop Trading Interval: 2560
Algorithm Settings for Interval: [1, 5, 1.25]
Account value: $1218.934
Optimizing Algorithm took 2.5378775596618652 seconds ---
After Trading Interval 7. Start Training Interval: 1960, Stop Training Interval: 2560
Start Trading Interval: 2560, Stop Trading Interval: 2840
Algorithm Settings for Interval: [1, 61, 0.75]
Account value: $1340.471
Optimizing Algorithm took 2.550318479537964 seconds ---
After Trading Interval 8. Start Training Interval: 2240, Stop Training Interval: 2840
Start Trading Interval: 2840, Stop Trading Interval: 3120
Algorithm Settings for Interval: [1, 61, 0.75]
Account value: $1451.43
Optimizing Algorithm took 2.5722579956054688 seconds ---
After Trading Interval 9. Start Training Interval: 2520, Stop Training Interval: 3120
Start Trading Interval: 3120, Stop Trading Interval: 3400
Algorithm Settings for Interval: [1, 40, 0.75]
Account value: $1497.63
Optimizing Algorithm took 2.6954965591430664 seconds ---
After Trading Interval 10. Start Training Interval: 2800, Stop Training Interval: 3400
Start Trading Interval: 3400, Stop Trading Interval: 3680
Algorithm Settings for Interval: [7, 8, 1.5]
Account value: $1516.007
Optimizing Algorithm took 2.713691234588623 seconds ---
After Trading Interval 11. Start Training Interval: 3080, Stop Training Interval: 3680
Start Trading Interval: 3680, Stop Trading Interval: 3960
Algorithm Settings for Interval: [1, 3, 1.25]
Account value: $1420.003
Optimizing Algorithm took 2.8091063499450684 seconds ---
After Trading Interval 12. Start Training Interval: 3360, Stop Training Interval: 3960
Start Trading Interval: 3960, Stop Trading Interval: 4240
Algorithm Settings for Interval: [2, 33, 1.0]
Account value: $1518.759
Optimizing Algorithm took 2.8214099407196045 seconds ---
After Trading Interval 13. Start Training Interval: 3640, Stop Training Interval: 4240
Start Trading Interval: 4240, Stop Trading Interval: 4520
Algorithm Settings for Interval: [2, 17, 1.5]
Account value: $1527.939
Optimizing Algorithm took 2.8566079139709473 seconds ---
After Trading Interval 14. Start Training Interval: 3920, Stop Training Interval: 4520
Start Trading Interval: 4520, Stop Trading Interval: 4800
Algorithm Settings for Interval: [2, 3, 1.75]
Account value: $1662.384
Optimizing Algorithm took 3.0150656700134277 seconds ---
After Trading Interval 15. Start Training Interval: 4200, Stop Training Interval: 4800
Start Trading Interval: 4800, Stop Trading Interval: 5080
Algorithm Settings for Interval: [2, 8, 2.0]
Account value: $1722.009
Optimizing Algorithm took 3.1268057823181152 seconds ---
After Trading Interval 16. Start Training Interval: 4480, Stop Training Interval: 5080
Start Trading Interval: 5080, Stop Trading Interval: 5360
Algorithm Settings for Interval: [11, 12, 1.75]
Account value: $2019.609
Optimizing Algorithm took 3.135713577270508 seconds ---
After Trading Interval 17. Start Training Interval: 4760, Stop Training Interval: 5360
Start Trading Interval: 5360, Stop Trading Interval: 5640
Algorithm Settings for Interval: [3, 4, 2.75]
Account value: $2232.092
Optimizing Algorithm took 3.1545493602752686 seconds ---
After Trading Interval 18. Start Training Interval: 5040, Stop Training Interval: 5640
Start Trading Interval: 5640, Stop Trading Interval: 5920
Algorithm Settings for Interval: [8, 33, 0.75]
Account value: $1944.674
Optimizing Algorithm took 3.206143617630005 seconds ---
After Trading Interval 19. Start Training Interval: 5320, Stop Training Interval: 5920
Start Trading Interval: 5920, Stop Trading Interval: 6200
Algorithm Settings for Interval: [1, 9, 1.5]
Account value: $1672.906
Optimizing Algorithm took 3.301114320755005 seconds ---
After Trading Interval 20. Start Training Interval: 5600, Stop Training Interval: 6200
Start Trading Interval: 6200, Stop Trading Interval: 6480
Algorithm Settings for Interval: [5, 70, 2.5]
Account value: $1569.767
Optimizing Algorithm took 3.3348488807678223 seconds ---
After Trading Interval 21. Start Training Interval: 5880, Stop Training Interval: 6480
Start Trading Interval: 6480, Stop Trading Interval: 6760
Algorithm Settings for Interval: [8, 50, 3.25]
Account value: $1537.467
Optimizing Algorithm took 3.3977556228637695 seconds ---
After Trading Interval 22. Start Training Interval: 6160, Stop Training Interval: 6760
Start Trading Interval: 6760, Stop Trading Interval: 7040
Algorithm Settings for Interval: [9, 72, 1.25]
Account value: $1645.397
Optimizing Algorithm took 3.427157163619995 seconds ---
After Trading Interval 23. Start Training Interval: 6440, Stop Training Interval: 7040
Start Trading Interval: 7040, Stop Trading Interval: 7320
Algorithm Settings for Interval: [4, 5, 3.25]
Account value: $1613.503
Optimizing Algorithm took 3.55214262008667 seconds ---
After Trading Interval 24. Start Training Interval: 6720, Stop Training Interval: 7320
Start Trading Interval: 7320, Stop Trading Interval: 7600
Algorithm Settings for Interval: [2, 5, 2.5]
Account value: $1531.787
Optimizing Algorithm took 3.606344223022461 seconds ---
After Trading Interval 25. Start Training Interval: 7000, Stop Training Interval: 7600
Start Trading Interval: 7600, Stop Trading Interval: 7880
Algorithm Settings for Interval: [2, 3, 2.5]
Account value: $1606.075
Optimizing Algorithm took 3.609682321548462 seconds ---
After Trading Interval 26. Start Training Interval: 7280, Stop Training Interval: 7880
Start Trading Interval: 7880, Stop Trading Interval: 8160
Algorithm Settings for Interval: [1, 2, 2.5]
Account value: $1567.535
Optimizing Algorithm took 3.8940372467041016 seconds ---
After Trading Interval 27. Start Training Interval: 7560, Stop Training Interval: 8160
Start Trading Interval: 8160, Stop Trading Interval: 8440
Algorithm Settings for Interval: [1, 2, 3.0]
Account value: $1535.16
Optimizing Algorithm took 3.8666257858276367 seconds ---
After Trading Interval 28. Start Training Interval: 7840, Stop Training Interval: 8440
Start Trading Interval: 8440, Stop Trading Interval: 8720
Algorithm Settings for Interval: [12, 61, 0.5]
Account value: $1457.118
Optimizing Algorithm took 3.9236369132995605 seconds ---
After Trading Interval 29. Start Training Interval: 8120, Stop Training Interval: 8720
Start Trading Interval: 8720, Stop Trading Interval: 9000
Algorithm Settings for Interval: [6, 40, 3.75]
Account value: $1223.397
Optimizing Algorithm took 4.246946096420288 seconds ---
After Trading Interval 30. Start Training Interval: 8400, Stop Training Interval: 9000
Start Trading Interval: 9000, Stop Trading Interval: 9280
Algorithm Settings for Interval: [1, 23, 1.75]
Account value: $1368.934
Optimizing Algorithm took 4.159064292907715 seconds ---
After Trading Interval 31. Start Training Interval: 8680, Stop Training Interval: 9280
Start Trading Interval: 9280, Stop Trading Interval: 9560
Algorithm Settings for Interval: [1, 16, 2.0]
Account value: $1154.123
Optimizing Algorithm took 4.207367181777954 seconds ---
After Trading Interval 32. Start Training Interval: 8960, Stop Training Interval: 9560
Start Trading Interval: 9560, Stop Trading Interval: 9840
Algorithm Settings for Interval: [6, 31, 2.25]
Account value: $1094.8
Optimizing Algorithm took 4.285806655883789 seconds ---
After Trading Interval 33. Start Training Interval: 9240, Stop Training Interval: 9840
Start Trading Interval: 9840, Stop Trading Interval: 10120
Algorithm Settings for Interval: [2, 22, 2.25]
Account value: $1051.103
Optimizing Algorithm took 4.420947551727295 seconds ---
After Trading Interval 34. Start Training Interval: 9520, Stop Training Interval: 10120
Start Trading Interval: 10120, Stop Trading Interval: 10400
Algorithm Settings for Interval: [9, 58, 0.25]
Account value: $1014.228
Optimizing Algorithm took 4.484391212463379 seconds ---
After Trading Interval 35. Start Training Interval: 9800, Stop Training Interval: 10400
Start Trading Interval: 10400, Stop Trading Interval: 10680
Algorithm Settings for Interval: [1, 3, 1.75]
Account value: $1093.703
Optimizing Algorithm took 4.6357643604278564 seconds ---
After Trading Interval 36. Start Training Interval: 10080, Stop Training Interval: 10680
Start Trading Interval: 10680, Stop Trading Interval: 10960
Algorithm Settings for Interval: [2, 15, 1.75]
Account value: $1095.43
Optimizing Algorithm took 4.501395225524902 seconds ---
After Trading Interval 37. Start Training Interval: 10360, Stop Training Interval: 10960
Start Trading Interval: 10960, Stop Trading Interval: 11240
Algorithm Settings for Interval: [1, 2, 1.75]
Account value: $1008.133
Optimizing Algorithm took 4.649824380874634 seconds ---
After Trading Interval 38. Start Training Interval: 10640, Stop Training Interval: 11240
Start Trading Interval: 11240, Stop Trading Interval: 11520
Algorithm Settings for Interval: [9, 10, 4.0]
Account value: $1003.201
Optimizing Algorithm took 4.918131113052368 seconds ---
After Trading Interval 39. Start Training Interval: 10920, Stop Training Interval: 11520
Start Trading Interval: 11520, Stop Trading Interval: 11800
Algorithm Settings for Interval: [1, 2, 3.5]
Account value: $1074.761
Optimizing Algorithm took 4.858669996261597 seconds ---
After Trading Interval 40. Start Training Interval: 11200, Stop Training Interval: 11800
Start Trading Interval: 11800, Stop Trading Interval: 12080
Algorithm Settings for Interval: [2, 5, 4.0]
Account value: $1221.481
Optimizing Algorithm took 4.964216709136963 seconds ---
After Trading Interval 41. Start Training Interval: 11480, Stop Training Interval: 12080
Start Trading Interval: 12080, Stop Trading Interval: 12360
Algorithm Settings for Interval: [2, 51, 2.5]
Account value: $1343.967
Optimizing Algorithm took 4.880105257034302 seconds ---
After Trading Interval 42. Start Training Interval: 11760, Stop Training Interval: 12360
Start Trading Interval: 12360, Stop Trading Interval: 12640
Algorithm Settings for Interval: [1, 27, 2.25]
Account value: $1371.301
Optimizing Algorithm took 5.122848033905029 seconds ---
After Trading Interval 43. Start Training Interval: 12040, Stop Training Interval: 12640
Start Trading Interval: 12640, Stop Trading Interval: 12920
Algorithm Settings for Interval: [2, 58, 1.75]
Account value: $1436.121
--- 154.37500500679016 seconds ---

View File

@ -0,0 +1,309 @@
Optimizing Algorithm took 8.998775243759155 seconds ---
After Trading Interval 0. Start Training Interval: 0, Stop Training Interval: 600
Start Trading Interval: 600, Stop Trading Interval: 880
Algorithm Settings for Interval: [2, 3, 0.95]
Account value: $1069.906
Optimizing Algorithm took 9.899574518203735 seconds ---
After Trading Interval 1. Start Training Interval: 280, Stop Training Interval: 880
Start Trading Interval: 880, Stop Trading Interval: 1160
Algorithm Settings for Interval: [14, 41, 0.45]
Account value: $1089.985
Optimizing Algorithm took 10.110156297683716 seconds ---
After Trading Interval 2. Start Training Interval: 560, Stop Training Interval: 1160
Start Trading Interval: 1160, Stop Trading Interval: 1440
Algorithm Settings for Interval: [2, 45, 0.55]
Account value: $1073.168
Optimizing Algorithm took 10.340864181518555 seconds ---
After Trading Interval 3. Start Training Interval: 840, Stop Training Interval: 1440
Start Trading Interval: 1440, Stop Trading Interval: 1720
Algorithm Settings for Interval: [2, 15, 1.65]
Account value: $1120.812
Optimizing Algorithm took 10.655256271362305 seconds ---
After Trading Interval 4. Start Training Interval: 1120, Stop Training Interval: 1720
Start Trading Interval: 1720, Stop Trading Interval: 2000
Algorithm Settings for Interval: [1, 26, 0.65]
Account value: $1251.817
Optimizing Algorithm took 10.892971992492676 seconds ---
After Trading Interval 5. Start Training Interval: 1400, Stop Training Interval: 2000
Start Trading Interval: 2000, Stop Trading Interval: 2280
Algorithm Settings for Interval: [2, 5, 1.35]
Account value: $1096.26
Optimizing Algorithm took 11.383354902267456 seconds ---
After Trading Interval 6. Start Training Interval: 1680, Stop Training Interval: 2280
Start Trading Interval: 2280, Stop Trading Interval: 2560
Algorithm Settings for Interval: [1, 4, 1.35]
Account value: $1181.844
Optimizing Algorithm took 11.412535667419434 seconds ---
After Trading Interval 7. Start Training Interval: 1960, Stop Training Interval: 2560
Start Trading Interval: 2560, Stop Trading Interval: 2840
Algorithm Settings for Interval: [1, 63, 0.8]
Account value: $1303.382
Optimizing Algorithm took 11.731790542602539 seconds ---
After Trading Interval 8. Start Training Interval: 2240, Stop Training Interval: 2840
Start Trading Interval: 2840, Stop Trading Interval: 3120
Algorithm Settings for Interval: [1, 60, 0.7]
Account value: $1414.341
Optimizing Algorithm took 12.236228942871094 seconds ---
After Trading Interval 9. Start Training Interval: 2520, Stop Training Interval: 3120
Start Trading Interval: 3120, Stop Trading Interval: 3400
Algorithm Settings for Interval: [1, 34, 0.9]
Account value: $1460.541
Optimizing Algorithm took 12.30902624130249 seconds ---
After Trading Interval 10. Start Training Interval: 2800, Stop Training Interval: 3400
Start Trading Interval: 3400, Stop Trading Interval: 3680
Algorithm Settings for Interval: [7, 8, 1.45]
Account value: $1476.937
Optimizing Algorithm took 12.746545791625977 seconds ---
After Trading Interval 11. Start Training Interval: 3080, Stop Training Interval: 3680
Start Trading Interval: 3680, Stop Trading Interval: 3960
Algorithm Settings for Interval: [1, 3, 1.25]
Account value: $1380.933
Optimizing Algorithm took 13.028409004211426 seconds ---
After Trading Interval 12. Start Training Interval: 3360, Stop Training Interval: 3960
Start Trading Interval: 3960, Stop Trading Interval: 4240
Algorithm Settings for Interval: [2, 33, 1.0]
Account value: $1479.689
Optimizing Algorithm took 13.068600416183472 seconds ---
After Trading Interval 13. Start Training Interval: 3640, Stop Training Interval: 4240
Start Trading Interval: 4240, Stop Trading Interval: 4520
Algorithm Settings for Interval: [12, 13, 2.95]
Account value: $1476.359
Optimizing Algorithm took 13.554868459701538 seconds ---
After Trading Interval 14. Start Training Interval: 3920, Stop Training Interval: 4520
Start Trading Interval: 4520, Stop Trading Interval: 4800
Algorithm Settings for Interval: [2, 3, 1.75]
Account value: $1535.399
Optimizing Algorithm took 13.959465265274048 seconds ---
After Trading Interval 15. Start Training Interval: 4200, Stop Training Interval: 4800
Start Trading Interval: 4800, Stop Trading Interval: 5080
Algorithm Settings for Interval: [1, 9, 1.35]
Account value: $1456.06
Optimizing Algorithm took 13.987566709518433 seconds ---
After Trading Interval 16. Start Training Interval: 4480, Stop Training Interval: 5080
Start Trading Interval: 5080, Stop Trading Interval: 5360
Algorithm Settings for Interval: [3, 13, 1.55]
Account value: $1679.26
Optimizing Algorithm took 14.316407442092896 seconds ---
After Trading Interval 17. Start Training Interval: 4760, Stop Training Interval: 5360
Start Trading Interval: 5360, Stop Trading Interval: 5640
Algorithm Settings for Interval: [3, 4, 2.7]
Account value: $1826.766
Optimizing Algorithm took 14.517505407333374 seconds ---
After Trading Interval 18. Start Training Interval: 5040, Stop Training Interval: 5640
Start Trading Interval: 5640, Stop Trading Interval: 5920
Algorithm Settings for Interval: [9, 70, 0.05]
Account value: $1600.762
Optimizing Algorithm took 14.995418071746826 seconds ---
After Trading Interval 19. Start Training Interval: 5320, Stop Training Interval: 5920
Start Trading Interval: 5920, Stop Trading Interval: 6200
Algorithm Settings for Interval: [1, 9, 1.5]
Account value: $1383.347
Optimizing Algorithm took 15.257695198059082 seconds ---
After Trading Interval 20. Start Training Interval: 5600, Stop Training Interval: 6200
Start Trading Interval: 6200, Stop Trading Interval: 6480
Algorithm Settings for Interval: [5, 70, 2.5]
Account value: $1300.836
Optimizing Algorithm took 15.656415700912476 seconds ---
After Trading Interval 21. Start Training Interval: 5880, Stop Training Interval: 6480
Start Trading Interval: 6480, Stop Trading Interval: 6760
Algorithm Settings for Interval: [1, 74, 0.8]
Account value: $1270.727
Optimizing Algorithm took 16.216188192367554 seconds ---
After Trading Interval 22. Start Training Interval: 6160, Stop Training Interval: 6760
Start Trading Interval: 6760, Stop Trading Interval: 7040
Algorithm Settings for Interval: [13, 73, 1.15]
Account value: $1358.147
Optimizing Algorithm took 16.275983333587646 seconds ---
After Trading Interval 23. Start Training Interval: 6440, Stop Training Interval: 7040
Start Trading Interval: 7040, Stop Trading Interval: 7320
Algorithm Settings for Interval: [4, 5, 3.05]
Account value: $1339.219
Optimizing Algorithm took 17.00299596786499 seconds ---
After Trading Interval 24. Start Training Interval: 6720, Stop Training Interval: 7320
Start Trading Interval: 7320, Stop Trading Interval: 7600
Algorithm Settings for Interval: [2, 5, 2.45]
Account value: $1271.122
Optimizing Algorithm took 16.92785096168518 seconds ---
After Trading Interval 25. Start Training Interval: 7000, Stop Training Interval: 7600
Start Trading Interval: 7600, Stop Trading Interval: 7880
Algorithm Settings for Interval: [2, 3, 2.4]
Account value: $1352.324
Optimizing Algorithm took 16.968461513519287 seconds ---
After Trading Interval 26. Start Training Interval: 7280, Stop Training Interval: 7880
Start Trading Interval: 7880, Stop Trading Interval: 8160
Algorithm Settings for Interval: [1, 2, 1.95]
Account value: $1334.379
Optimizing Algorithm took 17.830673933029175 seconds ---
After Trading Interval 27. Start Training Interval: 7560, Stop Training Interval: 8160
Start Trading Interval: 8160, Stop Trading Interval: 8440
Algorithm Settings for Interval: [1, 2, 2.9]
Account value: $1396.779
Optimizing Algorithm took 18.180543422698975 seconds ---
After Trading Interval 28. Start Training Interval: 7840, Stop Training Interval: 8440
Start Trading Interval: 8440, Stop Trading Interval: 8720
Algorithm Settings for Interval: [12, 60, 0.55]
Account value: $1301.459
Optimizing Algorithm took 18.44070339202881 seconds ---
After Trading Interval 29. Start Training Interval: 8120, Stop Training Interval: 8720
Start Trading Interval: 8720, Stop Trading Interval: 9000
Algorithm Settings for Interval: [1, 17, 2.1]
Account value: $1373.579
Optimizing Algorithm took 18.863008975982666 seconds ---
After Trading Interval 30. Start Training Interval: 8400, Stop Training Interval: 9000
Start Trading Interval: 9000, Stop Trading Interval: 9280
Algorithm Settings for Interval: [1, 13, 3.2]
Account value: $1320.929
Optimizing Algorithm took 19.098549604415894 seconds ---
After Trading Interval 31. Start Training Interval: 8680, Stop Training Interval: 9280
Start Trading Interval: 9280, Stop Trading Interval: 9560
Algorithm Settings for Interval: [1, 16, 1.95]
Account value: $1277.625
Optimizing Algorithm took 18.959242343902588 seconds ---
After Trading Interval 32. Start Training Interval: 8960, Stop Training Interval: 9560
Start Trading Interval: 9560, Stop Trading Interval: 9840
Algorithm Settings for Interval: [9, 17, 3.35]
Account value: $1269.569
Optimizing Algorithm took 19.519113779067993 seconds ---
After Trading Interval 33. Start Training Interval: 9240, Stop Training Interval: 9840
Start Trading Interval: 9840, Stop Trading Interval: 10120
Algorithm Settings for Interval: [2, 22, 2.2]
Account value: $1197.636
Optimizing Algorithm took 20.262263536453247 seconds ---
After Trading Interval 34. Start Training Interval: 9520, Stop Training Interval: 10120
Start Trading Interval: 10120, Stop Trading Interval: 10400
Algorithm Settings for Interval: [9, 60, 0.1]
Account value: $1165.346
Optimizing Algorithm took 20.49344754219055 seconds ---
After Trading Interval 35. Start Training Interval: 9800, Stop Training Interval: 10400
Start Trading Interval: 10400, Stop Trading Interval: 10680
Algorithm Settings for Interval: [1, 2, 1.85]
Account value: $1260.716
Optimizing Algorithm took 21.046724796295166 seconds ---
After Trading Interval 36. Start Training Interval: 10080, Stop Training Interval: 10680
Start Trading Interval: 10680, Stop Trading Interval: 10960
Algorithm Settings for Interval: [1, 4, 1.85]
Account value: $1367.776
Optimizing Algorithm took 21.566428661346436 seconds ---
After Trading Interval 37. Start Training Interval: 10360, Stop Training Interval: 10960
Start Trading Interval: 10960, Stop Trading Interval: 11240
Algorithm Settings for Interval: [1, 2, 1.55]
Account value: $1254.02
Optimizing Algorithm took 21.4471492767334 seconds ---
After Trading Interval 38. Start Training Interval: 10640, Stop Training Interval: 11240
Start Trading Interval: 11240, Stop Trading Interval: 11520
Algorithm Settings for Interval: [9, 10, 4.0]
Account value: $1247.052
Optimizing Algorithm took 22.343998432159424 seconds ---
After Trading Interval 39. Start Training Interval: 10920, Stop Training Interval: 11520
Start Trading Interval: 11520, Stop Trading Interval: 11800
Algorithm Settings for Interval: [1, 2, 3.3]
Account value: $1333.797
Optimizing Algorithm took 22.43312954902649 seconds ---
After Trading Interval 40. Start Training Interval: 11200, Stop Training Interval: 11800
Start Trading Interval: 11800, Stop Trading Interval: 12080
Algorithm Settings for Interval: [6, 14, 3.45]
Account value: $1375.367
Optimizing Algorithm took 24.033164978027344 seconds ---
After Trading Interval 41. Start Training Interval: 11480, Stop Training Interval: 12080
Start Trading Interval: 12080, Stop Trading Interval: 12360
Algorithm Settings for Interval: [15, 29, 4.6]
Account value: $1513.163
Optimizing Algorithm took 24.43971085548401 seconds ---
After Trading Interval 42. Start Training Interval: 11760, Stop Training Interval: 12360
Start Trading Interval: 12360, Stop Trading Interval: 12640
Algorithm Settings for Interval: [1, 53, 1.4]
Account value: $1503.887
Optimizing Algorithm took 25.531949758529663 seconds ---
After Trading Interval 43. Start Training Interval: 12040, Stop Training Interval: 12640
Start Trading Interval: 12640, Stop Trading Interval: 12920
Algorithm Settings for Interval: [2, 57, 1.85]
Account value: $1368.785
--- 713.9990713596344 seconds ---

147
test_strategy.py Normal file
View File

@ -0,0 +1,147 @@
from datetime import datetime, timedelta
from pytz import timezone
from Historical_Data_Accessor import HistoricalDataAccessor
from Model_Simulation import ModelSimulation
from simulation.strategies.Random_Strategy import RandomStrategy
EASTERN = timezone('US/Eastern')
def fft_custom():
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
symbol = "TSLA"
num_frequencies = 50
num_moving_average = 5
datetime_start = EASTERN.localize(datetime(year=2023, month=1, day=6, hour=10, minute=30))
datetime_end = EASTERN.localize(datetime(year=2024, month=1, day=1, hour=10, minute=30))
data_accessor = HistoricalDataAccessor()
proj = {
"_id": 0,
"Close": 1
}
extra_query = {
"Verbose_Phase": "Regular hours"
}
# Example: Retrieving only_close data from your data source
datapoints = data_accessor.get_range_datapoints(symbol=symbol, datetime_first=datetime_start, datetime_last=datetime_end, proj=proj, extra_query=extra_query)
only_close = np.array([datapoint["Close"] for datapoint in datapoints])
real_only_close = only_close
# Calculate daily gain percentage
period = int(60 * 6.5*5) # Assuming period in minutes
daily_gain = []
for i in range(len(only_close)):
if i >= period:
gain = (only_close[i] - only_close[i - period]) / only_close[i - period] * 100.0
daily_gain.append(gain)
else:
daily_gain.append(0) # For the first `period` elements, set to NaN or handle as needed
# Convert daily_gain to numpy array
only_close = np.array(daily_gain)
# Plotting daily gain
plt.figure(figsize=(10, 6))
plt.plot(only_close, label='Daily Gain (%)')
plt.xlabel('Time')
plt.ylabel('Daily Gain (%)')
plt.title(f'Daily Percentage Gain of {symbol} Close Prices')
plt.legend()
plt.grid(True)
plt.savefig('daily_gain_plot.png')
plt.show()
# Applying Simple Moving Average (SMA)
window_size = num_moving_average
sma = np.convolve(only_close, np.ones(window_size)/window_size, mode='valid')
# Split SMA data into training (80%) and testing (20%)
train_size = int(0.8 * len(sma))
sma_train = sma[:train_size]
sma_test = sma[train_size:]
# Step 3: Perform the FFT on training data
fft_result_train = np.fft.fft(sma_train)
frequencies = np.fft.fftfreq(len(sma_train))
magnitude_train = np.abs(fft_result_train)
peak_frequency = frequencies[np.argmax(magnitude_train)]
periods_train = 1 / frequencies
periods_train = periods_train / (60 * 6.5)
# Filter periods and magnitudes
mask_train = (periods_train > 0) & (periods_train < 365)
filtered_periods_train = periods_train[mask_train]
filtered_frequencies_train = frequencies[mask_train]
filtered_magnitude_train = magnitude_train[mask_train]
# Identify the top 10 frequencies by magnitude
top_indices_train = np.argsort(filtered_magnitude_train)[-1*(num_frequencies):] # Indices of the top 10 magnitudes
top_periods_train = filtered_periods_train[top_indices_train]
# Calculate phase shift for each top period
phase_shifts_train = np.zeros(len(top_periods_train))
for i, period in enumerate(top_periods_train):
freq = 1 / (period * 60 * 6.5)
index = np.argmin(np.abs(frequencies - freq))
phase_shifts_train[i] = np.angle(fft_result_train[index])
# Construct features for linear regression on training data
X_train = np.zeros((len(sma_train), len(top_periods_train)))
for i, period in enumerate(top_periods_train):
freq = 1 / (period * 60 * 6.5)
X_train[:, i] = np.cos(2 * np.pi * freq * np.arange(len(sma_train)) + phase_shifts_train[i])
# Perform linear regression on training data
model = LinearRegression()
model.fit(X_train, sma_train)
# Predict using the model on entire dataset
X_all = np.zeros((len(sma), len(top_periods_train)))
for i, period in enumerate(top_periods_train):
freq = 1 / (period * 60 * 6.5)
X_all[:, i] = np.cos(2 * np.pi * freq * np.arange(len(sma)) + phase_shifts_train[i])
predicted_all = model.predict(X_all)
# Plot original only_close and regression line
plt.figure(figsize=(10, 6))
plt.plot(sma, label='SMA Daily Gain of Original Data')
plt.plot(predicted_all, label='Linear Regression on SMA Daily Gain', linestyle='--', linewidth=2)
plt.plot(real_only_close, label='Real Only Close', linestyle='-.')
# Vertical line at 80% cutoff
plt.axvline(train_size, color='r', linestyle='--', label='80% Cutoff')
plt.xlabel('Time')
plt.ylabel('SMA Close Price')
plt.title(f'SMA of {symbol} Close Prices with Linear Regression')
plt.legend()
plt.savefig('sma_and_regression.png')
plt.close()
def main():
fft_custom()
return
datetime_start = EASTERN.localize(datetime(year=2016, month=1, day=6, hour=10, minute=30))
datetime_end = EASTERN.localize(datetime(year=2023, month=1, day=6, hour=10, minute=30))
random_strategy = RandomStrategy(symbol="AMD")
model_simulation = ModelSimulation(symbol="AMD", datetime_start=datetime_start, datetime_end=datetime_end, datetime_delta=timedelta(minutes=1), strategy=random_strategy)
if model_simulation.status != 0:
print("uh oh")
if __name__ == "__main__":
main()