bbstrader 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bbstrader might be problematic. Click here for more details.

@@ -4,6 +4,38 @@ import logging
4
4
  from typing import List, NamedTuple, Optional
5
5
  from enum import Enum
6
6
 
7
+
8
+ __all__ = [
9
+ "TIMEFRAMES",
10
+ "TimeFrame",
11
+ "TerminalInfo",
12
+ "AccountInfo",
13
+ "SymbolInfo",
14
+ "TickInfo",
15
+ "TradeRequest",
16
+ "OrderCheckResult",
17
+ "OrderSentResult",
18
+ "TradeOrder",
19
+ "TradePosition",
20
+ "TradeDeal",
21
+ "InvalidBroker",
22
+ "GenericFail",
23
+ "InvalidParams",
24
+ "HistoryNotFound",
25
+ "InvalidVersion",
26
+ "AuthFailed",
27
+ "UnsupportedMethod",
28
+ "AutoTradingDisabled",
29
+ "InternalFailSend",
30
+ "InternalFailReceive",
31
+ "InternalFailInit",
32
+ "InternalFailConnect",
33
+ "InternalFailTimeout",
34
+ "trade_retcode_message",
35
+ "raise_mt5_error",
36
+ "config_logger",
37
+ ]
38
+
7
39
  def config_logger(log_file: str, console_log=True):
8
40
  # Configure the logger
9
41
  logger = logging.getLogger(__name__)
@@ -55,34 +87,6 @@ class LogLevelFilter(logging.Filter):
55
87
  return record.levelno in self.levels
56
88
 
57
89
 
58
- __all__ = [
59
- "TIMEFRAMES",
60
- "TimeFrame",
61
- "TerminalInfo",
62
- "AccountInfo",
63
- "SymbolInfo",
64
- "TickInfo",
65
- "TradeRequest",
66
- "OrderCheckResult",
67
- "OrderSentResult",
68
- "TradeOrder",
69
- "TradePosition",
70
- "TradeDeal",
71
- "GenericFail",
72
- "InvalidParams",
73
- "HistoryNotFound",
74
- "InvalidVersion",
75
- "AuthFailed",
76
- "UnsupportedMethod",
77
- "AutoTradingDisabled",
78
- "InternalFailSend",
79
- "InternalFailReceive",
80
- "InternalFailInit",
81
- "InternalFailConnect",
82
- "InternalFailTimeout",
83
- "trade_retcode_message",
84
- "raise_mt5_error",
85
- ]
86
90
 
87
91
  # TIMEFRAME is an enumeration with possible chart period values
88
92
  # See https://www.mql5.com/en/docs/python_metatrader5/mt5copyratesfrom_py#timeframe
@@ -137,7 +141,6 @@ class TimeFrame(Enum):
137
141
  W1 = "W1"
138
142
  MN1 = "MN1"
139
143
 
140
-
141
144
  class TerminalInfo(NamedTuple):
142
145
  """
143
146
  Represents general information about the trading terminal.
@@ -467,6 +470,11 @@ class TradeDeal(NamedTuple):
467
470
  comment: str
468
471
  external_id: str
469
472
 
473
+ class InvalidBroker(Exception):
474
+ """Exception raised for invalid broker errors."""
475
+ def __init__(self, message="Invalid broker."):
476
+ super().__init__(message)
477
+
470
478
 
471
479
  class MT5TerminalError(Exception):
472
480
  """Base exception class for trading-related errors."""
@@ -1,12 +1,10 @@
1
- from math import log
2
1
  import time
2
+ import MetaTrader5 as mt5
3
3
  from datetime import datetime
4
4
  from bbstrader.metatrader.trade import Trade
5
5
  from bbstrader.trading.strategies import Strategy
6
- from typing import Optional, Literal, List, Tuple, Dict
7
- import MetaTrader5 as mt5
8
- from bbstrader.metatrader.account import INIT_MSG
9
- from bbstrader.metatrader.utils import raise_mt5_error
6
+ from bbstrader.metatrader.account import check_mt5_connection
7
+ from typing import Optional, Literal, Tuple, List, Dict
10
8
 
11
9
 
12
10
  _TF_MAPPING = {
@@ -22,7 +20,7 @@ _TF_MAPPING = {
22
20
  'D1': 1440
23
21
  }
24
22
 
25
- TRADING_DAYS = [
23
+ TradingDays = [
26
24
  'monday',
27
25
  'tuesday',
28
26
  'wednesday',
@@ -30,19 +28,16 @@ TRADING_DAYS = [
30
28
  'friday'
31
29
  ]
32
30
 
33
- def _check_mt5_connection():
34
- if not mt5.initialize():
35
- raise_mt5_error(INIT_MSG)
36
31
 
37
32
  def _mt5_execution(
38
- symbol_list, trades_instances, strategy_cls, /,
39
- mm, time_frame, iter_time, period, trading_days,
40
- comment, **kwargs
41
- ):
33
+ symbol_list, trades_instances, strategy_cls, /,
34
+ mm, trail, stop_trail, trail_after_points, be_plus_points,
35
+ time_frame, iter_time, period, period_end_action, trading_days,
36
+ comment, **kwargs):
42
37
  symbols = symbol_list.copy()
43
38
  STRATEGY = kwargs.get('strategy_name')
44
39
  _max_trades = kwargs.get('max_trades')
45
- logger = kwargs.get('logger')
40
+ logger = trades_instances[symbols[0]].logger
46
41
  max_trades = {symbol: _max_trades[symbol] for symbol in symbols}
47
42
  if comment is None:
48
43
  trade = trades_instances[symbols[0]]
@@ -54,26 +49,33 @@ def _mt5_execution(
54
49
  if buys is not None:
55
50
  logger.info(
56
51
  f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
57
- trades_instances[symbol].break_even(mm=mm)
52
+ trades_instances[symbol].break_even(
53
+ mm=mm, trail=trail, stop_trail=stop_trail,
54
+ trail_after_points=trail_after_points, be_plus_points=be_plus_points)
58
55
  if sells is not None:
59
56
  logger.info(
60
57
  f"Checking for Break even, SYMBOL={symbol}...STRATEGY={STRATEGY}")
61
- trades_instances[symbol].break_even(mm=mm)
58
+ trades_instances[symbol].break_even(
59
+ mm=mm, trail=trail, stop_trail=stop_trail,
60
+ trail_after_points=trail_after_points, be_plus_points=be_plus_points)
62
61
  num_days = 0
63
62
  time_intervals = 0
64
63
  trade_time = _TF_MAPPING[time_frame]
65
64
 
66
65
  long_market = {symbol: False for symbol in symbols}
67
66
  short_market = {symbol: False for symbol in symbols}
68
-
67
+ try:
68
+ check_mt5_connection()
69
+ strategy: Strategy = strategy_cls(symbol_list=symbols, mode='live', **kwargs)
70
+ except Exception as e:
71
+ logger.error(f"Error initializing strategy, {e}, STRATEGY={STRATEGY}")
72
+ return
69
73
  logger.info(
70
74
  f'Running {STRATEGY} Strategy on {symbols} in {time_frame} Interval ...')
71
- strategy: Strategy = strategy_cls(
72
- symbol_list=symbols, mode='live', **kwargs)
73
-
75
+
74
76
  while True:
75
77
  try:
76
- _check_mt5_connection()
78
+ check_mt5_connection()
77
79
  current_date = datetime.now()
78
80
  today = current_date.strftime("%A").lower()
79
81
  time.sleep(0.5)
@@ -98,13 +100,21 @@ def _mt5_execution(
98
100
  sells[symbol]) >= max_trades[symbol] for symbol in symbols}
99
101
  except Exception as e:
100
102
  logger.error(f"{e}, STRATEGY={STRATEGY}")
103
+ continue
101
104
  time.sleep(0.5)
105
+ try:
106
+ check_mt5_connection()
107
+ signals = strategy.calculate_signals()
108
+ except Exception as e:
109
+ logger.error(f"Calculating signal, {e}, STRATEGY={STRATEGY}")
110
+ continue
102
111
  for symbol in symbols:
103
112
  try:
113
+ check_mt5_connection()
104
114
  trade = trades_instances[symbol]
105
115
  logger.info(
106
116
  f"Calculating signal... SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
107
- signal = strategy.calculate_signals()[symbol]
117
+ signal = signals[symbol]
108
118
  if trade.trading_time() and today in trading_days:
109
119
  if signal is not None:
110
120
  logger.info(
@@ -148,7 +158,7 @@ def _mt5_execution(
148
158
 
149
159
  except Exception as e:
150
160
  logger.error(f"{e}, SYMBOL={symbol}, STRATEGY={STRATEGY}")
151
-
161
+ continue
152
162
  time.sleep((60 * iter_time) - 1.0)
153
163
  if iter_time == 1:
154
164
  time_intervals += 1
@@ -160,74 +170,92 @@ def _mt5_execution(
160
170
  f"(e.g; if time_frame is 15m, iter_time must be 1.5, 3, 3, 15 etc)"
161
171
  )
162
172
  print()
163
- if period.lower() == 'day':
164
- for symbol in symbols:
165
- trade = trades_instances[symbol]
166
- if trade.days_end():
167
- trade.close_positions(position_type='all', comment=comment)
168
- logger.info(
169
- f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
170
- trade.statistics(save=True)
171
- if trades_instances[symbols[-1]].days_end():
172
- break
173
+ try:
174
+ check_mt5_connection()
175
+ day_end = all(trade.days_end() for trade in trades_instances.values())
176
+ if period.lower() == 'day':
177
+ for symbol in symbols:
178
+ trade = trades_instances[symbol]
179
+ if trade.days_end():
180
+ trade.close_positions(position_type='all', comment=comment)
181
+ logger.info(
182
+ f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
183
+ trade.statistics(save=True)
184
+ if day_end:
185
+ if period_end_action == 'break':
186
+ break
187
+ elif period_end_action == 'sleep':
188
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
189
+ logger.info(f"Sleeping for {sleep_time} minutes ...\n")
190
+ time.sleep(60 * sleep_time)
191
+ logger.info("STARTING NEW TRADING SESSION ...\n")
173
192
 
174
- elif period.lower() == 'week':
175
- for symbol in symbols:
176
- trade = trades_instances[symbol]
177
- if trade.days_end() and today != 'friday':
178
- logger.info(
179
- f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
193
+ elif period.lower() == 'week':
194
+ for symbol in symbols:
195
+ trade = trades_instances[symbol]
196
+ if trade.days_end() and today != 'friday':
197
+ logger.info(
198
+ f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
180
199
 
181
- elif trade.days_end() and today == 'friday':
182
- trade.close_positions(position_type='all', comment=comment)
183
- logger.info(
184
- f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
185
- trade.statistics(save=True)
186
- if trades_instances[symbols[-1]].days_end() and today != 'friday':
187
- sleep_time = trades_instances[symbols[-1]].sleep_time()
188
- logger.info(f"Sleeping for {sleep_time} minutes ...")
189
- time.sleep(60 * sleep_time)
190
- logger.info("\nSTARTING NEW TRADING SESSION ...")
191
- elif trades_instances[symbols[-1]].days_end() and today == 'friday':
192
- break
200
+ elif trade.days_end() and today == 'friday':
201
+ trade.close_positions(position_type='all', comment=comment)
202
+ logger.info(
203
+ f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
204
+ trade.statistics(save=True)
205
+ if day_end and today != 'friday':
206
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
207
+ logger.info(f"Sleeping for {sleep_time} minutes ...\n")
208
+ time.sleep(60 * sleep_time)
209
+ logger.info("STARTING NEW TRADING SESSION ...\n")
210
+ elif day_end and today == 'friday':
211
+ if period_end_action == 'break':
212
+ break
213
+ elif period_end_action == 'sleep':
214
+ sleep_time = trades_instances[symbols[-1]].sleep_time(weekend=True)
215
+ logger.info(f"Sleeping for {sleep_time} minutes ...\n")
216
+ time.sleep(60 * sleep_time)
217
+ logger.info("STARTING NEW TRADING SESSION ...\n")
193
218
 
194
- elif period.lower() == 'month':
195
- for symbol in symbols:
196
- trade = trades_instances[symbol]
197
- if trade.days_end() and today != 'friday':
198
- logger.info(
199
- f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
219
+ elif period.lower() == 'month':
220
+ for symbol in symbols:
221
+ trade = trades_instances[symbol]
222
+ if trade.days_end() and today != 'friday':
223
+ logger.info(
224
+ f"End of the Day !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
200
225
 
201
- elif trade.days_end() and today == 'friday':
202
- logger.info(
203
- f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
204
- elif (
205
- trade.days_end()
206
- and today == 'friday'
207
- and num_days/len(symbols) >= 20
208
- ):
209
- trade.close_positions(position_type='all', comment=comment)
210
- logger.info(
211
- f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
212
- trade.statistics(save=True)
213
- if trades_instances[symbols[-1]].days_end() and today != 'friday':
214
- sleep_time = trades_instances[symbols[-1]].sleep_time()
215
- logger.info(f"Sleeping for {sleep_time} minutes ...")
216
- time.sleep(60 * sleep_time)
217
- logger.info("\nSTARTING NEW TRADING SESSION ...")
218
- num_days += 1
219
- elif trades_instances[symbols[-1]].days_end() and today == 'friday':
220
- sleep_time = trades_instances[symbols[-1]
221
- ].sleep_time(weekend=True)
222
- logger.info(f"Sleeping for {sleep_time} minutes ...")
223
- time.sleep(60 * sleep_time)
224
- logger.info("\nSTARTING NEW TRADING SESSION ...")
225
- num_days += 1
226
- elif (trades_instances[symbols[-1]].days_end()
227
- and today == 'friday'
228
- and num_days/len(symbols) >= 20
229
- ):
230
- break
226
+ elif trade.days_end() and today == 'friday':
227
+ logger.info(
228
+ f"End of the Week !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
229
+ elif (
230
+ trade.days_end()
231
+ and today == 'friday'
232
+ and num_days/len(symbols) >= 20
233
+ ):
234
+ trade.close_positions(position_type='all', comment=comment)
235
+ logger.info(
236
+ f"End of the Month !!! SYMBOL={trade.symbol}, STRATEGY={STRATEGY}")
237
+ trade.statistics(save=True)
238
+ if day_end and today != 'friday':
239
+ sleep_time = trades_instances[symbols[-1]].sleep_time()
240
+ logger.info(f"Sleeping for {sleep_time} minutes ...\n")
241
+ time.sleep(60 * sleep_time)
242
+ logger.info("STARTING NEW TRADING SESSION ...\n")
243
+ num_days += 1
244
+ elif day_end and today == 'friday':
245
+ sleep_time = trades_instances[symbols[-1]
246
+ ].sleep_time(weekend=True)
247
+ logger.info(f"Sleeping for {sleep_time} minutes ...\n")
248
+ time.sleep(60 * sleep_time)
249
+ logger.info("STARTING NEW TRADING SESSION ...\n")
250
+ num_days += 1
251
+ elif (day_end
252
+ and today == 'friday'
253
+ and num_days/len(symbols) >= 20
254
+ ):
255
+ break
256
+ except Exception as e:
257
+ logger.error(f"Handling period end actions, {e}, STRATEGY={STRATEGY}")
258
+ continue
231
259
 
232
260
 
233
261
  def _tws_execution(*args, **kwargs):
@@ -317,10 +345,15 @@ class ExecutionEngine():
317
345
  strategy_cls: Strategy,
318
346
  /,
319
347
  mm: Optional[bool] = True,
348
+ trail: Optional[bool] = True,
349
+ stop_trail: Optional[int] = None,
350
+ trail_after_points: Optional[int] = None,
351
+ be_plus_points: Optional[int] = None,
320
352
  time_frame: Optional[str] = '15m',
321
353
  iter_time: Optional[int | float] = 5,
322
354
  period: Literal['day', 'week', 'month'] = 'week',
323
- trading_days: Optional[List[str]] = TRADING_DAYS,
355
+ period_end_action: Literal['break', 'sleep'] = 'break',
356
+ trading_days: Optional[List[str]] = TradingDays,
324
357
  comment: Optional[str] = None,
325
358
  **kwargs
326
359
  ):
@@ -333,18 +366,20 @@ class ExecutionEngine():
333
366
  time_frame : Time frame to trade. Defaults to '15m'.
334
367
  iter_time : Interval to check for signals and `mm`. Defaults to 5.
335
368
  period : Period to trade. Defaults to 'week'.
369
+ period_end_action : Action to take at the end of the period. Defaults to 'break',
370
+ this only applies when period is 'day', 'week'.
336
371
  trading_days : Trading days in a week. Defaults to monday to friday.
337
372
  comment: Comment for trades. Defaults to None.
338
373
  **kwargs: Additional keyword arguments
339
374
  - strategy_name (Optional[str]): Strategy name. Defaults to None.
340
375
  - max_trades (Dict[str, int]): Maximum trades per symbol. Defaults to None.
341
- - logger (Optional[logging.Logger]): Logger instance. Defaults to None.
342
376
 
343
377
  Note:
344
- 1. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` class
378
+ 1. For `trail` , `stop_trail` , `trail_after_points` , `be_plus_points` see `bbstrader.metatrader.trade.Trade.break_even()` .
379
+ 2. All Strategies must inherit from `bbstrader.btengine.strategy.Strategy` class
345
380
  and have a `calculate_signals` method that returns a dictionary of signals for each symbol in symbol_list.
346
381
 
347
- 2. All strategies must have the following arguments in their `__init__` method:
382
+ 3. All strategies must have the following arguments in their `__init__` method:
348
383
  - bars (DataHandler): DataHandler instance default to None
349
384
  - events (Queue): Queue instance default to None
350
385
  - symbol_list (List[str]): List of symbols to trade can be none for backtesting
@@ -354,16 +389,21 @@ class ExecutionEngine():
354
389
  the `Strategy` class, the `DataHandler` class, the `Portfolio` class and the `ExecutionHandler` class.
355
390
  - The `bars` and `events` arguments are used for backtesting only.
356
391
 
357
- 3. All strategies must generate signals for backtesting and live trading.
392
+ 4. All strategies must generate signals for backtesting and live trading.
358
393
  See the `bbstrader.trading.strategies` module for more information on how to create custom strategies.
359
394
  """
360
395
  self.symbol_list = symbol_list
361
396
  self.trades_instances = trades_instances
362
397
  self.strategy_cls = strategy_cls
363
398
  self.mm = mm
399
+ self.trail = trail
400
+ self.stop_trail = stop_trail
401
+ self.trail_after_points = trail_after_points
402
+ self.be_plus_points = be_plus_points
364
403
  self.time_frame = time_frame
365
404
  self.iter_time = iter_time
366
405
  self.period = period
406
+ self.period_end_action = period_end_action
367
407
  self.trading_days = trading_days
368
408
  self.comment = comment
369
409
  self.kwargs = kwargs
@@ -372,14 +412,21 @@ class ExecutionEngine():
372
412
  if terminal not in _TERMINALS:
373
413
  raise ValueError(
374
414
  f"Invalid terminal: {terminal}. Must be either 'MT5' or 'TWS'")
415
+ elif terminal == 'MT5':
416
+ check_mt5_connection()
375
417
  _TERMINALS[terminal](
376
418
  self.symbol_list,
377
419
  self.trades_instances,
378
420
  self.strategy_cls,
379
421
  mm=self.mm,
422
+ trail=self.trail,
423
+ stop_trail=self.stop_trail,
424
+ trail_after_points=self.trail_after_points,
425
+ be_plus_points=self.be_plus_points,
380
426
  time_frame=self.time_frame,
381
427
  iter_time=self.iter_time,
382
428
  period=self.period,
429
+ period_end_action=self.period_end_action,
383
430
  trading_days=self.trading_days,
384
431
  comment=self.comment,
385
432
  **self.kwargs
@@ -7,7 +7,6 @@ import pandas as pd
7
7
  from queue import Queue
8
8
  import yfinance as yf
9
9
  from datetime import datetime
10
- from typing import List, Literal, Dict, Union, Optional
11
10
  from bbstrader.metatrader.rates import Rates
12
11
  from bbstrader.metatrader.account import Account
13
12
  from bbstrader.btengine.event import SignalEvent
@@ -19,18 +18,20 @@ from bbstrader.btengine.strategy import Strategy
19
18
  from bbstrader.btengine.execution import *
20
19
  from bbstrader.btengine.data import *
21
20
  from bbstrader.tseries import (
22
- KalmanFilterModel, ArimaGarchModel
23
- )
21
+ KalmanFilterModel, ArimaGarchModel)
22
+ from typing import Union, Optional, Literal, Dict, List
23
+
24
24
  __all__ = [
25
25
  'SMAStrategy',
26
26
  'ArimaGarchStrategy',
27
27
  'KalmanFilterStrategy',
28
28
  'StockIndexSTBOTrading',
29
- 'test_strategy'
29
+ 'test_strategy',
30
+ 'get_quantities'
30
31
  ]
31
32
 
32
33
 
33
- def _get_quantities(quantities, symbol_list):
34
+ def get_quantities(quantities, symbol_list):
34
35
  if isinstance(quantities, dict):
35
36
  return quantities
36
37
  elif isinstance(quantities, int):
@@ -84,7 +85,7 @@ class SMAStrategy(Strategy):
84
85
  self.short_window = kwargs.get("short_window", 50)
85
86
  self.long_window = kwargs.get("long_window", 200)
86
87
  self.tf = kwargs.get("time_frame", 'D1')
87
- self.qty = _get_quantities(
88
+ self.qty = get_quantities(
88
89
  kwargs.get('quantities', 100), self.symbol_list)
89
90
  self.sd = kwargs.get("session_duration", 23.0)
90
91
  self.risk_models = build_hmm_models(self.symbol_list, **kwargs)
@@ -246,7 +247,7 @@ class ArimaGarchStrategy(Strategy):
246
247
  self.symbol_list = self.bars.symbol_list
247
248
  self.mode = mode
248
249
 
249
- self.qty = _get_quantities(
250
+ self.qty = get_quantities(
250
251
  kwargs.get('quantities', 100), self.symbol_list)
251
252
  self.arima_window = kwargs.get('arima_window', 252)
252
253
  self.tf = kwargs.get('time_frame', 'D1')
@@ -557,6 +558,7 @@ class KalmanFilterStrategy(Strategy):
557
558
  self.calculate_backtest_signals()
558
559
  elif self.mode == 'live':
559
560
  return self.calculate_live_signals()
561
+
560
562
 
561
563
  class StockIndexSTBOTrading(Strategy):
562
564
  """
@@ -617,7 +619,7 @@ class StockIndexSTBOTrading(Strategy):
617
619
  self.lowerst_price = {index: None for index in symbols}
618
620
 
619
621
  if self.mode == 'backtest':
620
- self.qty = _get_quantities(quantities, symbols)
622
+ self.qty = get_quantities(quantities, symbols)
621
623
  self.num_buys = {index: 0 for index in symbols}
622
624
  self.buy_prices = {index: [] for index in symbols}
623
625
 
@@ -653,13 +655,15 @@ class StockIndexSTBOTrading(Strategy):
653
655
  position.price_open for position in positions
654
656
  if position.type == 0 and position.magic == self.ID
655
657
  ]
658
+ if len(buy_prices) == 0:
659
+ continue
656
660
  avg_price = sum(buy_prices) / len(buy_prices)
657
661
  if self._calculate_pct_change(
658
662
  current_price, avg_price) >= (self.expeted_return[index]):
659
663
  signals[index] = 'EXIT'
660
664
  self.logger.info(
661
665
  f"SYMBOL={index} - Hp={self.heightest_price[index]} - "
662
- f"Lp={self.lowerst_price[index]} - Cp={current_price} - %change={down_change}"
666
+ f"Lp={self.lowerst_price[index]} - Cp={current_price} - %chg={round(down_change, 2)}"
663
667
  )
664
668
  return signals
665
669