@@ -217,6 +217,28 @@ class Position:
217217
218218 cum_profit : float | NA [float ] = 0.0
219219
220+ # Risk management settings
221+ risk_allowed_direction : direction .Direction | None = None
222+ risk_max_cons_loss_days : int | None = None
223+ risk_max_cons_loss_days_alert : str | None = None
224+ risk_max_drawdown_value : float | None = None
225+ risk_max_drawdown_type : QtyType | None = None
226+ risk_max_drawdown_alert : str | None = None
227+ risk_max_intraday_filled_orders : int | None = None
228+ risk_max_intraday_filled_orders_alert : str | None = None
229+ risk_max_intraday_loss_value : float | None = None
230+ risk_max_intraday_loss_type : QtyType | None = None
231+ risk_max_intraday_loss_alert : str | None = None
232+ risk_max_position_size : float | None = None
233+
234+ # Risk management state tracking
235+ risk_cons_loss_days : int = 0
236+ risk_last_day_index : int = - 1
237+ risk_last_day_equity : float = 0.0
238+ risk_intraday_filled_orders : int = 0
239+ risk_intraday_start_equity : float = 0.0
240+ risk_halt_trading : bool = False
241+
220242 def __init__ (self ):
221243 self .orders = {}
222244
@@ -380,6 +402,7 @@ def _fill_order(self, order: Order, price: float, h: float, l: float):
380402 self .size += size
381403 # Handle too small sizes because of floating point inaccuracy and rounding
382404 if math .isclose (self .size , 0.0 , abs_tol = 1 / syminfo ._size_round_factor ):
405+ size -= self .size
383406 self .size = 0.0
384407 self .sign = 0.0 if self .size == 0.0 else 1.0 if self .size > 0.0 else - 1.0
385408 trade .size += size
@@ -479,7 +502,12 @@ def _fill_order(self, order: Order, price: float, h: float, l: float):
479502
480503 # Average entry price
481504 self .entry_summ += price * abs (order .size )
482- self .avg_price = self .entry_summ / abs (self .size )
505+ try :
506+ self .avg_price = self .entry_summ / abs (self .size )
507+ except ZeroDivisionError :
508+ self .avg_price = 0.0
509+ # Unrealized P&L
510+ self .openprofit = self .size * (self .c - self .avg_price )
483511 # Commission summ
484512 self .open_commission += commission
485513
@@ -506,6 +534,8 @@ def fill_order(self, order: Order, price: float, h: float, l: float) -> bool:
506534 # If position direction is about to change, we split it into two separate orders
507535 # This is necessary to create a new average entry price
508536 new_size = self .size + order .size
537+ if math .isclose (new_size , 0.0 , abs_tol = 1 / syminfo ._size_round_factor ): # Check for rounding errors
538+ new_size = 0.0
509539 new_sign = 0.0 if new_size == 0.0 else 1.0 if new_size > 0.0 else - 1.0
510540 if self .size != 0.0 and new_sign != self .sign :
511541 # Create a copy for closing existing position
@@ -685,6 +715,7 @@ def process_orders(self):
685715# Functions
686716#
687717
718+ # noinspection PyProtectedMember
688719def _size_round (qty : float ) -> float :
689720 """
690721 Round size to the nearest possible value
@@ -842,6 +873,11 @@ def entry(id: str, direction: direction.Direction, qty: int | float | NA[float]
842873
843874 script = lib ._script
844875 assert script is not None and script .position is not None
876+ position = script .position
877+
878+ # Risk management: Check if trading is halted
879+ if position .risk_halt_trading :
880+ return
845881
846882 # Get default qty by script parameters if no qty is specified
847883 if isinstance (qty , NA ):
@@ -897,27 +933,56 @@ def entry(id: str, direction: direction.Direction, qty: int | float | NA[float]
897933 return
898934
899935 # We need a signed size instead of qty, the sign is the direction
900- direction : float = (- 1.0 if direction == short else 1.0 )
901- size = qty * direction
936+ direction_sign : float = (- 1.0 if direction == short else 1.0 )
937+ size = qty * direction_sign
902938 sign = 0.0 if size == 0.0 else 1.0 if size > 0.0 else - 1.0
903939
904940 # Change direction
905- if script .position .size :
906- if script .position .sign != sign :
907- size -= script .position .size
941+ is_direction_change = False
942+ if position .size :
943+ if position .sign != sign :
944+ is_direction_change = True
908945 else :
909946 # Handle pyramiding
910947 if script .pyramiding <= len (script .position .open_trades ):
911948 return
912949
950+ # Risk management: Check allowed direction (only for new positions, not direction changes)
951+ if position .risk_allowed_direction is not None :
952+ if (sign > 0 and position .risk_allowed_direction != long ) or \
953+ (sign < 0 and position .risk_allowed_direction != short ):
954+ if not is_direction_change :
955+ return
956+ else :
957+ is_direction_change = False
958+
959+ # We need to adjust the size if we are changing direction
960+ if is_direction_change :
961+ size -= position .size
962+
963+ # Risk management: Check max position size
964+ if position .risk_max_position_size is not None :
965+ new_position_size = abs (position .size + size )
966+ if new_position_size > position .risk_max_position_size :
967+ # Adjust size to not exceed max position size
968+ max_allowed_size = position .risk_max_position_size - abs (position .size )
969+ if max_allowed_size <= 0 :
970+ return
971+ size = max_allowed_size * sign
972+
973+ # Risk management: Check max intraday filled orders
974+ if position .risk_max_intraday_filled_orders is not None :
975+ if position .risk_intraday_filled_orders >= position .risk_max_intraday_filled_orders :
976+ return
977+
913978 size = _size_round (size )
914979 if size == 0.0 :
915980 return
916981
917982 if limit is not None :
918- limit = _price_round (limit , direction )
983+ limit = _price_round (limit , direction_sign )
919984 if stop is not None :
920- stop = _price_round (stop , - direction )
985+ stop = _price_round (stop , - direction_sign )
921986
922987 order = Order (id , size , order_type = _order_type_entry , limit = limit , stop = stop , oca_name = oca_name ,
923988 oca_type = oca_type , comment = comment , alert_message = alert_message )
0 commit comments