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
- MetaBroker
- MetaParams
- MetaBase
- builtins.type
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 ComTraderAccounts
collection will be used.If an account name is provided, the
Accounts
collection will be checked and used if presentcommission
(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 fromdatetime
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