Skip to content

Commit cdf1ccf

Browse files
author
yunjinqi
committed
fix: apply live asset specs to broker valuation
1 parent ac182c0 commit cdf1ccf

11 files changed

Lines changed: 7713 additions & 491 deletions

File tree

backtrader/broker.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,31 @@ def set_fund_history(self, fund):
132132
# Add fund history. See cerebro for details
133133
raise NotImplementedError
134134

135-
# Get commission info, if data._name is in commission info dict, get corresponding value, otherwise use default self.p.commission
135+
@staticmethod
136+
def _commission_lookup_keys(data):
137+
"""Return stable symbol/name candidates for commission lookup."""
138+
keys = []
139+
seen = set()
140+
for attr in ("name", "_name", "_dataname", "symbol"):
141+
value = getattr(data, attr, None)
142+
if value in (None, ""):
143+
continue
144+
text = str(value)
145+
for key in (value, text, text.upper(), text.lower()):
146+
try:
147+
marker = ("hashable", key)
148+
if marker in seen:
149+
continue
150+
seen.add(marker)
151+
except TypeError:
152+
marker = ("repr", repr(key))
153+
if marker in seen:
154+
continue
155+
seen.add(marker)
156+
keys.append(key)
157+
return keys
158+
159+
# Get commission info, if data name is in commission info dict, get corresponding value, otherwise use default self.p.commission
136160
def getcommissioninfo(self, data):
137161
"""Get the commission info for a given data.
138162
@@ -145,9 +169,9 @@ def getcommissioninfo(self, data):
145169
# PERFORMANCE OPTIMIZATION: Use getattr instead of hasattr+access
146170
# Called 2.1M+ times, avoid double attribute lookup
147171
comminfo = self.comminfo
148-
name = getattr(data, "name", None)
149-
if name is not None and name in comminfo:
150-
return comminfo[name]
172+
for name in self._commission_lookup_keys(data):
173+
if name in comminfo:
174+
return comminfo[name]
151175

152176
return comminfo[None]
153177

@@ -157,6 +181,10 @@ def setcommission(
157181
commission=0.0,
158182
maker_commission=None,
159183
taker_commission=None,
184+
open_commission=None,
185+
close_commission=None,
186+
close_today_commission=None,
187+
close_yesterday_commission=None,
160188
margin=None,
161189
mult=1.0,
162190
commtype=None,
@@ -180,6 +208,10 @@ def setcommission(
180208
commission=commission,
181209
maker_commission=maker_commission,
182210
taker_commission=taker_commission,
211+
open_commission=open_commission,
212+
close_commission=close_commission,
213+
close_today_commission=close_today_commission,
214+
close_yesterday_commission=close_yesterday_commission,
183215
margin=margin,
184216
mult=mult,
185217
commtype=commtype,

backtrader/brokers/bbroker.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,22 @@ def _attach_position_meta(order, position_side=None, offset=None, **kwargs):
549549
order.addinfo(**kwargs)
550550
return order
551551

552+
@staticmethod
553+
def _close_commission_role(offset):
554+
offset_text = str(offset or "").strip().lower()
555+
if offset_text in {"close_today", "closetoday"}:
556+
return "close_today"
557+
if offset_text in {"close_yesterday", "closeyesterday"}:
558+
return "close_yesterday"
559+
return "close"
560+
561+
@staticmethod
562+
def _getcommission_role(comminfo, size, price, role):
563+
try:
564+
return comminfo.getcommission(size, price, role=role)
565+
except TypeError:
566+
return comminfo.getcommission(size, price)
567+
552568
@staticmethod
553569
def _position_storage_key(data):
554570
return data
@@ -1643,7 +1659,12 @@ def _execute(self, order, ago=None, price=None, cash=None, position=None, dtcoc=
16431659
cash += closecash + pnl * comminfo.stocklike
16441660
# Calculate and subtract commission
16451661
# Commission when closing position
1646-
closedcomm = comminfo.getcommission(closed, price)
1662+
closedcomm = self._getcommission_role(
1663+
comminfo,
1664+
closed,
1665+
price,
1666+
self._close_commission_role(getattr(order.info, "offset", None)),
1667+
)
16471668
# Cash equals cash minus closing commission
16481669
cash -= closedcomm
16491670
# If ago is not None
@@ -1675,7 +1696,7 @@ def _execute(self, order, ago=None, price=None, cash=None, position=None, dtcoc=
16751696
# Subtract cash obtained after opening
16761697
cash -= opencash # original behavior
16771698
# Commission for opening
1678-
openedcomm = cinfocomp.getcommission(opened, price)
1699+
openedcomm = self._getcommission_role(cinfocomp, opened, price, "open")
16791700
# Cash obtained after subtracting opening commission
16801701
cash -= openedcomm
16811702
# If cash is less than 0, opening position is not possible
@@ -1824,7 +1845,12 @@ def _execute_dual_side(self, order, ago=None, price=None, cash=None, position=No
18241845
if closedvalue > 0:
18251846
closecash /= comminfo.get_leverage()
18261847
cash += closecash + pnl * comminfo.stocklike
1827-
closedcomm = comminfo.getcommission(closed, price)
1848+
closedcomm = self._getcommission_role(
1849+
comminfo,
1850+
closed,
1851+
price,
1852+
self._close_commission_role(getattr(order.info, "offset", None)),
1853+
)
18281854
cash -= closedcomm
18291855
if ago is not None:
18301856
cash += comminfo.cashadjust(-closed, signed_position.adjbase, price)
@@ -1843,7 +1869,7 @@ def _execute_dual_side(self, order, ago=None, price=None, cash=None, position=No
18431869
if openedvalue > 0:
18441870
opencash /= comminfo.get_leverage()
18451871
cash -= opencash
1846-
openedcomm = cinfocomp.getcommission(opened, price)
1872+
openedcomm = self._getcommission_role(cinfocomp, opened, price, "open")
18471873
cash -= openedcomm
18481874
if cash < 0.0:
18491875
opened = 0

0 commit comments

Comments
 (0)