@@ -85,6 +85,7 @@ class Order:
8585 "trail_price" , "trail_offset" ,
8686 "trail_triggered" ,
8787 "profit_ticks" , "loss_ticks" , "trail_points_ticks" , # Store tick values for later calculation
88+ "is_market_order" , # Flag to check if this is a market order
8889 )
8990
9091 def __init__ (
@@ -129,6 +130,9 @@ def __init__(
129130 self .loss_ticks = loss_ticks
130131 self .trail_points_ticks = trail_points_ticks
131132
133+ # Check if this is a market order (no limit, stop, or trail price)
134+ self .is_market_order = self .limit is None and self .stop is None and self .trail_price is None
135+
132136 def __repr__ (self ):
133137 return f"Order(order_id={ self .order_id } ; exit_id={ self .exit_id } ; size={ self .size } ; type: { self .order_type } ; " \
134138 f"limit={ self .limit } ; stop={ self .stop } ; " \
@@ -468,9 +472,8 @@ def _fill_order(self, order: Order, price: float, h: float, l: float):
468472
469473 self .open_trades = open_trades
470474 if delete :
471- if order .exit_id is not None :
472- # Remove from exit_orders dict
473- self .exit_orders .pop (order .exit_id , None )
475+ # Remove from exit_orders dict
476+ self .exit_orders .pop (order .exit_id , None )
474477
475478 if commission_type == _commission .cash_per_order :
476479 # Realize commission
@@ -565,16 +568,35 @@ def fill_order(self, order: Order, price: float, h: float, l: float) -> bool:
565568 new_size = 0.0
566569 new_sign = 0.0 if new_size == 0.0 else 1.0 if new_size > 0.0 else - 1.0
567570 if self .size != 0.0 and new_sign != self .sign and new_size != 0.0 :
571+ # Exit orders should never reverse position direction
572+ # Only entry orders can open new positions or reverse direction
573+ if order .order_type == _order_type_close :
574+ # Limit the exit order size to just close the position
575+ order .size = - self .size
576+ self ._fill_order (order , price , h , l )
577+ return False
578+
568579 # Create a copy for closing existing position
569580 order1 = copy (order )
570581 order1 .order_type = _order_type_close
571582 order1 .size = - self .size
572583 # Set order_id to None so it will close any open trades
573584 order1 .order_id = None
574- # Don't need to remove this order, because it is not in the orders list
575- order1 .exit_id = None
585+ # The exit_id will be the order_id of the original order
586+ order1 .exit_id = order . order_id
576587 # Fill the closing order first
577588 self ._fill_order (order1 , price , h , l )
589+
590+ # Check if new direction is allowed by risk management
591+ # According to Pine Script docs: "long exit trades will be made instead of reverse trades"
592+ new_direction_sign = 1.0 if new_size > 0.0 else - 1.0
593+ if self .risk_allowed_direction is not None :
594+ if (new_direction_sign > 0 and self .risk_allowed_direction != long ) or \
595+ (new_direction_sign < 0 and self .risk_allowed_direction != short ):
596+ # Direction not allowed - convert entry to exit only
597+ # Don't open new position in restricted direction
598+ return False
599+
578600 # Modify the original order to open a position in the new direction
579601 order .size = new_size
580602 # Store in the appropriate dict based on order type
@@ -666,15 +688,17 @@ def process_orders(self):
666688 self .l = round_to_mintick (lib .low )
667689 self .c = round_to_mintick (lib .close )
668690
691+ # Get script reference for slippage
692+ script = lib ._script
693+
669694 # If the order is open → high → low → close or open → low → high → close
670695 ohlc = abs (self .h - self .o ) < abs (self .l - self .o )
671696
672697 self .drawdown_summ = self .runup_summ = 0.0
673698 self .new_closed_trades .clear ()
674699
675- # Process all orders: entry orders first, then exit orders (guaranteed order with chain)
676- from itertools import chain
677- for order in list (chain (self .entry_orders .values (), self .exit_orders .values ())):
700+ # Process all orders: entry orders first, then exit orders (guaranteed order)
701+ for order in list (self .entry_orders .values ()) + list (self .exit_orders .values ()):
678702 # For exit orders, calculate limit/stop from entry price if ticks are specified
679703 if order .order_type == _order_type_close and order .order_id :
680704 # Try to find the trade with matching entry_id
@@ -705,13 +729,22 @@ def process_orders(self):
705729 order .trail_price = _price_round (order .trail_price , direction )
706730
707731 # Market orders
708- if order .limit is None and order .stop is None and order .trail_price is None :
732+ if order .is_market_order :
733+ # Apply slippage to market orders
734+ fill_price = self .o
735+ if script .slippage > 0 :
736+ # Slippage is in ticks, always adverse to trade direction
737+ # For long orders (buying), slippage increases the price
738+ # For short orders (selling), slippage decreases the price
739+ slippage_amount = syminfo .mintick * script .slippage * order .sign
740+ fill_price = self .o + slippage_amount
741+
709742 # open → high → low → close
710743 if ohlc :
711- self .fill_order (order , self . o , self .o , self .l )
744+ self .fill_order (order , fill_price , self .o , self .l )
712745 # open → low → high → close
713746 else :
714- self .fill_order (order , self . o , self .l , self .o )
747+ self .fill_order (order , fill_price , self .l , self .o )
715748
716749 # Limit/Stop orders
717750 else :
@@ -725,7 +758,7 @@ def process_orders(self):
725758 self ._check_low (order )
726759
727760 # 2nd round of process open orders
728- for order in list (chain ( self .entry_orders .values (), self .exit_orders .values () )):
761+ for order in list (self .entry_orders .values ()) + list ( self .exit_orders .values ()):
729762 # For exit orders, calculate limit/stop from entry price if not already done
730763 if order .order_type == _order_type_close and order .order_id :
731764 # Only recalculate if not already set in first round
@@ -1095,28 +1128,22 @@ def entry(id: str, direction: direction.Direction, qty: int | float | NA[float]
10951128 size = qty * direction_sign
10961129 sign = 0.0 if size == 0.0 else 1.0 if size > 0.0 else - 1.0
10971130
1098- # Change direction
1099- is_direction_change = False
1131+ # Check pyramiding limit (only for same direction trades)
11001132 if position .size :
1101- if position .sign != sign :
1102- is_direction_change = True
1103- else :
1104- # Handle pyramiding
1133+ if position .sign == sign :
1134+ # Same direction - check pyramiding
11051135 if script .pyramiding <= len (script .position .open_trades ):
11061136 return
11071137
1108- # Risk management: Check allowed direction (only for new positions, not direction changes)
1138+ # Risk management: Check allowed direction for new positions
1139+ # Direction changes are handled in fill_order() which will convert entry to exit if needed
11091140 if position .risk_allowed_direction is not None :
11101141 if (sign > 0 and position .risk_allowed_direction != long ) or \
11111142 (sign < 0 and position .risk_allowed_direction != short ):
1112- if not is_direction_change :
1113- return
1114- else :
1115- is_direction_change = False
1116-
1117- # We need to adjust the size if we are changing direction
1118- if is_direction_change :
1119- size -= position .size
1143+ # Check if this would be a new position (not a direction change)
1144+ if not position .size or position .sign == sign :
1145+ return # Block new positions in restricted direction
1146+ # For direction changes, let fill_order() handle the conversion to exit
11201147
11211148 # Risk management: Check max position size
11221149 if position .risk_max_position_size is not None :
0 commit comments