#!/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 .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
[文档]
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
[文档]
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
# Store actual order information for creation and execution. When creating, it requests creation; when executing, it produces the final result
[文档]
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
[文档]
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() # 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
[文档]
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
[文档]
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
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
[文档]
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
[文档]
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
[文档]
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
[文档]
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
[文档]
class OrderParams:
"""Simple parameter container for Order classes.
Stores order parameters like owner, data, size, price, execution type, etc.
"""
[文档]
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}")
[文档]
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}'")
# 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 = list()
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
[文档]
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
# # If not simulated, pclose equals closing price, otherwise equals price
# pclose = self.data.close[0] if not self.simulated else self.price
# # If self.price is None and self.pricelimit is None, price equals pclose, otherwise price equals self.price
# if not self.price and not self.pricelimit:
# price = pclose
# else:
# price = self.price
pclose = self.data.close[0] if not self.p.simulated else self.price
price = pclose if not self.price and not self.pricelimit 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
# todo need to understand better where dteos is used
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
[文档]
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
# Get order status name
[文档]
def getstatusname(self, status=None):
"""Returns the name for a given status or the one of the order"""
return self.Status[self.status if status is None else status]
# Get order name
[文档]
def getordername(self, exectype=None):
"""Returns the name for a given exectype or the one of the order"""
return self.ExecTypes[self.exectype if exectype is None else exectype]
[文档]
@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
[文档]
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
[文档]
def active(self):
"""Check if the order is active.
Returns:
bool: True if order is active, False otherwise.
"""
return self._active
# Activate order
[文档]
def activate(self):
"""Activate the order."""
self._active = True
# Order is alive if it's in Created, Submitted, Partial, or Accepted status
[文档]
def alive(self):
"""Returns True if the order is in a status in which it can still be
executed
"""
return self.status in [
OrderBase.Created,
OrderBase.Submitted,
OrderBase.Partial,
OrderBase.Accepted,
]
# Add commission related information
[文档]
def addcomminfo(self, comminfo):
"""Stores a CommInfo scheme associated with the asset"""
self.comminfo = comminfo
# Add information
[文档]
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 self.ref != other.ref
# Check if current order is a buy order
[文档]
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
[文档]
def issell(self):
"""Returns True if the order is a Sell order"""
return self.ordtype == OrderBase.Sell
# Set specific position size for order
[文档]
def setposition(self, position):
"""Receives the current position for the asset and stores it"""
self.position = position
# Submit order to broker
[文档]
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
[文档]
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
[文档]
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
[文档]
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
[文档]
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
[文档]
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
[文档]
def completed(self):
"""Marks an order as completely filled"""
self.status = OrderBase.Completed
# Partial execution
[文档]
def partial(self):
"""Marks an order as partially filled"""
self.status = OrderBase.Partial
# Execute order
[文档]
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
[文档]
def expire(self):
"""Marks an order as expired. Returns True if it worked"""
self.status = OrderBase.Expired
return True
# Trail price adjustment
[文档]
def trailadjust(self, price):
"""Adjust trailing stop price.
Args:
price: Current price for trailing calculation.
Note:
Generic interface - override in subclasses for specific behavior.
"""
pass # generic interface
# Modified Order class to work without metaclass
[文档]
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 = None
# Override initialization function, add processing for ordtype and dteos
[文档]
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
[文档]
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
[文档]
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
[文档]
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:
self.created.price = price_new
elif 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
[文档]
class BuyOrder(Order):
"""Buy order class.
Represents a buy order with ordtype set to Order.Buy.
"""
ordtype = Order.Buy
# Stop buy order
[文档]
class StopBuyOrder(BuyOrder):
"""Stop buy order class.
Used for buy orders that trigger when price crosses a threshold.
"""
pass
# Create stop limit buy order
[文档]
class StopLimitBuyOrder(BuyOrder):
"""Stop limit buy order class.
Used for buy orders that become limit orders after stop price is triggered.
"""
pass
# Create sell order
[文档]
class SellOrder(Order):
"""Sell order class.
Represents a sell order with ordtype set to Order.Sell.
"""
ordtype = Order.Sell
# Create stop sell order
[文档]
class StopSellOrder(SellOrder):
"""Stop sell order class.
Used for sell orders that trigger when price crosses a threshold.
"""
pass
# Create stop limit sell order
[文档]
class StopLimitSellOrder(SellOrder):
"""Stop limit sell order class.
Used for sell orders that become limit orders after stop price is triggered.
"""
pass