Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8dfdced
Merge dev into development for 1.3.0
cloudQuant Jul 26, 2026
04adc08
Merge dev report rendering fix into development
cloudQuant Jul 26, 2026
dc391b5
删除了contrib下的vortex文件,因和indicators目录下功能完全重复,会导致重复导入覆盖
pp2024 Aug 14, 2026
aea60a5
Merge pull request #19 from pp2024/feature/indicators
cloudQuant Aug 15, 2026
c7ebd16
Merge branch 'dev' into development
cloudQuant Aug 16, 2026
a85aaac
Merge remote-tracking branch 'origin/development' into development
cloudQuant Aug 16, 2026
a04fd5b
cci指标在平盘是会出现除0异常
pp2024 Aug 17, 2026
1cdf2db
obv指标在backtrader\indicators\__init__.py中有说明,但未实现。故提交一版obv指标实现
pp2024 Aug 17, 2026
a4f838c
Merge pull request #20 from pp2024/feature/indicators
cloudQuant Aug 18, 2026
da78c39
在exactbars>0时,lineroot.py:624 与 :610:return bool(value != 0.0),返回 np.…
pp2024 Aug 19, 2026
d8b6da8
Merge pull request #21 from pp2024/feature/indicators
cloudQuant Aug 20, 2026
eab5fc4
LineBuffer.qbuffer 设 maxlen = max(1, self._minperiod)(linebuffer.py:3…
pp2024 Aug 21, 2026
95c7302
Merge pull request #22 from pp2024/feature/indicators
cloudQuant Aug 22, 2026
ef0bc19
merge: synchronize dev into development integration
cloudQuant Aug 22, 2026
aee30f8
fix(indicators): preserve undefined CCI on flat prices
cloudQuant Aug 22, 2026
d4ff36b
Merge pull request #24 from cloudQuant/codex/fix-cci-zero-division
cloudQuant Aug 22, 2026
b52b3f7
Merge pull request #25 from cloudQuant/dev
cloudQuant Aug 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backtrader/indicators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
from .directionalmove import PlusDirectionalIndicator as PlusDirectionalIndicator
from .rsi import RSI as RSI
from .rsi import RelativeStrengthIndex as RelativeStrengthIndex
from .obv import OnBalanceVolume as OnBalanceVolume

OBV = OnBalanceVolume

SimpleMovingAverage = MovingAverageSimple
SMMA = SmoothedMovingAverage
Expand Down Expand Up @@ -115,6 +118,7 @@
from .accdecoscillator import *
from .priceops_ext import *
from .moneyflow import *
from .obv import *
from .demarker import *
from .channels_ext import *
from .trend_ext import *
Expand Down
8 changes: 5 additions & 3 deletions backtrader/indicators/cci.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def next(self):
self.buy()
"""

from . import Indicator, MeanDev, MovAv
from . import DivByZero, Indicator, MeanDev, MovAv


class CommodityChannelIndex(Indicator):
Expand Down Expand Up @@ -83,5 +83,7 @@ def __init__(self):
# This matches master branch's behavior: SMA(|tp - tpmean|) where tpmean varies
meandev = MeanDev(tp, tpmean, period=self.p.period)

# cci = dev / (factor * meandev)
self.lines.cci = dev / (self.p.factor * meandev)
# A zero mean deviation makes CCI mathematically undefined. Preserve that
# state while avoiding a division-by-zero exception, rather than treating
# it as the neutral (and signal-bearing) value 0.0.
self.lines.cci = DivByZero(dev, self.p.factor * meandev, zero=float("nan"))
79 changes: 0 additions & 79 deletions backtrader/indicators/contrib/vortex.py

This file was deleted.

94 changes: 94 additions & 0 deletions backtrader/indicators/obv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python
"""On-Balance Volume indicator.

This module provides the On-Balance Volume (OBV) cumulative volume indicator.
OBV adds the current volume when the closing price rises, subtracts it when
it falls, and leaves the cumulative value unchanged when the closing price is
unchanged.

Classes:
OnBalanceVolume: On-Balance Volume indicator (alias: OBV).

Example:
class MyStrategy(bt.Strategy):
def __init__(self):
self.obv = bt.indicators.OBV(self.data)
"""

from . import Indicator


class OnBalanceVolume(Indicator):
"""Cumulative On-Balance Volume indicator.

Formula:
- first value = volume
- close > previous close: obv = previous obv + volume
- close < previous close: obv = previous obv - volume
- close == previous close: obv = previous obv
"""

alias = ("OBV",)
lines = ("obv",)

def __init__(self):
"""Initialize the indicator."""
super().__init__()

def nextstart(self):
"""Seed OBV with the first available volume value."""
self.lines.obv[0] = self.data.volume[0]

def next(self):
"""Update OBV for the current bar in event-driven mode."""
previous = self.lines.obv[-1]
close = self.data.close[0]
previous_close = self.data.close[-1]
volume = self.data.volume[0]

if close > previous_close:
self.lines.obv[0] = previous + volume
elif close < previous_close:
self.lines.obv[0] = previous - volume
else:
self.lines.obv[0] = previous

def oncestart(self, start, end):
"""Seed OBV in batch-processing mode."""
dst = self.lines.obv.array
volume = self.data.volume.array

while len(dst) < end:
dst.append(float("nan"))

for i in range(start, min(end, len(volume))):
dst[i] = volume[i]

def once(self, start, end):
"""Calculate OBV values in batch-processing mode."""
dst = self.lines.obv.array
close = self.data.close.array
volume = self.data.volume.array
actual_end = min(end, len(close), len(volume))

while len(dst) < end:
dst.append(float("nan"))

if start >= actual_end:
return

if start == 0:
dst[0] = volume[0]
start = 1

previous = dst[start - 1]
for i in range(start, actual_end):
if close[i] > close[i - 1]:
previous += volume[i]
elif close[i] < close[i - 1]:
previous -= volume[i]

dst[i] = previous


OBV = OnBalanceVolume
14 changes: 14 additions & 0 deletions backtrader/lineiterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2091,6 +2091,20 @@ def qbuffer(self, savemem=0):
if savemem:
for line in self.lines:
line.qbuffer()
# LineBuffer.qbuffer sizes the ring buffer from the LINE's own
# _minperiod, which is frequently still 1: only addminperiod() /
# updateminperiod() propagate a value down to the lines, and many
# indicators never call either. An indicator that reads its own
# output recursively (self.lines.x[-1], as cumulative/stateful ones
# do) then finds maxlen == 1 and silently reads NaN instead of the
# previous bar.
#
# Retention must therefore cover this object's lookback needs, with a
# floor of 2 for the [-1] self-reference. minbuffer() only ever grows
# maxlen in QBuffer mode and is a no-op otherwise, so it cannot alter
# results -- unlike raising _minperiod, which is a semantic claim that
# would delay output and propagate to downstream consumers.
line.minbuffer(max(2, self._minperiod))

# If called, anything under it, must save
for obj in self._lineiterators[self.IndType]:
Expand Down
26 changes: 22 additions & 4 deletions backtrader/lineroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,13 @@ def _makeoperationown(self, operation, _ownerskip=None):

if not math.isfinite(value):
return False
return value != 0.0
# bool() for the same reason as in __nonzero__:
# numpy.float64 subclasses float, so it reaches
# this branch and `!= 0.0` yields numpy.bool_.
# This path returns a value rather than going
# through the __bool__ protocol, so a leak here
# would propagate silently instead of raising.
return bool(value != 0.0)
return bool(value)
return False
except Exception:
Expand All @@ -265,7 +271,9 @@ def _makeoperationown(self, operation, _ownerskip=None):

if not math.isfinite(value):
return False
return value != 0.0
# See the note above: guard against numpy.bool_ leaking
# out of this non-__bool__ return path.
return bool(value != 0.0)
return bool(value)
return False
except Exception:
Expand Down Expand Up @@ -607,7 +615,14 @@ def __nonzero__(self):

if not math.isfinite(value):
return False
return value != 0.0
# bool() is required, not cosmetic: numpy.float64
# subclasses float, so numpy scalars reach this branch
# and `np.float64 != 0.0` yields numpy.bool_, which
# CPython rejects from __bool__. Such scalars come from
# PandasData and survive only in QBuffer/exactbars mode,
# where lines are backed by a deque rather than
# array.array("d") (which coerces them to float).
return bool(value != 0.0)
return bool(value)
return False
if hasattr(self, "__getitem__") and hasattr(self, "__len__"):
Expand All @@ -621,7 +636,10 @@ def __nonzero__(self):

if not math.isfinite(value):
return False
return value != 0.0
# See the note above: bool() guards against numpy.float64
# reaching the isinstance(value, float) branch and making
# `!=` return numpy.bool_.
return bool(value != 0.0)
return bool(value)
return False
# Fallback: if no data available, return False
Expand Down
Loading
Loading