From 843e393420b47625ef98b42a9f78f5db641c2c22 Mon Sep 17 00:00:00 2001 From: Mark O'Reilly Date: Thu, 1 Jun 2023 05:56:55 +0100 Subject: [PATCH 1/6] added class representing a position --- Breakout/position.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Breakout/position.py diff --git a/Breakout/position.py b/Breakout/position.py new file mode 100644 index 0000000..e3e1d4d --- /dev/null +++ b/Breakout/position.py @@ -0,0 +1,22 @@ +import datetime +import json +from typing import Optional + + +class Position: + start: datetime.datetime + stop_price: Optional[float] + + def __init__(self, start: datetime.datetime = None, stop_price: float = None) -> None: + self.start = start + self.stop_price = stop_price + + def __str__(self) -> str: + return json.dumps({ + "start": str(self.start), + "stop_price": self.stop_price + }) + + +def position_from_str(content: str) -> Position: + return Position(**json.loads(content)) From e56ea754fc7916e99335ff0fb1fd820535ae0a58 Mon Sep 17 00:00:00 2001 From: Mark O'Reilly Date: Thu, 1 Jun 2023 05:58:16 +0100 Subject: [PATCH 2/6] removed stop price from position --- Breakout/position.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Breakout/position.py b/Breakout/position.py index e3e1d4d..f8e1bca 100644 --- a/Breakout/position.py +++ b/Breakout/position.py @@ -1,20 +1,16 @@ import datetime import json -from typing import Optional class Position: start: datetime.datetime - stop_price: Optional[float] - def __init__(self, start: datetime.datetime = None, stop_price: float = None) -> None: + def __init__(self, start: datetime.datetime = None) -> None: self.start = start - self.stop_price = stop_price def __str__(self) -> str: return json.dumps({ "start": str(self.start), - "stop_price": self.stop_price }) From d5b4369df6fd24d974d08dab0bdf7630a763d21b Mon Sep 17 00:00:00 2001 From: Mark O'Reilly Date: Thu, 1 Jun 2023 06:02:57 +0100 Subject: [PATCH 3/6] deserialize the start time correctly --- Breakout/position.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Breakout/position.py b/Breakout/position.py index f8e1bca..75395aa 100644 --- a/Breakout/position.py +++ b/Breakout/position.py @@ -1,4 +1,5 @@ import datetime +from dateutil.parser import parse import json @@ -15,4 +16,6 @@ def __str__(self) -> str: def position_from_str(content: str) -> Position: - return Position(**json.loads(content)) + content = json.loads(content) + content['start'] = parse(content['start']) + return Position(**content) From 299a12e5cb880a6d28a9a7bb6daeb0cf4f9f55d0 Mon Sep 17 00:00:00 2001 From: Mark O'Reilly Date: Thu, 1 Jun 2023 06:03:10 +0100 Subject: [PATCH 4/6] store position data on save --- Breakout/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Breakout/main.py b/Breakout/main.py index 3205840..b7b4ef8 100644 --- a/Breakout/main.py +++ b/Breakout/main.py @@ -1,5 +1,6 @@ from AlgorithmImports import QCAlgorithm, Resolution, BrokerageName, OrderProperties, TimeInForce from datetime import timedelta, datetime +from Breakout.position import Position from indicators import SymbolIndicators @@ -85,6 +86,7 @@ def buy(self, symbol, order_tag=None, order_properties=None, price=None): else: self.live_log(f"Market order {symbol.Value} {position_value}: {order_tag or 'no tag'}") self.MarketOrder(symbol, position_size, tag=order_tag) + self.ObjectStore.save(symbol.Value, Position(self.Time)) else: self.live_log(f"insufficient cash ({self.Portfolio.Cash}) to purchase {symbol.Value}") From 327c2559e56395d3bca51c0ce552cb3ce1aebc65 Mon Sep 17 00:00:00 2001 From: Mark O'Reilly Date: Thu, 1 Jun 2023 06:05:15 +0100 Subject: [PATCH 5/6] remove position data from object store when stock is sold --- Breakout/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Breakout/main.py b/Breakout/main.py index b7b4ef8..a38238e 100644 --- a/Breakout/main.py +++ b/Breakout/main.py @@ -56,7 +56,7 @@ def OnData(self, data): if not self.symbol_map[symbol].ready: continue if self.sell_signal(symbol, data): - self.Liquidate(symbol) + self.sell(symbol) if self.symbol_map[symbol].uptrending and not self.ActiveSecurities[symbol].Invested: symbols.append(symbol) self.live_log("processing on data") @@ -89,6 +89,11 @@ def buy(self, symbol, order_tag=None, order_properties=None, price=None): self.ObjectStore.save(symbol.Value, Position(self.Time)) else: self.live_log(f"insufficient cash ({self.Portfolio.Cash}) to purchase {symbol.Value}") + + def sell(self, symbol): + self.Liquidate(symbol) + if self.ObjectStore.ContainsKey(symbol.Value): + self.ObjectStore.Delete(symbol.Value) def good_for_a_day(self): """ From f45cecc16d90743ee719c23f3ec5a6f2d997c52e Mon Sep 17 00:00:00 2001 From: Mark O'Reilly Date: Thu, 1 Jun 2023 06:08:21 +0100 Subject: [PATCH 6/6] added docsstring --- Breakout/position.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Breakout/position.py b/Breakout/position.py index 75395aa..5cec259 100644 --- a/Breakout/position.py +++ b/Breakout/position.py @@ -4,6 +4,10 @@ class Position: + """ + Stores information relative to an open position. + Intended to be persisted in object storage as a json string. + """ start: datetime.datetime def __init__(self, start: datetime.datetime = None) -> None: @@ -16,6 +20,15 @@ def __str__(self) -> str: def position_from_str(content: str) -> Position: + """ + Method for parsing a Position from object storage. + + Args: + content (str): the Position content as a json string. + + Returns: + Position: The position class. + """ content = json.loads(content) content['start'] = parse(content['start']) return Position(**content)