Skip to content

Commit

Permalink
...
Browse files Browse the repository at this point in the history
  • Loading branch information
furechan committed Jan 10, 2025
1 parent 4a2359a commit 6c14b7d
Show file tree
Hide file tree
Showing 14 changed files with 1,311 additions and 771 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 0.0.21
- Refactored `wrappers.py` into `plotters.py`
- Single line indicators now accept a `line_style` attribute

## 0.0.20
- Added `CCI` Indicator (Commodity Channel Index)
- Added `BOP` Indicator (Balance of Power)
Expand Down
9 changes: 9 additions & 0 deletions src/mplchart/wrappers.py → archive/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@
from .utils import get_name, get_label, get_info


warnings.warn(f"Module {__name__} is deprecated!", DeprecationWarning, stacklevel=2)


# TODO Remove Wrappers


@singledispatch
def get_wrapper(indicator):
"""create rendering wrapper for indicator"""

warnings.warn("get_wrapper is deprecated!", DeprecationWarning, stacklevel=2)

if hasattr(indicator, "plot_handler"):
return None

Expand All @@ -22,6 +30,7 @@ def get_wrapper(indicator):

class AutoWrapper(Wrapper):
def __init__(self, indicator):
warnings.warn("AutoWrapper is deprecated!", DeprecationWarning, stacklevel=2)
self.indicator = indicator

def __call__(self, data):
Expand Down
39 changes: 23 additions & 16 deletions examples/chart-primitives.ipynb

Large diffs are not rendered by default.

45 changes: 26 additions & 19 deletions examples/talib-examples.ipynb

Large diffs are not rendered by default.

29 changes: 15 additions & 14 deletions examples/typical-usage.ipynb

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions examples/volume-chart.ipynb

Large diffs are not rendered by default.

84 changes: 25 additions & 59 deletions misc/mplchart-tests.ipynb

Large diffs are not rendered by default.

416 changes: 416 additions & 0 deletions misc/sar-prototype.ipynb

Large diffs are not rendered by default.

1,174 changes: 587 additions & 587 deletions misc/talib-functions.ipynb

Large diffs are not rendered by default.

76 changes: 22 additions & 54 deletions src/mplchart/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@

import matplotlib.pyplot as plt

from functools import cached_property
from collections import Counter
from functools import cached_property


from .wrappers import get_wrapper
from .colors import closest_color
from .utils import series_xy, same_scale, get_info
from .utils import same_scale, get_info
from .layout import make_twinx, StandardLayout
from .mapper import RawDateMapper, DateIndexMapper
from .plotters import AutoPlotter


"""
Expand Down Expand Up @@ -79,7 +78,6 @@ def __init__(
stacklevel=2,
)


if bgcolor is not None:
warnings.warn(
"bgcolor parameter is deprecated. Use matplotlib styles instead!",
Expand Down Expand Up @@ -112,6 +110,7 @@ def figure(self):

@staticmethod
def normalize(prices):
"""normalize prices dataframe"""
prices = prices.rename(columns=str.lower).rename_axis(index=str.lower)
return prices

Expand Down Expand Up @@ -234,7 +233,7 @@ def map_date(self, date):
return self.mapper.map_date(date)

def set_title(self, title):
"""Sets chart title on root axes. Must be called after init_axes!"""
"""Set chart title on root axes. Must be called after init_axes!"""

if title is None:
return
Expand All @@ -245,7 +244,7 @@ def set_title(self, title):
ax.set_title(title)

def config_axes(self, ax, root=False):
"""configures axes"""
"""configure axes"""

ax.set_xmargin(0.0)
ax.set_axisbelow(True)
Expand Down Expand Up @@ -286,7 +285,7 @@ def init_axes(self):
self.config_axes(ax, root=True)

def root_axes(self):
"""returns root axes, usualy axes[0]"""
"""root axes (usualy axes[0])"""

if not self.figure.axes:
warnings.warn("root_axes called before init_axes!")
Expand All @@ -295,7 +294,7 @@ def root_axes(self):
return self.figure.axes[0]

def main_axes(self):
"""returns main axes, usualy axes[1]"""
"""main axes (usualy axes[1])"""

if not self.figure.axes:
warnings.warn("main_axes called before init_axes!")
Expand Down Expand Up @@ -348,7 +347,7 @@ def force_target(self, target):

def get_axes(self, target=None, *, height_ratio=None):
"""
selects existing axes or creates new axes depending on target
select existing axes or creates new axes depending on target
Args:
target: one of "main", "same", twinx", "above", "below"
Expand Down Expand Up @@ -405,7 +404,7 @@ def dump_axes(self):
print(i, label, xlim, ylim)

def count_axes(self, include_root=False, include_twins=False):
""" " counts axes that are neither root or twinx"""
"""count axes that are neither root or twinx"""
count = 0
for ax in self.figure.axes:
label = getattr(ax, "_label", None)
Expand All @@ -417,7 +416,7 @@ def count_axes(self, include_root=False, include_twins=False):
return count

def calc_result(self, prices, indicator):
""" calculates indicator result saving last result"""
"""calculate indicator result saving last result"""

if indicator is not None:
result = indicator(prices)
Expand All @@ -430,75 +429,44 @@ def calc_result(self, prices, indicator):
return result

def plot_indicator(self, data, indicator):
"""calculates and plots an indicator"""
"""calculate and plot an indicator"""

# Call the indicator's plot_handler if defined (before any calc)
# this is the only location where plot_handler is called
# plot_handler is currently defined only for Primitives
# Note that dates have not been mapped yet (see slice)
# Note that data have not been mapped/sliced yet
if hasattr(indicator, "plot_handler"):
indicator.plot_handler(data, chart=self)
return

# Invoke indicator and compute result if indicator is callable
# Result data is mapped to the charting view (see slice)
# Result data is mapped to the charting view
if callable(indicator):
result = self.calc_result(data, indicator)
result = self.slice(result)
else:
raise ValueError(f"Indicator {indicator!r} not callable")

# Use wrapper in place of indicator if applicable
wrapper = get_wrapper(indicator)
if wrapper is not None:
indicator = wrapper

# Select axes according to indicator properties (default_pane, same_scale)
# target = self.get_target(indicator)
# ax = self.get_axes(target)
ax = None

# Calling indicator plot_result if present
# Note here we are calling plot_result with a defined axes
if hasattr(indicator, "plot_result"):
indicator.plot_result(result, chart=self, ax=ax)
return
plotter = AutoPlotter(self, indicator, result)
plotter.plot_all()

self.plot_result(result, indicator, ax=ax)

def plot_result(self, result, indicator, ax=None):
"""last resort plot_result handler"""

raise RuntimeError("Last resort handler is legacy!")

name = getattr(indicator, "__name__", str(indicator))

if result.__class__.__name__ == "Series":
result = result.to_frame()

for colnum, colname in enumerate(result):
label = name if colnum == 0 else colname
series = result[colname]
xv, yv = series_xy(series)
ax.plot(xv, yv, label=label)

def add_legends(self):
"""adds legends to all axes"""
"""add legends to all axes"""
for ax in self.figure.axes:
handles, labels = ax.get_legend_handles_labels()
if handles:
ax.legend(loc="upper left")

def plot(self, prices, indicators, *, target=None, rebase=False):
"""plots a list of indicators
"""plot list of indicators
Parameters
----------
prices: dataframe
the prices data frame
indicators: list of indicators
list of indciators to plot
list of indicators to plot
"""

self.last_result = None
Expand All @@ -517,7 +485,7 @@ def plot(self, prices, indicators, *, target=None, rebase=False):
self.add_legends()

def plot_vline(self, date):
"""plots a vertical line across all axes"""
"""plot vertical line across all axes"""

if not self.figure.axes:
raise RuntimeError("axes not initialized!")
Expand All @@ -528,15 +496,15 @@ def plot_vline(self, date):
ax.axvline(xv, linestyle="dashed")

def show(self):
"""shows the chart"""
"""show chart"""
if not self.figure.axes:
self.get_axes()

# figure.show() seems only to work if figure was not created by pyplot!
plt.show()

def render(self, format="svg", *, dpi="figure"):
"""renders the chart to the specific format"""
"""render chart to the specific format"""
if not self.figure.axes:
self.get_axes()

Expand Down
12 changes: 5 additions & 7 deletions src/mplchart/indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def __call__(self, prices):
class TSF(Indicator):
"""Time Siries Forecast (time linear regression)"""

same_scale = True
same_scale: bool = True

def __init__(self, period: int = 20, offset: int =0):
self.period = period
Expand Down Expand Up @@ -183,8 +183,7 @@ def __call__(self, prices):
class CMF(Indicator):
"""Chaikin Money Flow"""

overbought = 0
oversold = 0
line_style: str = "area"

def __init__(self, period: int = 20):
self.period = period
Expand All @@ -196,9 +195,9 @@ def __call__(self, prices):
class MFI(Indicator):
"""Money Flow Index"""

overbought = 80
oversold = 20
yticks = 20, 50, 80
overbought: float = 80
oversold: float = 20
yticks: tuple = 20, 50, 80

def __init__(self, period: int = 14):
self.period = period
Expand Down Expand Up @@ -258,7 +257,6 @@ def __call__(self, prices):
return library.calc_ppo(series, self.n1, self.n2, self.n3)



class STOCH(Indicator):
"""Stochastic Oscillator"""

Expand Down
Loading

0 comments on commit 6c14b7d

Please sign in to comment.