Initial Commit
This commit is contained in:
commit
3be2d2191d
51
.$Logic Diagram.drawio.bkp
Normal file
51
.$Logic Diagram.drawio.bkp
Normal 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'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<br>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<br>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
208
Account_Information.py
Normal 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
115
Brokerage_Simulation.py
Normal 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
56
Common.py
Normal 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
181
Historical_Data_Accessor.py
Normal 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
75
Logic Diagram.drawio
Normal 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'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<br>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<br>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="<br><b>ModelSimulation</b>" 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
137
Model_Simulation.py
Normal 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
82
Orders.py
Normal 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
16
Todo
Normal 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
|
||||
BIN
__pycache__/Account_Information.cpython-310.pyc
Normal file
BIN
__pycache__/Account_Information.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/Brokerage_Simulation.cpython-310.pyc
Normal file
BIN
__pycache__/Brokerage_Simulation.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/Common.cpython-310.pyc
Normal file
BIN
__pycache__/Common.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/Historical_Data_Accessor.cpython-310.pyc
Normal file
BIN
__pycache__/Historical_Data_Accessor.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/Model_Simulation.cpython-310.pyc
Normal file
BIN
__pycache__/Model_Simulation.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/Orders.cpython-310.pyc
Normal file
BIN
__pycache__/Orders.cpython-310.pyc
Normal file
Binary file not shown.
48
strategies/Random_Strategy.py
Normal file
48
strategies/Random_Strategy.py
Normal 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]
|
||||
|
||||
|
||||
56
strategies/Strategy_Template.py
Normal file
56
strategies/Strategy_Template.py
Normal 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
|
||||
BIN
strategies/__pycache__/Random.cpython-310.pyc
Normal file
BIN
strategies/__pycache__/Random.cpython-310.pyc
Normal file
Binary file not shown.
BIN
strategies/__pycache__/Random_Strategy.cpython-310.pyc
Normal file
BIN
strategies/__pycache__/Random_Strategy.cpython-310.pyc
Normal file
Binary file not shown.
BIN
strategies/__pycache__/StrategyTemplate.cpython-310.pyc
Normal file
BIN
strategies/__pycache__/StrategyTemplate.cpython-310.pyc
Normal file
Binary file not shown.
BIN
strategies/__pycache__/Strategy_Template.cpython-310.pyc
Normal file
BIN
strategies/__pycache__/Strategy_Template.cpython-310.pyc
Normal file
Binary file not shown.
BIN
strategies/deprecated/sma_offset_trigger/Flow Chart.vsdx
Normal file
BIN
strategies/deprecated/sma_offset_trigger/Flow Chart.vsdx
Normal file
Binary file not shown.
@ -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))
|
||||
810
strategies/deprecated/sma_offset_trigger/SMA Offset Trigger.py
Normal file
810
strategies/deprecated/sma_offset_trigger/SMA Offset Trigger.py
Normal 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))
|
||||
810
strategies/deprecated/sma_offset_trigger/run1.py
Normal file
810
strategies/deprecated/sma_offset_trigger/run1.py
Normal 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))
|
||||
309
strategies/deprecated/sma_offset_trigger/run1.txt
Normal file
309
strategies/deprecated/sma_offset_trigger/run1.txt
Normal 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 ---
|
||||
309
strategies/deprecated/sma_offset_trigger/run2.txt
Normal file
309
strategies/deprecated/sma_offset_trigger/run2.txt
Normal 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
147
test_strategy.py
Normal 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()
|
||||
Loading…
Reference in New Issue
Block a user