Module backtrader.brokers.vcbroker

Expand source code
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
#
# Copyright (C) 2015-2023 Daniel Rodriguez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import collections
from datetime import date, datetime, timedelta
import threading

from backtrader import BrokerBase, Order, BuyOrder, SellOrder
from backtrader.comminfo import CommInfoBase
from backtrader.feed import DataBase
from backtrader.metabase import MetaParams
from backtrader.position import Position
from backtrader.utils.py3 import with_metaclass

from backtrader.stores import vcstore


class VCCommInfo(CommInfoBase):
    '''
    Commissions are calculated by ib, but the trades calculations in the
    ```Strategy`` rely on the order carrying a CommInfo object attached for the
    calculation of the operation cost and value.

    These are non-critical informations, but removing them from the trade could
    break existing usage and it is better to provide a CommInfo objet which
    enables those calculations even if with approvimate values.

    The margin calculation is not a known in advance information with IB
    (margin impact can be gotten from OrderState objects) and therefore it is
    left as future exercise to get it'''

    def getvaluesize(self, size, price):
        # In real life the margin approaches the price
        return abs(size) * price

    def getoperationcost(self, size, price):
        '''Returns the needed amount of cash an operation would cost'''
        # Same reasoning as above
        return abs(size) * price


class MetaVCBroker(BrokerBase.__class__):
    def __init__(cls, name, bases, dct):
        '''Class has already been created ... register'''
        # Initialize the class
        super(MetaVCBroker, cls).__init__(name, bases, dct)
        vcstore.VCStore.BrokerCls = cls


class VCBroker(with_metaclass(MetaVCBroker, BrokerBase)):
    '''Broker implementation for VisualChart.

    This class maps the orders/positions from VisualChart to the
    internal API of ``backtrader``.

    Params:

      - ``account`` (default: None)

        VisualChart supports several accounts simultaneously on the broker. If
        the default ``None`` is in place the 1st account in the ComTrader
        ``Accounts`` collection will be used.

        If an account name is provided, the ``Accounts`` collection will be
        checked and used if present

      - ``commission`` (default: None)

        An object will be autogenerated if no commission-scheme is passed as
        parameter

        See the notes below for further explanations

    Notes:

      - Position

        VisualChart reports "OpenPositions" updates through the ComTrader
        interface but only when the position has a "size". An update to
        indicate a position has moved to ZERO is reported by the absence of
        such position. This forces to keep accounting of the positions by
        looking at the execution events, just like the simulation broker does

      - Commission

        The ComTrader interface of VisualChart does not report commissions and
        as such the auto-generated CommissionInfo object cannot use
        non-existent commissions to properly account for them. In order to
        support commissions a ``commission`` parameter has to be passed with
        the appropriate commission schemes.

        The documentation on Commission Schemes details how to do this

      - Expiration Timing

        The ComTrader interface (or is it the comtypes module?) discards
        ``time`` information from ``datetime`` objects and expiration dates are
        always full dates.

      - Expiration Reporting

        At the moment no heuristic is in place to determine when a cancelled
        order has been cancelled due to expiration. And therefore expired
        orders are reported as cancelled.
    '''
    params = (
        ('account', None),
        ('commission', None),
    )

    def __init__(self, **kwargs):
        super(VCBroker, self).__init__()

        self.store = vcstore.VCStore(**kwargs)

        # Account data
        self._acc_name = None
        self.startingcash = self.cash = 0.0
        self.startingvalue = self.value = 0.0

        # Position accounting
        self._lock_pos = threading.Lock()  # sync account updates
        self.positions = collections.defaultdict(Position)  # actual positions

        # Order storage
        self._lock_orders = threading.Lock()  # control access
        self.orderbyid = dict()  # orders by order id

        # Notifications
        self.notifs = collections.deque()

        # Dictionaries of values for order mapping
        self._otypes = {
            Order.Market: self.store.vcctmod.OT_Market,
            Order.Close: self.store.vcctmod.OT_Market,
            Order.Limit: self.store.vcctmod.OT_Limit,
            Order.Stop: self.store.vcctmod.OT_StopMarket,
            Order.StopLimit: self.store.vcctmod.OT_StopLimit,
        }

        self._osides = {
            Order.Buy: self.store.vcctmod.OS_Buy,
            Order.Sell: self.store.vcctmod.OS_Sell,
        }

        self._otrestriction = {
            Order.T_None: self.store.vcctmod.TR_NoRestriction,
            Order.T_Date: self.store.vcctmod.TR_Date,
            Order.T_Close: self.store.vcctmod.TR_CloseAuction,
            Order.T_Day: self.store.vcctmod.TR_Session,
        }

        self._ovrestriction = {
            Order.V_None: self.store.vcctmod.VR_NoRestriction,
        }

        self._futlikes = (
            self.store.vcdsmod.IT_Future, self.store.vcdsmod.IT_Option,
            self.store.vcdsmod.IT_Fund,
        )

    def start(self):
        super(VCBroker, self).start()
        self.store.start(broker=self)

    def stop(self):
        super(VCBroker, self).stop()
        self.store.stop()

    def getcash(self):
        # This call cannot block if no answer is available from ib
        return self.cash

    def getvalue(self, datas=None):
        return self.value

    def get_notification(self):
        return self.notifs.popleft()  # at leat a None is present

    def notify(self, order):
        self.notifs.append(order.clone())

    def next(self):
        self.notifs.append(None)  # mark notificatino boundary

    def getposition(self, data, clone=True):
        with self._lock_pos:
            pos = self.positions[data._tradename]
            if clone:
                return pos.clone()

        return pos

    def getcommissioninfo(self, data):
        if data._tradename in self.comminfo:
            return self.comminfo[data._tradename]

        comminfo = self.comminfo[None]
        if comminfo is not None:
            return comminfo

        stocklike = data._syminfo.Type in self._futlikes

        return VCCommInfo(mult=data._syminfo.PointValue, stocklike=stocklike)

    def _makeorder(self, ordtype, owner, data,
                   size, price=None, plimit=None,
                   exectype=None, valid=None,
                   tradeid=0, **kwargs):

        order = self.store.vcctmod.Order()
        order.Account = self._acc_name
        order.SymbolCode = data._tradename
        order.OrderType = self._otypes[exectype]
        order.OrderSide = self._osides[ordtype]

        order.VolumeRestriction = self._ovrestriction[Order.V_None]
        order.HideVolume = 0
        order.MinVolume = 0

        # order.UserName = 'danjrod'  # str(tradeid)
        # order.OrderId = 'a' * 50  # str(tradeid)
        order.UserOrderId = ''
        if tradeid:
            order.ExtendedInfo = 'TradeId {}'.format(tradeid)
        else:
            order.ExtendedInfo = ''

        order.Volume = abs(size)

        order.StopPrice = 0.0
        order.Price = 0.0
        if exectype == Order.Market:
            pass
        elif exectype == Order.Limit:
            order.Price = price or plimit  # cover naming confusion cases
        elif exectype == Order.Close:
            pass
        elif exectype == Order.Stop:
            order.StopPrice = price
        elif exectype == Order.StopLimit:
            order.StopPrice = price
            order.Price = plimit

        order.ValidDate = None
        if exectype == Order.Close:
            order.TimeRestriction = self._otrestriction[Order.T_Close]
        else:
            if valid is None:
                order.TimeRestriction = self._otrestriction[Order.T_None]
            elif isinstance(valid, (datetime, date)):
                order.TimeRestriction = self._otrestriction[Order.T_Date]
                order.ValidDate = valid
            elif isinstance(valid, (timedelta,)):
                if valid == Order.DAY:
                    order.TimeRestriction = self._otrestriction[Order.T_Day]
                else:
                    order.TimeRestriction = self._otrestriction[Order.T_Date]
                    order.ValidDate = datetime.now() + valid

            elif not self.valid:  # DAY
                order.TimeRestriction = self._otrestriction[Order.T_Day]

        # Support for custom user arguments
        for k in kwargs:
            if hasattr(order, k):
                setattr(order, k, kwargs[k])

        return order

    def submit(self, order, vcorder):
        order.submit(self)

        vco = vcorder
        oid = self.store.vcct.SendOrder(
            vco.Account, vco.SymbolCode,
            vco.OrderType, vco.OrderSide, vco.Volume, vco.Price, vco.StopPrice,
            vco.VolumeRestriction, vco.TimeRestriction,
            ValidDate=vco.ValidDate
        )

        order.vcorder = oid
        order.addcomminfo(self.getcommissioninfo(order.data))

        with self._lock_orders:
            self.orderbyid[oid] = order
        self.notify(order)
        return order

    def buy(self, owner, data,
            size, price=None, plimit=None,
            exectype=None, valid=None, tradeid=0,
            **kwargs):

        order = BuyOrder(owner=owner, data=data,
                         size=size, price=price, pricelimit=plimit,
                         exectype=exectype, valid=valid, tradeid=tradeid)

        order.addinfo(**kwargs)

        vcorder = self._makeorder(order.ordtype, owner, data, size, price,
                                  plimit, exectype, valid, tradeid,
                                  **kwargs)

        return self.submit(order, vcorder)

    def sell(self, owner, data,
             size, price=None, plimit=None,
             exectype=None, valid=None, tradeid=0,
             **kwargs):

        order = SellOrder(owner=owner, data=data,
                          size=size, price=price, pricelimit=plimit,
                          exectype=exectype, valid=valid, tradeid=tradeid)

        order.addinfo(**kwargs)

        vcorder = self._makeorder(order.ordtype, owner, data, size, price,
                                  plimit, exectype, valid, tradeid,
                                  **kwargs)

        return self.submit(order, vcorder)

    #
    # COM Events implementation
    #
    def __call__(self, trader):
        # Called to start the process, call in sub-thread. only the passed
        # trader can be used in the thread
        self.trader = trader

        for acc in trader.Accounts:
            if self.p.account is None or self.p.account == acc.Account:
                self.startingcash = self.cash = acc.Balance.Cash
                self.startingvalue = self.value = acc.Balance.NetWorth
                self._acc_name = acc.Account
                break  # found the account

        return self

    def OnChangedBalance(self, Account):
        if self._acc_name is None or self._acc_name != Account:
            return  # skip notifs for other accounts

        for acc in self.trader.Accounts:
            if acc.Account == Account:
                # Update store values
                self.cash = acc.Balance.Cash
                self.value = acc.Balance.NetWorth
                break

    def OnModifiedOrder(self, Order):
        # We are not expecting this: unless backtrader starts implementing
        # modify order method
        pass

    def OnCancelledOrder(self, Order):
        with self._lock_orders:
            try:
                border = self.orderbyid[Order.OrderId]
            except KeyError:
                return  # possibly external order

        border.cancel()
        self.notify(border)

    def OnTotalExecutedOrder(self, Order):
        self.OnExecutedOrder(Order, partial=False)

    def OnPartialExecutedOrder(self, Order):
        self.OnExecutedOrder(Order, partial=True)

    def OnExecutedOrder(self, Order, partial):
        with self._lock_orders:
            try:
                border = self.orderbyid[Order.OrderId]
            except KeyError:
                return  # possibly external order

        price = Order.Price
        size = Order.Volume
        if border.issell():
            size *= -1

        # Find position and do a real update - accounting happens here
        position = self.getposition(border.data, clone=False)
        pprice_orig = position.price
        psize, pprice, opened, closed = position.update(size, price)

        comminfo = border.comminfo
        closedvalue = comminfo.getoperationcost(closed, pprice_orig)
        closedcomm = comminfo.getcommission(closed, price)

        openedvalue = comminfo.getoperationcost(opened, price)
        openedcomm = comminfo.getcommission(opened, price)

        pnl = comminfo.profitandloss(-closed, pprice_orig, price)
        margin = comminfo.getvaluesize(size, price)

        # NOTE: No commission information available in the Trader interface
        # CHECK: Use reported time instead of last data time?
        border.execute(border.data.datetime[0],
                       size, price,
                       closed, closedvalue, closedcomm,
                       opened, openedvalue, openedcomm,
                       margin, pnl,
                       psize, pprice)  # pnl

        if partial:
            border.partial()
        else:
            border.completed()

        self.notify(border)

    def OnOrderInMarket(self, Order):
        # Other is in ther market ... therefore "accepted"
        with self._lock_orders:
            try:
                border = self.orderbyid[Order.OrderId]
            except KeyError:
                return  # possibly external order

        border.accept()
        self.notify(border)

    def OnNewOrderLocation(self, Order):
        # Can be used for "submitted", but the status is set manually
        pass

    def OnChangedOpenPositions(self, Account):
        # This would be useful if it reported a position moving back to 0. In
        # this case the report contains a no-position and this doesn't help in
        # the accounting. That's why the accounting is delegated to the
        # reception of order execution
        pass

    def OnNewClosedOperations(self, Account):
        # This call-back has not been seen
        pass

    def OnServerShutDown(self):
        pass

    def OnInternalEvent(self, p1, p2, p3):
        pass

Classes

class MetaVCBroker (name, bases, dct)

type(object) -> the object's type type(name, bases, dict, **kwds) -> a new type

Class has already been created … register

Expand source code
class MetaVCBroker(BrokerBase.__class__):
    def __init__(cls, name, bases, dct):
        '''Class has already been created ... register'''
        # Initialize the class
        super(MetaVCBroker, cls).__init__(name, bases, dct)
        vcstore.VCStore.BrokerCls = cls

Ancestors

class VCBroker (**kwargs)

Broker implementation for VisualChart.

This class maps the orders/positions from VisualChart to the internal API of backtrader.

Params

  • account (default: None)

VisualChart supports several accounts simultaneously on the broker. If the default None is in place the 1st account in the ComTrader Accounts collection will be used.

If an account name is provided, the Accounts collection will be checked and used if present

  • commission (default: None)

An object will be autogenerated if no commission-scheme is passed as parameter

See the notes below for further explanations

Notes

  • Position

VisualChart reports "OpenPositions" updates through the ComTrader interface but only when the position has a "size". An update to indicate a position has moved to ZERO is reported by the absence of such position. This forces to keep accounting of the positions by looking at the execution events, just like the simulation broker does

  • Commission

The ComTrader interface of VisualChart does not report commissions and as such the auto-generated CommissionInfo object cannot use non-existent commissions to properly account for them. In order to support commissions a commission parameter has to be passed with the appropriate commission schemes.

The documentation on Commission Schemes details how to do this

  • Expiration Timing

The ComTrader interface (or is it the comtypes module?) discards time information from datetime objects and expiration dates are always full dates.

  • Expiration Reporting

At the moment no heuristic is in place to determine when a cancelled order has been cancelled due to expiration. And therefore expired orders are reported as cancelled.

Expand source code
class VCBroker(with_metaclass(MetaVCBroker, BrokerBase)):
    '''Broker implementation for VisualChart.

    This class maps the orders/positions from VisualChart to the
    internal API of ``backtrader``.

    Params:

      - ``account`` (default: None)

        VisualChart supports several accounts simultaneously on the broker. If
        the default ``None`` is in place the 1st account in the ComTrader
        ``Accounts`` collection will be used.

        If an account name is provided, the ``Accounts`` collection will be
        checked and used if present

      - ``commission`` (default: None)

        An object will be autogenerated if no commission-scheme is passed as
        parameter

        See the notes below for further explanations

    Notes:

      - Position

        VisualChart reports "OpenPositions" updates through the ComTrader
        interface but only when the position has a "size". An update to
        indicate a position has moved to ZERO is reported by the absence of
        such position. This forces to keep accounting of the positions by
        looking at the execution events, just like the simulation broker does

      - Commission

        The ComTrader interface of VisualChart does not report commissions and
        as such the auto-generated CommissionInfo object cannot use
        non-existent commissions to properly account for them. In order to
        support commissions a ``commission`` parameter has to be passed with
        the appropriate commission schemes.

        The documentation on Commission Schemes details how to do this

      - Expiration Timing

        The ComTrader interface (or is it the comtypes module?) discards
        ``time`` information from ``datetime`` objects and expiration dates are
        always full dates.

      - Expiration Reporting

        At the moment no heuristic is in place to determine when a cancelled
        order has been cancelled due to expiration. And therefore expired
        orders are reported as cancelled.
    '''
    params = (
        ('account', None),
        ('commission', None),
    )

    def __init__(self, **kwargs):
        super(VCBroker, self).__init__()

        self.store = vcstore.VCStore(**kwargs)

        # Account data
        self._acc_name = None
        self.startingcash = self.cash = 0.0
        self.startingvalue = self.value = 0.0

        # Position accounting
        self._lock_pos = threading.Lock()  # sync account updates
        self.positions = collections.defaultdict(Position)  # actual positions

        # Order storage
        self._lock_orders = threading.Lock()  # control access
        self.orderbyid = dict()  # orders by order id

        # Notifications
        self.notifs = collections.deque()

        # Dictionaries of values for order mapping
        self._otypes = {
            Order.Market: self.store.vcctmod.OT_Market,
            Order.Close: self.store.vcctmod.OT_Market,
            Order.Limit: self.store.vcctmod.OT_Limit,
            Order.Stop: self.store.vcctmod.OT_StopMarket,
            Order.StopLimit: self.store.vcctmod.OT_StopLimit,
        }

        self._osides = {
            Order.Buy: self.store.vcctmod.OS_Buy,
            Order.Sell: self.store.vcctmod.OS_Sell,
        }

        self._otrestriction = {
            Order.T_None: self.store.vcctmod.TR_NoRestriction,
            Order.T_Date: self.store.vcctmod.TR_Date,
            Order.T_Close: self.store.vcctmod.TR_CloseAuction,
            Order.T_Day: self.store.vcctmod.TR_Session,
        }

        self._ovrestriction = {
            Order.V_None: self.store.vcctmod.VR_NoRestriction,
        }

        self._futlikes = (
            self.store.vcdsmod.IT_Future, self.store.vcdsmod.IT_Option,
            self.store.vcdsmod.IT_Fund,
        )

    def start(self):
        super(VCBroker, self).start()
        self.store.start(broker=self)

    def stop(self):
        super(VCBroker, self).stop()
        self.store.stop()

    def getcash(self):
        # This call cannot block if no answer is available from ib
        return self.cash

    def getvalue(self, datas=None):
        return self.value

    def get_notification(self):
        return self.notifs.popleft()  # at leat a None is present

    def notify(self, order):
        self.notifs.append(order.clone())

    def next(self):
        self.notifs.append(None)  # mark notificatino boundary

    def getposition(self, data, clone=True):
        with self._lock_pos:
            pos = self.positions[data._tradename]
            if clone:
                return pos.clone()

        return pos

    def getcommissioninfo(self, data):
        if data._tradename in self.comminfo:
            return self.comminfo[data._tradename]

        comminfo = self.comminfo[None]
        if comminfo is not None:
            return comminfo

        stocklike = data._syminfo.Type in self._futlikes

        return VCCommInfo(mult=data._syminfo.PointValue, stocklike=stocklike)

    def _makeorder(self, ordtype, owner, data,
                   size, price=None, plimit=None,
                   exectype=None, valid=None,
                   tradeid=0, **kwargs):

        order = self.store.vcctmod.Order()
        order.Account = self._acc_name
        order.SymbolCode = data._tradename
        order.OrderType = self._otypes[exectype]
        order.OrderSide = self._osides[ordtype]

        order.VolumeRestriction = self._ovrestriction[Order.V_None]
        order.HideVolume = 0
        order.MinVolume = 0

        # order.UserName = 'danjrod'  # str(tradeid)
        # order.OrderId = 'a' * 50  # str(tradeid)
        order.UserOrderId = ''
        if tradeid:
            order.ExtendedInfo = 'TradeId {}'.format(tradeid)
        else:
            order.ExtendedInfo = ''

        order.Volume = abs(size)

        order.StopPrice = 0.0
        order.Price = 0.0
        if exectype == Order.Market:
            pass
        elif exectype == Order.Limit:
            order.Price = price or plimit  # cover naming confusion cases
        elif exectype == Order.Close:
            pass
        elif exectype == Order.Stop:
            order.StopPrice = price
        elif exectype == Order.StopLimit:
            order.StopPrice = price
            order.Price = plimit

        order.ValidDate = None
        if exectype == Order.Close:
            order.TimeRestriction = self._otrestriction[Order.T_Close]
        else:
            if valid is None:
                order.TimeRestriction = self._otrestriction[Order.T_None]
            elif isinstance(valid, (datetime, date)):
                order.TimeRestriction = self._otrestriction[Order.T_Date]
                order.ValidDate = valid
            elif isinstance(valid, (timedelta,)):
                if valid == Order.DAY:
                    order.TimeRestriction = self._otrestriction[Order.T_Day]
                else:
                    order.TimeRestriction = self._otrestriction[Order.T_Date]
                    order.ValidDate = datetime.now() + valid

            elif not self.valid:  # DAY
                order.TimeRestriction = self._otrestriction[Order.T_Day]

        # Support for custom user arguments
        for k in kwargs:
            if hasattr(order, k):
                setattr(order, k, kwargs[k])

        return order

    def submit(self, order, vcorder):
        order.submit(self)

        vco = vcorder
        oid = self.store.vcct.SendOrder(
            vco.Account, vco.SymbolCode,
            vco.OrderType, vco.OrderSide, vco.Volume, vco.Price, vco.StopPrice,
            vco.VolumeRestriction, vco.TimeRestriction,
            ValidDate=vco.ValidDate
        )

        order.vcorder = oid
        order.addcomminfo(self.getcommissioninfo(order.data))

        with self._lock_orders:
            self.orderbyid[oid] = order
        self.notify(order)
        return order

    def buy(self, owner, data,
            size, price=None, plimit=None,
            exectype=None, valid=None, tradeid=0,
            **kwargs):

        order = BuyOrder(owner=owner, data=data,
                         size=size, price=price, pricelimit=plimit,
                         exectype=exectype, valid=valid, tradeid=tradeid)

        order.addinfo(**kwargs)

        vcorder = self._makeorder(order.ordtype, owner, data, size, price,
                                  plimit, exectype, valid, tradeid,
                                  **kwargs)

        return self.submit(order, vcorder)

    def sell(self, owner, data,
             size, price=None, plimit=None,
             exectype=None, valid=None, tradeid=0,
             **kwargs):

        order = SellOrder(owner=owner, data=data,
                          size=size, price=price, pricelimit=plimit,
                          exectype=exectype, valid=valid, tradeid=tradeid)

        order.addinfo(**kwargs)

        vcorder = self._makeorder(order.ordtype, owner, data, size, price,
                                  plimit, exectype, valid, tradeid,
                                  **kwargs)

        return self.submit(order, vcorder)

    #
    # COM Events implementation
    #
    def __call__(self, trader):
        # Called to start the process, call in sub-thread. only the passed
        # trader can be used in the thread
        self.trader = trader

        for acc in trader.Accounts:
            if self.p.account is None or self.p.account == acc.Account:
                self.startingcash = self.cash = acc.Balance.Cash
                self.startingvalue = self.value = acc.Balance.NetWorth
                self._acc_name = acc.Account
                break  # found the account

        return self

    def OnChangedBalance(self, Account):
        if self._acc_name is None or self._acc_name != Account:
            return  # skip notifs for other accounts

        for acc in self.trader.Accounts:
            if acc.Account == Account:
                # Update store values
                self.cash = acc.Balance.Cash
                self.value = acc.Balance.NetWorth
                break

    def OnModifiedOrder(self, Order):
        # We are not expecting this: unless backtrader starts implementing
        # modify order method
        pass

    def OnCancelledOrder(self, Order):
        with self._lock_orders:
            try:
                border = self.orderbyid[Order.OrderId]
            except KeyError:
                return  # possibly external order

        border.cancel()
        self.notify(border)

    def OnTotalExecutedOrder(self, Order):
        self.OnExecutedOrder(Order, partial=False)

    def OnPartialExecutedOrder(self, Order):
        self.OnExecutedOrder(Order, partial=True)

    def OnExecutedOrder(self, Order, partial):
        with self._lock_orders:
            try:
                border = self.orderbyid[Order.OrderId]
            except KeyError:
                return  # possibly external order

        price = Order.Price
        size = Order.Volume
        if border.issell():
            size *= -1

        # Find position and do a real update - accounting happens here
        position = self.getposition(border.data, clone=False)
        pprice_orig = position.price
        psize, pprice, opened, closed = position.update(size, price)

        comminfo = border.comminfo
        closedvalue = comminfo.getoperationcost(closed, pprice_orig)
        closedcomm = comminfo.getcommission(closed, price)

        openedvalue = comminfo.getoperationcost(opened, price)
        openedcomm = comminfo.getcommission(opened, price)

        pnl = comminfo.profitandloss(-closed, pprice_orig, price)
        margin = comminfo.getvaluesize(size, price)

        # NOTE: No commission information available in the Trader interface
        # CHECK: Use reported time instead of last data time?
        border.execute(border.data.datetime[0],
                       size, price,
                       closed, closedvalue, closedcomm,
                       opened, openedvalue, openedcomm,
                       margin, pnl,
                       psize, pprice)  # pnl

        if partial:
            border.partial()
        else:
            border.completed()

        self.notify(border)

    def OnOrderInMarket(self, Order):
        # Other is in ther market ... therefore "accepted"
        with self._lock_orders:
            try:
                border = self.orderbyid[Order.OrderId]
            except KeyError:
                return  # possibly external order

        border.accept()
        self.notify(border)

    def OnNewOrderLocation(self, Order):
        # Can be used for "submitted", but the status is set manually
        pass

    def OnChangedOpenPositions(self, Account):
        # This would be useful if it reported a position moving back to 0. In
        # this case the report contains a no-position and this doesn't help in
        # the accounting. That's why the accounting is delegated to the
        # reception of order execution
        pass

    def OnNewClosedOperations(self, Account):
        # This call-back has not been seen
        pass

    def OnServerShutDown(self):
        pass

    def OnInternalEvent(self, p1, p2, p3):
        pass

Ancestors

Class variables

var frompackages
var packages
var params

Methods

def OnCancelledOrder(self, Order)
Expand source code
def OnCancelledOrder(self, Order):
    with self._lock_orders:
        try:
            border = self.orderbyid[Order.OrderId]
        except KeyError:
            return  # possibly external order

    border.cancel()
    self.notify(border)
def OnChangedBalance(self, Account)
Expand source code
def OnChangedBalance(self, Account):
    if self._acc_name is None or self._acc_name != Account:
        return  # skip notifs for other accounts

    for acc in self.trader.Accounts:
        if acc.Account == Account:
            # Update store values
            self.cash = acc.Balance.Cash
            self.value = acc.Balance.NetWorth
            break
def OnChangedOpenPositions(self, Account)
Expand source code
def OnChangedOpenPositions(self, Account):
    # This would be useful if it reported a position moving back to 0. In
    # this case the report contains a no-position and this doesn't help in
    # the accounting. That's why the accounting is delegated to the
    # reception of order execution
    pass
def OnExecutedOrder(self, Order, partial)
Expand source code
def OnExecutedOrder(self, Order, partial):
    with self._lock_orders:
        try:
            border = self.orderbyid[Order.OrderId]
        except KeyError:
            return  # possibly external order

    price = Order.Price
    size = Order.Volume
    if border.issell():
        size *= -1

    # Find position and do a real update - accounting happens here
    position = self.getposition(border.data, clone=False)
    pprice_orig = position.price
    psize, pprice, opened, closed = position.update(size, price)

    comminfo = border.comminfo
    closedvalue = comminfo.getoperationcost(closed, pprice_orig)
    closedcomm = comminfo.getcommission(closed, price)

    openedvalue = comminfo.getoperationcost(opened, price)
    openedcomm = comminfo.getcommission(opened, price)

    pnl = comminfo.profitandloss(-closed, pprice_orig, price)
    margin = comminfo.getvaluesize(size, price)

    # NOTE: No commission information available in the Trader interface
    # CHECK: Use reported time instead of last data time?
    border.execute(border.data.datetime[0],
                   size, price,
                   closed, closedvalue, closedcomm,
                   opened, openedvalue, openedcomm,
                   margin, pnl,
                   psize, pprice)  # pnl

    if partial:
        border.partial()
    else:
        border.completed()

    self.notify(border)
def OnInternalEvent(self, p1, p2, p3)
Expand source code
def OnInternalEvent(self, p1, p2, p3):
    pass
def OnModifiedOrder(self, Order)
Expand source code
def OnModifiedOrder(self, Order):
    # We are not expecting this: unless backtrader starts implementing
    # modify order method
    pass
def OnNewClosedOperations(self, Account)
Expand source code
def OnNewClosedOperations(self, Account):
    # This call-back has not been seen
    pass
def OnNewOrderLocation(self, Order)
Expand source code
def OnNewOrderLocation(self, Order):
    # Can be used for "submitted", but the status is set manually
    pass
def OnOrderInMarket(self, Order)
Expand source code
def OnOrderInMarket(self, Order):
    # Other is in ther market ... therefore "accepted"
    with self._lock_orders:
        try:
            border = self.orderbyid[Order.OrderId]
        except KeyError:
            return  # possibly external order

    border.accept()
    self.notify(border)
def OnPartialExecutedOrder(self, Order)
Expand source code
def OnPartialExecutedOrder(self, Order):
    self.OnExecutedOrder(Order, partial=True)
def OnServerShutDown(self)
Expand source code
def OnServerShutDown(self):
    pass
def OnTotalExecutedOrder(self, Order)
Expand source code
def OnTotalExecutedOrder(self, Order):
    self.OnExecutedOrder(Order, partial=False)
def VCBroker(self, datas=None)
Expand source code
def getvalue(self, datas=None):
    return self.value
def buy(self, owner, data, size, price=None, plimit=None, exectype=None, valid=None, tradeid=0, **kwargs)
Expand source code
def buy(self, owner, data,
        size, price=None, plimit=None,
        exectype=None, valid=None, tradeid=0,
        **kwargs):

    order = BuyOrder(owner=owner, data=data,
                     size=size, price=price, pricelimit=plimit,
                     exectype=exectype, valid=valid, tradeid=tradeid)

    order.addinfo(**kwargs)

    vcorder = self._makeorder(order.ordtype, owner, data, size, price,
                              plimit, exectype, valid, tradeid,
                              **kwargs)

    return self.submit(order, vcorder)
def get_notification(self)
Expand source code
def get_notification(self):
    return self.notifs.popleft()  # at leat a None is present
def getcash(self)
Expand source code
def getcash(self):
    # This call cannot block if no answer is available from ib
    return self.cash
def getposition(self, data, clone=True)
Expand source code
def getposition(self, data, clone=True):
    with self._lock_pos:
        pos = self.positions[data._tradename]
        if clone:
            return pos.clone()

    return pos
def getvalue(self, datas=None)
Expand source code
def getvalue(self, datas=None):
    return self.value
def next(self)
Expand source code
def next(self):
    self.notifs.append(None)  # mark notificatino boundary
def notify(self, order)
Expand source code
def notify(self, order):
    self.notifs.append(order.clone())
def sell(self, owner, data, size, price=None, plimit=None, exectype=None, valid=None, tradeid=0, **kwargs)
Expand source code
def sell(self, owner, data,
         size, price=None, plimit=None,
         exectype=None, valid=None, tradeid=0,
         **kwargs):

    order = SellOrder(owner=owner, data=data,
                      size=size, price=price, pricelimit=plimit,
                      exectype=exectype, valid=valid, tradeid=tradeid)

    order.addinfo(**kwargs)

    vcorder = self._makeorder(order.ordtype, owner, data, size, price,
                              plimit, exectype, valid, tradeid,
                              **kwargs)

    return self.submit(order, vcorder)
def start(self)
Expand source code
def start(self):
    super(VCBroker, self).start()
    self.store.start(broker=self)
def stop(self)
Expand source code
def stop(self):
    super(VCBroker, self).stop()
    self.store.stop()
def submit(self, order, vcorder)
Expand source code
def submit(self, order, vcorder):
    order.submit(self)

    vco = vcorder
    oid = self.store.vcct.SendOrder(
        vco.Account, vco.SymbolCode,
        vco.OrderType, vco.OrderSide, vco.Volume, vco.Price, vco.StopPrice,
        vco.VolumeRestriction, vco.TimeRestriction,
        ValidDate=vco.ValidDate
    )

    order.vcorder = oid
    order.addcomminfo(self.getcommissioninfo(order.data))

    with self._lock_orders:
        self.orderbyid[oid] = order
    self.notify(order)
    return order

Inherited members

class VCCommInfo

Commissions are calculated by ib, but the trades calculations in the `Strategy rely on the order carrying a CommInfo object attached for the calculation of the operation cost and value.

These are non-critical informations, but removing them from the trade could break existing usage and it is better to provide a CommInfo objet which enables those calculations even if with approvimate values.

The margin calculation is not a known in advance information with IB (margin impact can be gotten from OrderState objects) and therefore it is left as future exercise to get it

Expand source code
class VCCommInfo(CommInfoBase):
    '''
    Commissions are calculated by ib, but the trades calculations in the
    ```Strategy`` rely on the order carrying a CommInfo object attached for the
    calculation of the operation cost and value.

    These are non-critical informations, but removing them from the trade could
    break existing usage and it is better to provide a CommInfo objet which
    enables those calculations even if with approvimate values.

    The margin calculation is not a known in advance information with IB
    (margin impact can be gotten from OrderState objects) and therefore it is
    left as future exercise to get it'''

    def getvaluesize(self, size, price):
        # In real life the margin approaches the price
        return abs(size) * price

    def getoperationcost(self, size, price):
        '''Returns the needed amount of cash an operation would cost'''
        # Same reasoning as above
        return abs(size) * price

Ancestors

Class variables

var frompackages
var packages
var params

Inherited members