Source code for backtrader.order

#!/usr/bin/env python
"""Backtrader Order Module.

This module provides order data structures and execution tracking.

Key Classes:
    OrderExecutionBit: Holds information about a single order execution.
    OrderData: Holds the full order data including creation and execution details.

The order system supports:
    - Order creation and execution tracking
    - Partial execution handling
    - Commission calculation
    - PnL calculation for closed positions
    - Position size and price tracking
"""

import collections
import datetime
import itertools
from copy import copy
from typing import Optional

from .utils import AutoOrderedDict
from .utils.py3 import iteritems, range


# Store order execution related information. This information does not determine if the order is fully or partially executed, it only stores the information
[docs] class OrderExecutionBit: """ Intended to hold information about order execution. A "bit" does not determine if the order has been fully/partially executed, it just holds information. Member Attributes: - dt: datetime (float) execution time # Execution time, float - size: how much was executed # How much was executed - price: execution price # Execution price - closed: how much of the execution closed an existing position # How much of existing position was closed - opened: how much of the execution opened a new position # How much new position was opened - openedvalue: market value of the "opened" part # Market value of opened position - closedvalue: market value of the "closed" part # Market value of closed position part - closedcomm: commission for the "closed" part # Commission for closed position part - openedcomm: commission for the "opened" part # Commission for opened position part - value: market value for the entire bit size # Market value of entire position - comm: commission for the entire bit execution # Commission for entire position - pnl: pnl generated by this bit (if something was closed) # PnL from closing part of position - psize: current open position size # Size of already opened position - pprice: current open position price # Price of already opened position """ # Initialize order execution information
[docs] def __init__( self, dt=None, size=0, price=0.0, closed=0, closedvalue=0.0, closedcomm=0.0, opened=0, openedvalue=0.0, openedcomm=0.0, pnl=0.0, psize=0, pprice=0.0, ): """Initialize order execution bit information. Args: dt: Execution datetime. size: Executed size. price: Execution price. closed: Size of position closed. closedvalue: Value of closed position. closedcomm: Commission for closed position. opened: Size of new position opened. openedvalue: Value of opened position. openedcomm: Commission for opened position. pnl: Profit/loss from closed position. psize: Current position size. pprice: Current position price. """ self.dt = dt self.size = size self.price = price self.closed = closed self.opened = opened self.closedvalue = closedvalue self.openedvalue = openedvalue self.closedcomm = closedcomm self.openedcomm = openedcomm self.value = closedvalue + openedvalue self.comm = closedcomm + openedcomm self.pnl = pnl self.psize = psize self.pprice = pprice
def __repr__(self): return ( f"OrderExecutionBit(size={self.size}, price={self.price}, " f"closed={self.closed}, opened={self.opened}, pnl={self.pnl})" )
# Store actual order information for creation and execution. When creating, it requests creation; when executing, it produces the final result
[docs] class OrderData: """ Holds actual order data for Creation and Execution. In the case of Creation the request made and in the case of Execution the actual outcome. Member Attributes: - exbits : iterable of OrderExecutionBits for this OrderData # Serialized order execution information for this order - dt: datetime (float) creation/execution time # Order creation or execution time, string format - size: requested/executed size # Creation or execution size - price: execution price # Execution price. If no price or limit price is given, the order creation # or current closing price will be used as reference Note: if no price is given and no pricelimite is given, the closing price at the time or order creation will be used as reference - pricelimit: holds pricelimit for StopLimit (which has trigger first) # Limit price for stop limit (triggered first) - trailamount: absolute price distance in trailing stops # Absolute price distance in trailing stop - trailpercent: percentage price distance in trailing stops # Percentage distance in trailing stop - value: market value for the entire bit size # Market value of entire position - comm: commission for the entire bit execution # Commission for entire position execution - pnl: pnl generated by this bit (if something was closed) # PnL after closing position - margin: margin incurred by the Order (if any) # Margin required for order - psize: current open position size # Current position size - pprice: current open position price # Current position price """ # According to the docs, collections.deque is thread-safe with appends at # both ends, there will be no pop (nowhere) and therefore to know which the # new exbits are two indices are needed. At time of cloning (__copy__) the # indices can be updated to match the previous end, and the new end # (len(exbits) # Example: start 0, 0 -> islice(exbits, 0, 0) -> [] # One added -> copy -> updated 0, 1 -> islice(exbits, 0, 1) -> [1 elem] # Other added -> copy -> updated 1, 2 -> islice(exbits, 1, 2) -> [1 elem] # "add" and "__copy__" happen always in the same thread (with all current # implementations) and therefore no append will happen during a copy and # the len of the exbits can be queried with no concerns about another # thread making an append and with no need for a lock
[docs] def __init__( self, dt=None, size=0, price=0.0, pricelimit=0.0, remsize=0, pclose=0.0, trailamount=0.0, trailpercent=0.0, ): """Initialize order data. Args: dt: Order datetime. size: Order size. price: Order price. pricelimit: Limit price for stop orders. remsize: Remaining size to execute. pclose: Previous close price. trailamount: Trailing amount for stop orders. trailpercent: Trailing percent for stop orders. """ self.pclose = pclose self.exbits: collections.deque = collections.deque() # for historical purposes self.p1, self.p2 = 0, 0 # indices to pending notifications self.dt = dt self.size = size self.remsize = remsize self.price = price self.pricelimit = pricelimit self.trailamount = trailamount self.trailpercent = trailpercent # If no limit price, use the price as limit price if not pricelimit: # if no pricelimit is given, use the given price self.pricelimit = self.price # If there is limit price but no price, price equals limit price if pricelimit and not price: # price must always be set if pricelimit is set ... self.price = pricelimit # Limit price self.plimit = pricelimit self.value = 0.0 self.comm = 0.0 self.margin = None self.pnl = 0.0 self.psize = 0 self.pprice = 0
# Set plimit property def _getplimit(self): return self._plimit def _setplimit(self, val): self._plimit = val plimit = property(_getplimit, _setplimit) # Return length of execution information def __len__(self): return len(self.exbits) # Get execution information value def __getitem__(self, key): return self.exbits[key] # Add execution information
[docs] def add( self, dt, size, price, closed=0, closedvalue=0.0, closedcomm=0.0, opened=0, openedvalue=0.0, openedcomm=0.0, pnl=0.0, psize=0, pprice=0.0, ): """Add execution information to this order. Args: dt: Execution datetime. size: Executed size. price: Execution price. closed: Size of position closed. closedvalue: Value of closed position. closedcomm: Commission for closed position. opened: Size of new position opened. openedvalue: Value of opened position. openedcomm: Commission for opened position. pnl: Profit/loss from closed position. psize: Current position size. pprice: Current position price. """ self.addbit( OrderExecutionBit( dt, size, price, closed, closedvalue, closedcomm, opened, openedvalue, openedcomm, pnl, psize, pprice, ) )
# Adjust current attributes based on order execution
[docs] def addbit(self, exbit): """Store an execution bit and recalculate order values. Args: exbit: OrderExecutionBit to add. """ # Stores an ExecutionBit and recalculates own values from ExBit self.exbits.append(exbit) self.remsize -= exbit.size self.dt = exbit.dt oldvalue = self.size * self.price newvalue = exbit.size * exbit.price self.size += exbit.size self.price = (oldvalue + newvalue) / self.size if self.size else 0.0 self.value += exbit.value self.comm += exbit.comm self.pnl += exbit.pnl self.psize = exbit.psize self.pprice = exbit.pprice
# Get current pending execution information
[docs] def getpending(self): """Get list of pending execution bits. Returns: list: List of pending OrderExecutionBit objects. """ return list(self.iterpending())
# Slice order pending execution information, if p1 and p2 both equal 0, returns empty
[docs] def iterpending(self): """Iterate over pending execution bits. Returns: iterator: Iterator over pending OrderExecutionBit objects. """ return itertools.islice(self.exbits, self.p1, self.p2)
# Mark which pending order execution information
[docs] def markpending(self): """Mark current execution bits as pending. Rebuilds the indices to mark which exbits are pending in clone. """ # rebuild the indices to mark which exbits are pending in clone self.p1, self.p2 = self.p2, len(self.exbits)
# Clone the object
[docs] def clone(self): """Clone the OrderData object. Returns: OrderData: A cloned copy with marked pending bits. """ self.markpending() obj = copy(self) return obj
# Simple parameter container to replace metaclass functionality
[docs] class OrderParams: """Simple parameter container for Order classes. Stores order parameters like owner, data, size, price, execution type, etc. """
[docs] def __init__(self, **kwargs): """Initialize order parameters with defaults. Args: **kwargs: Keyword arguments to override default parameters. Raises: AttributeError: If an invalid parameter is provided. """ # Default parameters defaults = { "owner": None, "data": None, "size": None, "price": None, "pricelimit": None, "exectype": None, "valid": None, "tradeid": 0, "oco": None, "trailamount": None, "trailpercent": None, "parent": None, "transmit": True, "simulated": False, "histnotify": False, } # Set defaults first for key, value in defaults.items(): setattr(self, key, value) # Override with provided kwargs for key, value in kwargs.items(): if hasattr(self, key): setattr(self, key, value) else: raise AttributeError(f"Invalid parameter: {key}")
[docs] class OrderBase: """Base class for order objects. Provides the foundation for all order types with common attributes and methods for order tracking, status management, and execution. Class Attributes: DAY: Constant for day order identification. Market, Close, Limit, Stop, StopLimit, StopTrail, StopTrailLimit: Order execution types. Buy, Sell: Order direction types. Created, Submitted, Accepted, Partial, Completed, Canceled, Expired, Margin, Rejected: Order status codes. Attributes: ref: Unique order reference number. broker: Broker instance handling this order. p: OrderParams instance containing order parameters. """ # Basic parameters for orders - removed metaclass usage # DAY currently represents empty time delta DAY = datetime.timedelta() # constant for DAY order identification # Time Restrictions for orders # Time restrictions for orders T_Close, T_Day, T_Date, T_None = range(4) # Volume Restrictions for orders # Volume restrictions for orders V_None = range(1) # Different order types, represented by different numbers Market, Close, Limit, Stop, StopLimit, StopTrail, StopTrailLimit, Historical = range(8) ExecTypes = [ "Market", "Close", "Limit", "Stop", "StopLimit", "StopTrail", "StopTrailLimit", "Historical", ] # Order direction types OrdTypes = ["Buy", "Sell"] Buy, Sell = range(2) # Different order statuses Created, Submitted, Accepted, Partial, Completed, Canceled, Expired, Margin, Rejected = range(9) Cancelled = Canceled # alias Status = [ "Created", "Submitted", "Accepted", "Partial", "Completed", "Canceled", "Expired", "Margin", "Rejected", ] # Add a number for each order refbasis = itertools.count(1) # for a unique identifier per order # Set/get plimit property def _getplimit(self): return self._plimit def _setplimit(self, val): self._plimit = val plimit = property(_getplimit, _setplimit) # Get order attribute - modified to work with OrderParams def __getattr__(self, name): # Return attr from params if not found in order # PERFORMANCE OPTIMIZATION: Use try/except instead of hasattr # Called 1.65M+ times, reduce attribute lookups if name == "p": # Avoid recursion when checking for 'p' itself raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") try: p = object.__getattribute__(self, "p") return getattr(p, name) except AttributeError: raise AttributeError( f"'{self.__class__.__name__}' object has no attribute '{name}'" ) from None # Set order attribute - modified to work with OrderParams def __setattr__(self, name, value): # Check if we have params and the name exists in params # Use object.__getattribute__ to avoid recursion try: p = object.__getattribute__(self, "p") if hasattr(p, name): setattr(p, name, value) return except AttributeError: pass # p doesn't exist yet, fall through to normal assignment super().__setattr__(name, value) # Content displayed when printing order def __str__(self): tojoin = [] tojoin.append(f"Ref: {self.ref}") tojoin.append(f"OrdType: {self.ordtype}") tojoin.append(f"OrdType: {self.ordtypename()}") tojoin.append(f"Status: {self.status}") tojoin.append(f"Status: {self.getstatusname()}") tojoin.append(f"Size: {self.size}") tojoin.append(f"Price: {self.price}") tojoin.append(f"Price Limit: {self.pricelimit}") tojoin.append(f"TrailAmount: {self.trailamount}") tojoin.append(f"TrailPercent: {self.trailpercent}") tojoin.append(f"ExecType: {self.exectype}") tojoin.append(f"ExecType: {self.getordername()}") tojoin.append(f"CommInfo: {self.comminfo}") tojoin.append(f"End of Session: {self.dteos}") tojoin.append(f"Info: {self.info}") tojoin.append(f"Broker: {self.broker}") tojoin.append(f"Alive: {self.alive()}") return "\n".join(tojoin) # Initialize class - modified to accept kwargs and create params manually
[docs] def __init__(self, **kwargs): """Initialize the order base instance. Args: **kwargs: Order parameters (owner, data, size, price, etc.). """ # Create params object manually instead of using metaclass self.p = OrderParams(**kwargs) # Create convenient direct access to params - alias for backward compatibility self.params = self.p # Increment a number each time an instance is created self.plen = None self.ref = next(self.refbasis) # broker defaults to None self.broker = None # order info information self.info = AutoOrderedDict() # commission defaults to None self.comminfo = None # triggered defaults to None self.triggered = False # If self.parent is None, self._active is True, otherwise it's None self._active = self.parent is None # Order status, when order initializes, defaults to Created self.status = OrderBase.Created # Set plimit property value self.plimit = self.p.pricelimit # alias via property # If order execution type is None, default is Market order if self.exectype is None: self.exectype = OrderBase.Market # If order is not a buy order, order size becomes negative if not self.isbuy(): self.size = -self.size # Set a reference price if price is not set using the close price pclose = self.data.close[0] if not self.p.simulated else self.price price = pclose if self.price is None and self.pricelimit is None else self.price # If not simulated, order creation time equals current data time, otherwise it's 0 dcreated = self.data.datetime[0] if not self.p.simulated else 0.0 # Order creation self.created = OrderData( dt=dcreated, size=self.size, price=price, pricelimit=self.pricelimit, pclose=pclose, trailamount=self.trailamount, trailpercent=self.trailpercent, ) # Adjust price in case a trailing limit is wished # If execution type is trailing stop, price needs adjustment. Limit offset equals created price minus created limit price # Price equals order creation price, reset created order price to infinity for buy orders, negative infinity for sell orders # Then adjust price; if not trailing stop type, limit offset is 0 if self.exectype in [OrderBase.StopTrail, OrderBase.StopTrailLimit]: self._limitoffset = self.created.price - self.created.pricelimit price = self.created.price self.created.price = float("inf" * self.isbuy() or "-inf") self.trailadjust(price) else: self._limitoffset = 0.0 # Order execution self.executed = OrderData(remsize=self.size) # Position set to 0 self.position = 0 # Next is to determine order validity period # If validity parameter is a date format if isinstance(self.valid, datetime.date): # comparison will later be done against the raw datetime[0] value # Convert date format to number self.valid = self.data.date2num(self.valid) # If validity parameter is a time delta format, if time delta is 0, valid for the day, otherwise current time plus time delta # Then convert obtained validity to number elif isinstance(self.valid, datetime.timedelta): # offset with regards to now ... get utcnow + offset # when reading with date2num ... it will be automatically localized if self.valid == self.DAY: valid = datetime.datetime.combine( self.data.datetime.date(), datetime.time(23, 59, 59, 9999) ) else: valid = self.data.datetime.datetime() + self.valid self.valid = self.data.date2num(valid) # If validity is not None, if not 0, valid for the day, if 0, currently valid elif self.valid is not None: if not self.valid: # avoid comparing None and 0 valid = datetime.datetime.combine( self.data.datetime.date(), datetime.time(23, 59, 59, 9999) ) else: # assume float valid = self.data.datetime[0] + self.valid # If not simulated, get dteos, if simulated, dteos is 0 # dteos: provisional end-of-session datetime, used by broker for order expiry checks if not self.p.simulated: # provisional end-of-session # get next session end dtime = self.data.datetime.datetime(0) session = self.data.p.sessionend dteos = dtime.replace( hour=session.hour, minute=session.minute, second=session.second, microsecond=session.microsecond, ) if dteos < dtime: # eos before current time ... no ... must be at least next day dteos += datetime.timedelta(days=1) self.dteos = self.data.date2num(dteos) else: self.dteos = 0.0
# Clone the order itself
[docs] def clone(self): """Clone the order. Returns: OrderBase: A cloned copy with cloned executed OrderData. """ # status, triggered and executed are the only moving parts in order # status and triggered are covered by copy # executed has to be replaced with an intelligent clone of itself obj = copy(self) obj.executed = self.executed.clone() return obj # status could change in next to completed
@property def position_side(self): """Read-only alias for order.info.position_side.""" return getattr(self.info, "position_side", None) @property def offset(self): """Read-only alias for order.info.offset.""" return getattr(self.info, "offset", None) # Get order status name
[docs] def getstatusname(self, status=None): """Returns the name for a given status or the one of the order""" idx = self.status if status is None else status try: return self.Status[idx] except (IndexError, TypeError): return f"Unknown({idx})"
# Get order name
[docs] def getordername(self, exectype=None): """Returns the name for a given exectype or the one of the order""" idx = self.exectype if exectype is None else exectype try: return self.ExecTypes[idx] except (IndexError, TypeError): return f"Unknown({idx})"
[docs] @classmethod def ExecType(cls, exectype): """Get the execution type constant from the class. Args: exectype: String name of the execution type. Returns: int: The execution type constant. """ return getattr(cls, exectype)
# Get order type name
[docs] def ordtypename(self, ordtype=None): """Returns the name for a given ordtype or the one of the order""" return self.OrdTypes[self.ordtype if ordtype is None else ordtype]
# Get active status
[docs] def active(self): """Check if the order is active. Returns: bool: True if order is active, False otherwise. """ return self._active
# Activate order
[docs] def activate(self): """Activate the order.""" self._active = True
# Frozenset for O(1) alive status lookup _ALIVE_STATUSES = frozenset(range(4)) # Created, Submitted, Accepted, Partial # Order is alive if it's in Created, Submitted, Partial, or Accepted status
[docs] def alive(self): """Returns True if the order is in a status in which it can still be executed """ return self.status in self._ALIVE_STATUSES
# Add commission related information
[docs] def addcomminfo(self, comminfo): """Stores a CommInfo scheme associated with the asset""" self.comminfo = comminfo
# Add information
[docs] def addinfo(self, **kwargs): """Add the keys, values of kwargs to the internal info dictionary to hold custom information in the order """ for key, val in iteritems(kwargs): self.info[key] = val
# Check if two orders are equal def __eq__(self, other): return other is not None and self.ref == other.ref # Check if two orders are not equal def __ne__(self, other): return other is None or self.ref != other.ref # Order ref is immutable, so hash based on ref is safe def __hash__(self): return hash(self.ref) # Check if current order is a buy order
[docs] def isbuy(self): """Returns True if the order is a Buy order""" return self.ordtype == OrderBase.Buy
# Check if current order is a sell order
[docs] def issell(self): """Returns True if the order is a Sell order""" return self.ordtype == OrderBase.Sell
# Set specific position size for order
[docs] def setposition(self, position): """Receives the current position for the asset and stores it""" self.position = position
# Submit order to broker
[docs] def submit(self, broker=None): """Marks an order as submitted and stores the broker to which it was submitted""" self.status = OrderBase.Submitted self.broker = broker self.plen = len(self.data)
# Accept order
[docs] def accept(self, broker=None): """Marks an order as accepted""" self.status = OrderBase.Accepted self.broker = broker
# Broker status, if broker is not None or 0, try to get order status from broker, if broker is None, directly return order status
[docs] def brokerstatus(self): """Tries to retrieve the status from the broker in which the order is. Defaults to last known status if no broker is associated""" if self.broker: return self.broker.orderstatus(self) return self.status
# Reject order, if already rejected return False, otherwise set order status and rejection execution time, broker, then return True
[docs] def reject(self, broker=None): """Marks an order as rejected""" if self.status == OrderBase.Rejected: return False self.status = OrderBase.Rejected # self.executed.dt = self.data.datetime[0] self.broker = broker if not self.p.simulated: self.executed.dt = self.data.datetime[0] return True
# Cancel order
[docs] def cancel(self): """Marks an order as cancelled""" self.status = OrderBase.Canceled # self.executed.dt = self.data.datetime[0] if not self.p.simulated: self.executed.dt = self.data.datetime[0]
# Insufficient margin, add margin
[docs] def margin(self): """Marks an order as having met a margin call""" self.status = OrderBase.Margin # self.executed.dt = self.data.datetime[0] if not self.p.simulated: self.executed.dt = self.data.datetime[0]
# Complete
[docs] def completed(self): """Marks an order as completely filled""" self.status = OrderBase.Completed
# Partial execution
[docs] def partial(self): """Marks an order as partially filled""" self.status = OrderBase.Partial
# Execute order
[docs] def execute( self, dt, size, price, closed, closedvalue, closedcomm, opened, openedvalue, openedcomm, margin, pnl, psize, pprice, ): """Receives data execution input and stores it""" if not size: return self.executed.add( dt, size, price, closed, closedvalue, closedcomm, opened, openedvalue, openedcomm, pnl, psize, pprice, ) self.executed.margin = margin
# Order expiration
[docs] def expire(self): """Marks an order as expired. Returns True if it worked""" self.status = OrderBase.Expired return True
# Trail price adjustment
[docs] def trailadjust(self, price): """Adjust trailing stop price. Args: price: Current price for trailing calculation. Note: Generic interface - override in subclasses for specific behavior. """
# generic interface # Modified Order class to work without metaclass
[docs] class Order(OrderBase): """Order class for buy/sell orders. Extends OrderBase with order type (buy/sell) and session end time handling. This is the main order class used for creating and managing trading orders. Attributes: ordtype: Order type (Buy or Sell). dteos: Date/time of end of session for order validity. """ # Above is processing of OrderBase, below is processing of Order, Order inherits from OrderBase # Order class mainly adds dteos, ordtype and other information, also rewrites some functions, adds ordtype, a tracking price # ordtype variable determines whether this order is a buy order or sell order, not set by default ordtype: Optional[int] = None # Override initialization function, add processing for ordtype and dteos
[docs] def __init__(self, **kwargs): """Initialize the order instance. Args: **kwargs: Order parameters (owner, data, size, price, etc.). """ super().__init__(**kwargs) # For Order, additional operations on dteos are needed # dteos represents the end time of this session # The code logic below is: # dteos == 0.0 represents day order, i.e., order valid for the day, in this case dteos is the session end time of the day # dteos >= self.data.datetime[0] means the order's validity period is greater than the current data time, no need to modify dteos # In other cases, set dteos to 0, making it a day order if self.dteos == 0.0: # day order -> till session end if not changed before pass elif self.dteos >= self.data.datetime[0]: # if dteos is in future -> inform order it's a GTD (good till date) pass else: # If current time exceeds dteos, set dteos to 0.0 # Expiration date less than current time -> becomes day order self.dteos = 0.0
# Execute this order, many parameters need to be passed during execution
[docs] def execute( self, dt, size, price, closed, closedvalue, closedcomm, opened, openedvalue, openedcomm, margin, pnl, psize, pprice, ): """Execute the order with given parameters. Args: dt: Execution datetime. size: Executed size. price: Execution price. closed: Size of position closed. closedvalue: Value of closed position. closedcomm: Commission for closed position. opened: Size of new position opened. openedvalue: Value of opened position. openedcomm: Commission for opened position. margin: Margin required for the order. pnl: Profit/loss from closed position. psize: Current position size. pprice: Current position price. """ self.executed.add( dt, size, price, closed, closedvalue, closedcomm, opened, openedvalue, openedcomm, pnl, psize, pprice, ) if margin is not None: self.executed.margin = margin if self.executed.remsize: self.status = OrderBase.Partial else: self.status = OrderBase.Completed
# Order expiration
[docs] def expire(self): """Check if order should be expired Returns: True: If order has expired False: If order has not expired """ # Market orders don't expire, will always be executed if self.exectype == Order.Market: return False # Check if order exceeds validity period if self.valid and self.data.datetime[0] > self.valid: self.status = Order.Expired self.executed.dt = self.data.datetime[0] return True return False
# Trail adjust price, trail adjust price is for trailing stop orders. Trailing stop order is also a moving stop order, # the moving distance can be represented by absolute value or percentage. This function is mainly to calculate # the price after trailing stop order adjustment
[docs] def trailadjust(self, price): """Adjust trailing stop order price. Args: price: Current market price for trailing calculation. For buy orders: stop price moves up as price increases. For sell orders: stop price moves down as price decreases. """ # If moving amount, price adjustment amount is the moving amount; if moving percentage, # price adjustment amount is price multiplied by percentage, otherwise price adjustment amount is 0 if self.trailamount: adjsize = self.trailamount elif self.trailpercent: adjsize = price * self.trailpercent else: adjsize = 0.0 # CRITICAL FIX: BUY stop is ABOVE market (+adjsize), SELL stop is BELOW market (-adjsize) # Original formula was backwards: (1 - 2*isbuy) gave -1 for buy, +1 for sell # Correct formula: (2*isbuy - 1) gives +1 for buy, -1 for sell price_new = price + adjsize * (2 * self.isbuy() - 1) # If price_new surpasses self.created.price -> readjust # If new price exceeds originally created price, readjust. # For buy orders, if new price is less than created price, use this new price # For sell orders, if new price is greater than created price, use this new price if price_new != self.created.price: if ( self.isbuy() and price_new < self.created.price or self.issell() and price_new > self.created.price ): self.created.price = price_new # For both trailing stop types, limitprice also needs adjustment if self.exectype == OrderBase.StopTrailLimit: self.created.pricelimit = self.created.price + self._limitoffset
# Buy order
[docs] class BuyOrder(Order): """Buy order class. Represents a buy order with ordtype set to Order.Buy. """ ordtype = Order.Buy
# Stop buy order
[docs] class StopBuyOrder(BuyOrder): """Stop buy order class. Used for buy orders that trigger when price crosses a threshold. """
# Create stop limit buy order
[docs] class StopLimitBuyOrder(BuyOrder): """Stop limit buy order class. Used for buy orders that become limit orders after stop price is triggered. """
# Create sell order
[docs] class SellOrder(Order): """Sell order class. Represents a sell order with ordtype set to Order.Sell. """ ordtype = Order.Sell
# Create stop sell order
[docs] class StopSellOrder(SellOrder): """Stop sell order class. Used for sell orders that trigger when price crosses a threshold. """
# Create stop limit sell order
[docs] class StopLimitSellOrder(SellOrder): """Stop limit sell order class. Used for sell orders that become limit orders after stop price is triggered. """