Skip to content

Commit cb41da7

Browse files
authored
Fix/stop loss datetime (#300)
* Add stop loss datetime fix * Add high water mark for stop loss and take profit tests * Fix flake8 warnings * Fix test_trade_price_update * Update readme backtest example
1 parent a84d26a commit cb41da7

File tree

15 files changed

+808
-150
lines changed

15 files changed

+808
-150
lines changed

README.md

+67-68
Large diffs are not rendered by default.

examples/backtest_example/run_backtest.py

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import time
2+
from datetime import datetime
23
import logging.config
34
from datetime import datetime, timedelta
45

@@ -124,6 +125,7 @@ def apply_strategy(self, context: Context, market_data):
124125
trade = context.get_trade(order_id=order.id)
125126
context.add_stop_loss(
126127
trade=trade,
128+
trade_risk_type="trailing",
127129
percentage=5,
128130
sell_percentage=50
129131
)

investing_algorithm_framework/domain/models/trade/trade.py

+37
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ def __init__(
5959
status,
6060
net_gain=0,
6161
last_reported_price=None,
62+
last_reported_price_datetime=None,
6263
high_water_mark=None,
64+
high_water_mark_datetime=None,
6365
updated_at=None,
6466
stop_losses=None,
6567
take_profits=None,
@@ -76,12 +78,47 @@ def __init__(
7678
self.remaining = remaining
7779
self.net_gain = net_gain
7880
self.last_reported_price = last_reported_price
81+
self.last_reported_price_datetime = last_reported_price_datetime
7982
self.high_water_mark = high_water_mark
83+
self.high_water_mark_datetime = high_water_mark_datetime
8084
self.status = status
8185
self.updated_at = updated_at
8286
self.stop_losses = stop_losses
8387
self.take_profits = take_profits
8488

89+
def update(self, data):
90+
91+
if "status" in data:
92+
self.status = TradeStatus.from_value(data["status"])
93+
94+
if TradeStatus.CLOSED.equals(self.status):
95+
96+
# Set all stop losses to inactive
97+
if self.stop_losses is not None:
98+
for stop_loss in self.stop_losses:
99+
stop_loss.active = False
100+
101+
# set all take profits to inactive
102+
if self.take_profits is not None:
103+
for take_profit in self.take_profits:
104+
take_profit.active = False
105+
106+
if "last_reported_price" in data:
107+
self.last_reported_price = data["last_reported_price"]
108+
109+
if self.high_water_mark is None:
110+
self.high_water_mark = data["last_reported_price"]
111+
self.high_water_mark_datetime = \
112+
data["last_reported_price_datetime"]
113+
else:
114+
115+
if data["last_reported_price"] > self.high_water_mark:
116+
self.high_water_mark = data["last_reported_price"]
117+
self.high_water_mark_datetime = \
118+
data["last_reported_price_datetime"]
119+
120+
return super().update(data)
121+
85122
@property
86123
def closed_prices(self):
87124
return [

investing_algorithm_framework/domain/models/trade/trade_stop_loss.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,26 @@ def __init__(
5555
total_amount_trade: float,
5656
sell_percentage: float = 100,
5757
active: bool = True,
58-
sell_prices: str = None
58+
sell_prices: str = None,
59+
sell_price_dates: str = None,
60+
high_water_mark_date: str = None,
5961
):
6062
self.trade_id = trade_id
6163
self.trade_risk_type = trade_risk_type
6264
self.percentage = percentage
6365
self.sell_percentage = sell_percentage
6466
self.high_water_mark = open_price
67+
self.high_water_mark_date = high_water_mark_date
6568
self.open_price = open_price
6669
self.stop_loss_price = self.high_water_mark * \
6770
(1 - (self.percentage / 100))
6871
self.sell_amount = total_amount_trade * (self.sell_percentage / 100)
6972
self.sold_amount = 0
7073
self.active = active
7174
self.sell_prices = sell_prices
75+
self.sell_price_dates = sell_price_dates
7276

73-
def update_with_last_reported_price(self, current_price: float):
77+
def update_with_last_reported_price(self, current_price: float, date):
7478
"""
7579
Function to update the take profit price based on the last
7680
reported price.
@@ -88,13 +92,16 @@ def update_with_last_reported_price(self, current_price: float):
8892

8993
if TradeRiskType.FIXED.equals(self.trade_risk_type):
9094
# Check if the current price is less than the high water mark
95+
if current_price > self.high_water_mark:
96+
self.high_water_mark = current_price
9197
return
9298
else:
9399
# Check if the current price is less than the stop loss price
94100
if current_price <= self.stop_loss_price:
95101
return
96102
elif current_price > self.high_water_mark:
97103
self.high_water_mark = current_price
104+
self.high_water_mark_date = date
98105
self.stop_loss_price = self.high_water_mark * \
99106
(1 - (self.percentage / 100))
100107

investing_algorithm_framework/domain/models/trade/trade_take_profit.py

+20-2
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,26 @@ def __init__(
5555
total_amount_trade: float,
5656
sell_percentage: float = 100,
5757
active: bool = True,
58-
sell_prices: str = None
58+
sell_prices: str = None,
59+
sell_price_dates: str = None,
60+
high_water_mark_date: str = None,
5961
):
6062
self.trade_id = trade_id
6163
self.trade_risk_type = trade_risk_type
6264
self.percentage = percentage
6365
self.sell_percentage = sell_percentage
6466
self.high_water_mark = None
67+
self.high_water_mark_date = high_water_mark_date
6568
self.open_price = open_price
6669
self.take_profit_price = open_price * \
6770
(1 + (self.percentage / 100))
6871
self.sell_amount = total_amount_trade * (self.sell_percentage / 100)
6972
self.sold_amount = 0
7073
self.active = active
7174
self.sell_prices = sell_prices
75+
self.sell_price_dates = sell_price_dates
7276

73-
def update_with_last_reported_price(self, current_price: float):
77+
def update_with_last_reported_price(self, current_price: float, date):
7478
"""
7579
Function to update the take profit price based on
7680
the last reported price.
@@ -85,15 +89,28 @@ def update_with_last_reported_price(self, current_price: float):
8589

8690
# Do nothing for fixed take profit
8791
if TradeRiskType.FIXED.equals(self.trade_risk_type):
92+
93+
if self.high_water_mark is not None:
94+
if current_price > self.high_water_mark:
95+
self.high_water_mark = current_price
96+
self.high_water_mark_date = date
97+
else:
98+
if current_price >= self.take_profit_price:
99+
self.high_water_mark = current_price
100+
self.high_water_mark_date = date
101+
return
102+
88103
return
89104
else:
90105

91106
if self.high_water_mark is None:
92107

93108
if current_price >= self.take_profit_price:
94109
self.high_water_mark = current_price
110+
self.high_water_mark_date = date
95111
new_take_profit_price = self.high_water_mark * \
96112
(1 - (self.percentage / 100))
113+
97114
if self.take_profit_price <= new_take_profit_price:
98115
self.take_profit_price = new_take_profit_price
99116

@@ -106,6 +123,7 @@ def update_with_last_reported_price(self, current_price: float):
106123
# Increase the high water mark and take profit price
107124
elif current_price > self.high_water_mark:
108125
self.high_water_mark = current_price
126+
self.high_water_mark_date = date
109127
new_take_profit_price = self.high_water_mark * \
110128
(1 - (self.percentage / 100))
111129

0 commit comments

Comments
 (0)