#!/usr/bin/env python
"""Backtrader Broker Module.
This module provides the broker system for order execution and portfolio
management. It handles order creation, position tracking, cash management,
and commission calculation.
Key Classes:
BrokerBase: Base class for broker implementations.
BrokerAliasMixin: Mixin providing method aliases.
The broker system supports:
- Order execution (buy, sell, cancel)
- Position management
- Cash and value tracking
- Commission schemes
- Order history
"""
from .comminfo import CommInfoBase
from .parameters import ParameterDescriptor, ParameterizedBase
# from . import fillers as fillers
# from . import fillers as filler
# Create a mixin to handle aliases without using metaclasses
[文档]
class BrokerAliasMixin:
"""Mixin to provide method aliases without using metaclasses.
This mixin creates method aliases for compatibility with different
naming conventions (e.g., get_cash/getcash, get_value/getvalue).
"""
[文档]
def __init__(self, *args, **kwargs):
"""Initialize the broker alias mixin.
Creates method aliases for compatibility:
- get_cash -> getcash
- get_value -> getvalue
Args:
*args: Positional arguments passed to parent.
**kwargs: Keyword arguments passed to parent.
"""
super().__init__(*args, **kwargs)
# Create aliases if they don't exist
if not hasattr(self, "get_cash"):
self.get_cash = self.getcash
if not hasattr(self, "get_value"):
self.get_value = self.getvalue
# broker base class - using new parameter system
[文档]
class BrokerBase(BrokerAliasMixin, ParameterizedBase):
"""Base class for broker implementations.
The broker handles order execution, position tracking, and cash management.
It supports commission schemes, margin requirements, and order history.
Attributes:
commission: Default commission scheme for all assets.
comminfo: Dictionary mapping asset names to commission info objects.
Params:
commission: Default commission scheme (CommInfoBase instance).
"""
# Use new parameter descriptor
commission = ParameterDescriptor(
default=CommInfoBase(percabs=True), doc="Default commission scheme for all assets"
)
# Initialize
[文档]
def __init__(self, **kwargs):
"""Initialize the broker instance.
Args:
**kwargs: Keyword arguments passed to parent class.
"""
super().__init__(**kwargs)
self.comminfo = dict()
self.init()
# This init uses None as key, commission as value
[文档]
def init(self):
"""Initialize the commission info dictionary.
Sets up the default commission scheme if not already present.
Called from both __init__ and start methods.
"""
# called from init and from start
if None not in self.comminfo:
self.comminfo = dict({None: self.get_param("commission")})
# Start
[文档]
def start(self):
"""Start the broker. Re-initializes commission info."""
self.init()
# Stop
[文档]
def stop(self):
"""Stop the broker.
Override this method in subclasses for cleanup operations.
"""
pass
# Add order history
[文档]
def add_order_history(self, orders, notify=False):
"""Add order history to the broker.
Args:
orders: Orders to add to history.
notify: Whether to notify about these orders.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
# Add order history. See cerebro for details
raise NotImplementedError
# Set fund history
[文档]
def set_fund_history(self, fund):
"""Set fund history for the broker.
Args:
fund: Fund history data.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
# Add fund history. See cerebro for details
raise NotImplementedError
# Get commission info, if data._name is in commission info dict, get corresponding value, otherwise use default self.p.commission
[文档]
def getcommissioninfo(self, data):
"""Get the commission info for a given data.
Args:
data: Data feed to get commission info for.
Returns:
CommInfoBase: The commission info for the data, or the default.
"""
# PERFORMANCE OPTIMIZATION: Use getattr instead of hasattr+access
# Called 2.1M+ times, avoid double attribute lookup
comminfo = self.comminfo
name = getattr(data, "name", None)
if name is not None and name in comminfo:
return comminfo[name]
return comminfo[None]
# Set commission
[文档]
def setcommission(
self,
commission=0.0,
margin=None,
mult=1.0,
commtype=None,
percabs=True,
stocklike=False,
interest=0.0,
interest_long=False,
leverage=1.0,
automargin=False,
name=None,
):
"""This method sets a `` CommissionInfo`` object for assets managed in
the broker with the parameters. Consult the reference for
``CommInfoBase``
If name is `None`, this will be the default for assets for which no
other ``CommissionInfo`` scheme can be found
"""
comm = CommInfoBase(
commission=commission,
margin=margin,
mult=mult,
commtype=commtype,
stocklike=stocklike,
percabs=percabs,
interest=interest,
interest_long=interest_long,
leverage=leverage,
automargin=automargin,
)
self.comminfo[name] = comm
# Add commission info
[文档]
def addcommissioninfo(self, comminfo, name=None):
"""Add a CommissionInfo object for an asset.
Args:
comminfo: The CommissionInfo object to add.
name: Asset name. If None, sets as default for all assets.
"""
# Adds a ``CommissionInfo`` object that will be the default for all assets if ``name`` is ``None``
self.comminfo[name] = comminfo
# Get cash
[文档]
def getcash(self):
"""Get the current available cash.
Returns:
float: Current cash amount.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
raise NotImplementedError
# Get value
[文档]
def getvalue(self, datas=None):
"""Get the current portfolio value.
Args:
datas: Data feeds to calculate value for (optional).
Returns:
float: Current portfolio value.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
raise NotImplementedError
# Get fund shares
[文档]
def get_fundshares(self):
"""Get the current number of shares in fund-like mode.
Returns:
float: Number of shares (1.0 for abstract mode).
"""
# Returns the current number of shares in the fund-like mode
return 1.0 # the abstract mode has only 1 share
fundshares = property(get_fundshares)
# Get fund value
[文档]
def get_fundvalue(self):
"""Get the current fund value.
Returns:
float: Current fund value.
"""
return self.getvalue()
fundvalue = property(get_fundvalue)
# Set fund mode
[文档]
def set_fundmode(self, fundmode, fundstartval=None):
"""Set the fund mode for the broker.
Args:
fundmode: True to enable fund mode, False otherwise.
fundstartval: Initial fund value (optional).
Note:
Not all brokers support fund mode.
"""
pass # do nothing, not all brokers can support this
# Get fund mode
[文档]
def get_fundmode(self):
"""Get the current fund mode status.
Returns:
bool: True if fund mode is enabled, False otherwise.
"""
# Returns the actual fundmode (True or False)
return False
fundmode = property(get_fundmode, set_fundmode)
# Get position
[文档]
def getposition(self, data):
"""Get the current position for a data feed.
Args:
data: Data feed to get position for.
Returns:
Position: Current position for the data feed.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
raise NotImplementedError
# Submit
[文档]
def submit(self, order):
"""Submit an order to the broker.
Args:
order: Order object to submit.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
raise NotImplementedError
# Cancel
[文档]
def cancel(self, order):
"""Cancel a pending order.
Args:
order: Order object to cancel.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
raise NotImplementedError
# Buy order
[文档]
def buy(
self,
owner,
data,
size,
price=None,
plimit=None,
exectype=None,
valid=None,
tradeid=0,
oco=None,
trailamount=None,
trailpercent=None,
**kwargs,
):
"""Create a buy order.
Args:
owner: Strategy/owner creating the order.
data: Data feed for the order.
size: Size of the order (positive for buy).
price: Limit price (optional).
plimit: Profit limit price (optional).
exectype: Execution type (Market, Limit, Stop, etc.).
valid: Validity period for the order.
tradeid: Trade identifier.
oco: One-cancels-other order reference.
trailamount: Trailing amount for stop orders.
trailpercent: Trailing percent for stop orders.
**kwargs: Additional keyword arguments.
Returns:
Order: The created order object.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
raise NotImplementedError
# Sell order
[文档]
def sell(
self,
owner,
data,
size,
price=None,
plimit=None,
exectype=None,
valid=None,
tradeid=0,
oco=None,
trailamount=None,
trailpercent=None,
**kwargs,
):
"""Create a sell order.
Args:
owner: Strategy/owner creating the order.
data: Data feed for the order.
size: Size of the order (positive for sell).
price: Limit price (optional).
plimit: Profit limit price (optional).
exectype: Execution type (Market, Limit, Stop, etc.).
valid: Validity period for the order.
tradeid: Trade identifier.
oco: One-cancels-other order reference.
trailamount: Trailing amount for stop orders.
trailpercent: Trailing percent for stop orders.
**kwargs: Additional keyword arguments.
Returns:
Order: The created order object.
Raises:
NotImplementedError: Must be implemented by subclasses.
"""
raise NotImplementedError
# Next bar
[文档]
def next(self):
"""Process the next bar in the backtest.
Called by the cerebro engine for each iteration.
Override in subclasses to perform per-bar operations.
"""
pass
# __all__ = ['BrokerBase', 'fillers', 'filler']