"""
Mansfield Stock Charts
======================
This module provides tools for generating and plotting Mansfield Stock Charts
based on Stan Weinstein's methods, as described in the book "Secrets for
Profiting in Bull and Bear Markets." It includes functionalities for
calculating and plotting Mansfield Relative Strength (RSM) using `mplfinance`
for visualization.
Classes:
--------
- StockChart: A class for generating and plotting Mansfield Stock Charts for
individual stocks.
- RelativeStrengthLines: A class for plotting Mansfield Relative Strength
(RSM) lines of multiple stocks compared to a reference index.
Usage:
------
To generate a Mansfield Stock Chart for a single stock:
>>> StockChart.plot('TSLA', interval='1wk')
To compare the Mansfield Relative Strength of multiple stocks:
>>> symbols = ['NVDA', 'MSFT', 'META', 'AAPL', 'TSM']
>>> RelativeStrengthLines.plot(symbols, interval='1d')
See Also:
---------
- `Mansfield relative strength | TrendSpider Store
<https://trendspider.com/trading-tools-store/indicators/
mansfield-relative-strength/>`_
"""
__software__ = "Mansfield Stock Charts"
__version__ = "2.4"
__author__ = "York <york.jong@gmail.com>"
__date__ = "2024/08/25 (initial version) ~ 2024/09/12 (last revision)"
__all__ = [
'StockChart',
'RelativeStrengthLines',
]
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
import mplfinance as mpf
from .. import tw
from .. import file_utils
from ..utils import MarketColorStyle, decide_market_color_style
from . import mpf_utils as mpfu
from .. import stock_indices as si
from ..ta import simple_moving_average, exponential_moving_average
from ..rsm import mansfield_relative_strength
[docs]
class StockChart:
"""
A class for generating and plotting Mansfield Stock Charts.
Mansfield Stock Charts are a method for visualizing stock trends and
relative strength compared to a reference index, as popularized by Stan
Weinstein. This class provides methods to fetch stock data, compute
relative strength, and plot the resulting charts using mplfinance.
"""
[docs]
@staticmethod
def plot(symbol, period='2y', interval='1d', ticker_ref=None, ma='SMA',
legend_loc='upper left', market_color_style=MarketColorStyle.AUTO,
style='yahoo', hides_nontrading=True, out_dir='out'):
"""Plot a Mansfield Stock Chart for a given stock symbol and time
period.
Parameters
----------
symbol: str
The stock symbol to analyze.
period: str, optional
The period of historical data to fetch. Valid values are '6mo', '1y',
'2y', '5y', '10y', 'ytd', 'max'. Default is '2y'.
- mo -- monthes
- y -- years
- ytd -- year to date
- max -- all data
interval: str, optional
The interval for data points. Valid values are '1d' for daily or '1wk'
for weekly. Default is '1d'.
ticker_ref: str, optional
The ticker symbol of the reference index. If None, defaults to S&P
500 ('^GSPC') or Taiwan Weighted Index ('^TWII') if the first stock is
a Taiwan stock.
ma: str, optional
Moving average type ('SMA', 'EMA'). Default to 'SMA'.
legend_loc: str, optional
the location of the legend (default is 'upper left').
Valid locations are
- 'best'
- 'upper right'
- 'upper left'
- 'lower left'
- 'lower right'
- 'right'
- 'center left'
- 'center right'
- 'lower center'
- 'upper center'
- 'center'
market_color_style: MarketColorStyle, optional
The market color style to use. Default is MarketColorStyle.AUTO.
style: str, optional
The chart style to use. Common styles include:
- 'yahoo': Yahoo Finance style
- 'charles': Charles style
- 'tradingview': TradingView style
- 'binance': Binance style
- 'binancedark': Binance dark mode style
- 'mike': Mike style (dark mode)
- 'nightclouds': Dark mode with sleek appearance
- 'checkers': Checkered style
- 'ibd': Investor's Business Daily style
- 'sas': SAS style
- 'starsandstripes': Stars and Stripes style
- 'kenan': Kenan style
- 'blueskies': Blue Skies style
- 'brasil': Brasil style
Default is 'yahoo'.
hides_nontrading: bool, optional
Whether to hide non-trading periods. Default is True.
out_dir: str, optional
the output directory for saving figure.
Raises
------
ValueError
If an unsupported interval is provided.
"""
ticker = tw.as_yfinance(symbol)
if not ticker_ref:
ticker_ref = '^GSPC' # S&P 500 Index
if tw.is_taiwan_stock(ticker):
ticker_ref = '^TWII' # Taiwan Weighted Index
# Set moving average windows based on the interval
try:
rs_window = { '1d': 252, '1wk': 52, '1mo': 12 }[interval]
ma_windows = {
'1d': [50, 150, 200],
'1wk': [10, 30, 40],
'1mo': [3, 8, 10],
}[interval]
except KeyError:
raise ValueError("Invalid interval. "
"Must be '1d', '1wk', or '1mo'.")
vma_window, *_ = ma_windows
# Select the MA function based on the 'ma' parameter
try:
ma_func = {
'SMA': simple_moving_average,
'EMA': exponential_moving_average,
}[ma]
except KeyError:
raise ValueError("Invalid ma type. Must be 'SMA' or 'EMA'.")
# Fetch data for stock and index
df = yf.download([ticker_ref, ticker], period=period, interval=interval)
df_ref = df.xs(ticker_ref, level='Ticker', axis=1)
df = df.xs(ticker, level='Ticker', axis=1)
# Calculate Mansfield Relative Strength (RSM)
df['RSM'] = mansfield_relative_strength(df['Close'], df_ref['Close'],
rs_window, ma=ma)
# Calculate moving averages for stock
ma = ma.replace('SMA', 'MA')
for window in ma_windows:
df[f'{ma}{window}'] = ma_func(df['Close'], window)
# Calculate volume MA
df[f'Vol {ma}{vma_window}'] = ma_func(df['Volume'], vma_window)
# Plot the figure
addplot = [
# Plot of Price Moving Average
*[mpf.make_addplot(df[f'{ma}{n}'], panel=0, label=f'{ma}{n}')
for n in ma_windows],
# Plot of Relative Strength
mpf.make_addplot(df['RSM'], panel=1, label=ticker, color='green',
ylabel='Relative Strength'),
mpf.make_addplot([0]*len(df), panel=1,
label=si.get_name(ticker_ref),
linestyle='--', color='gray', secondary_y=False),
# Plot of Volume Moving Average
mpf.make_addplot(df[f'Vol {ma}{vma_window}'], panel=2,
label=f'Vol {ma}{vma_window}', color='purple'),
]
# Make a customized color style
mc_style = decide_market_color_style(ticker, market_color_style)
mpf_style = mpfu.decide_mpf_style(base_mpf_style=style,
market_color_style=mc_style)
# Plot candlesticks, MA, volume, volume MA, and RS
fig, axes = mpf.plot(
df, type='candle', # candlesticks
volume=True, volume_panel=2, # volume
addplot=addplot, # MA, RS, and Volume MA
ylabel=f"Price ({yf.Ticker(ticker).info['currency']})",
panel_ratios=(5, 3, 2),
figratio=(2, 1), figscale=1.4,
style=mpf_style,
show_nontrading=not hides_nontrading,
returnfig=True,
)
# Set location of legends
for ax in axes:
if ax.legend_:
ax.legend(loc=legend_loc)
# Convert datetime index to string format suitable for display
df.index = df.index.strftime('%Y-%m-%d')
fig.suptitle(f"Mansfield Stock Chart: {symbol} - {interval} "
f"({df.index[0]} to {df.index[-1]})", y=0.93)
# Show the figure
mpf.show()
# Save the figure
out_dir = file_utils.make_dir(out_dir)
fn = file_utils.gen_fn_info(symbol, interval, df.index[-1], 'RSM')
fig.savefig(f'{out_dir}/{fn}.png', bbox_inches='tight')
[docs]
class RelativeStrengthLines:
"""
A class for plotting Mansfield Relative Strength (RSM) lines of multiple
stocks.
This class allows for the comparison of the relative strength of multiple
stocks against a reference index over a specified period. It supports
various intervals and moving average calculations (SMA and EMA) for the
relative strength computation.
"""
[docs]
@staticmethod
def plot(symbols, period='2y', interval='1d', ticker_ref=None, ma='SMA',
legend_loc='upper left', style='charles',
color_cycle=plt.cm.Paired.colors,
out_dir='out'):
"""
Plot the Mansfield Relative Strength (RSM) of multiple stocks compared
to a reference index.
Parameters
------------
symbols: list of str
List of stock symbols to compare. Can include both US and Taiwan
stocks.
period: str, optional
the period data to download. . Defaults to '2y'. Valid values are
6mo, 1y, 2y, 5y, 10y, ytd, max.
- mo -- monthes
- y -- years
- ytd -- year to date
- max -- all data
interval: str, optional
The interval for data points ('1d' for daily, '1wk' for weekly;
default is '1d').
ticker_ref: str, optional
The ticker symbol of the reference index. If None, defaults to S&P
500 ('^GSPC') or Taiwan Weighted Index ('^TWII') if the first stock
is a Taiwan stock.
ma: str, optional
Moving average type ('SMA', 'EMA'). Default to 'SMA'.
legend_loc: str, optional
the location of the legend (default is 'upper left').
Valid locations are
- 'best'
- 'upper right'
- 'upper left'
- 'lower left'
- 'lower right'
- 'right'
- 'center left'
- 'center right'
- 'lower center'
- 'upper center'
- 'center'
style: str, optional
The chart style to use. Common styles include:
- 'yahoo': Yahoo Finance style
- 'charles': Charles style
- 'tradingview': TradingView style
- 'binance': Binance style
- 'binancedark': Binance dark mode style
- 'mike': Mike style (dark mode)
- 'nightclouds': Dark mode with sleek appearance
- 'checkers': Checkered style
- 'ibd': Investor's Business Daily style
- 'sas': SAS style
- 'starsandstripes': Stars and Stripes style
- 'kenan': Kenan style
- 'blueskies': Blue Skies style
- 'brasil': Brasil style
Default is 'charles'.
color_cycle: list or None
Specifies a list of colors to be used for cycling through plot
lines. If None, the default matplotlib color cycle will be used.
You can pass a list of colors to override the default color cycle.
Each plot line will use the next color in the sequence.
Default color sequence:
- plt.cm.Paired.colors (20 colors, cooler and more muted)
Other useful predefined color cycles:
- plt.cm.tab20.colors (20 colors, brighter)
- plt.cm.tab20b.colors (20 colors, darker)
- plt.cm.Paired.colors (12 colors, alternating between deep and
pastel colors; useful for categorical data)
- plt.cm.Set3.colors (12 colors, pastel-like; good for categorical
data)
- plt.cm.Set1.colors (9 colors, bold and highly distinct; ideal for
categorical data)
out_dir: str, optional
Directory to save the image file. Defaults to 'out'.
Returns
--------
None
The function generates a plot and saves it as an image file.
Example
--------
>>> symbols = ['NVDA', 'MSFT', 'META', 'AAPL', 'TSM']
>>> plot(symbols)
"""
if not ticker_ref:
ticker_ref = '^GSPC' # S&P 500 Index
if tw.is_taiwan_stock(tw.as_yfinance(symbols[0])):
ticker_ref = '^TWII' # Taiwan Weighted Index
# Set moving average windows based on the interval
try:
rs_window = { '1d': 252, '1wk': 52, '1mo': 12 }[interval]
except KeyError:
raise ValueError("Invalid interval. Must be '1d', '1wk', or '1mo'.")
# Fetch data for stocks and index
tickers = [tw.as_yfinance(s) for s in symbols]
df = yf.download([ticker_ref]+tickers, period=period, interval=interval)
df_price = df.xs('Close', level='Price', axis=1)
# Set the figure
fig = mpf.figure(style=style, figsize=(12, 6))
ax = fig.add_subplot() # Add first subplot
if color_cycle:
ax.set_prop_cycle(color=color_cycle)
# Plot Relative Strength Lines
for ticker, symbol in zip(tickers, symbols):
rs = mansfield_relative_strength(df_price[ticker],
df_price[ticker_ref],
rs_window, ma=ma)
ax.plot(rs.index, rs, label=f'{si.get_name(symbol)}')
# Plot the Reference Line
ax.plot(rs.index, [0]*len(df), label=f'{si.get_name(ticker_ref)}',
color='gray', linestyle='--')
# Set Y axis
ax.set_ylabel('Relative Strength '
f'(Compared to {si.get_name(ticker_ref)})')
ax.yaxis.set_label_position("right")
ax.yaxis.tick_right()
ax.tick_params(axis='y', labelright=True, labelleft=False)
# Set location of legends
ax.legend(loc=legend_loc)
# Convert datetime index to string format suitable for display
df.index = df.index.strftime('%Y-%m-%d')
fig.suptitle(f"Mansfield Relative Strength Comparison - {interval} "
f"({df.index[0]} to {df.index[-1]})", y=0.93)
# Show the figure
mpf.show()
# Save the figure
out_dir = file_utils.make_dir(out_dir)
fn = file_utils.gen_fn_info('stocks', interval, df.index[-1], 'RsmLines')
fig.savefig(f'{out_dir}/{fn}.png', bbox_inches='tight')
if __name__ == '__main__':
"""
Example usage of StockChart and RelativeStrengthLines classes to generate
plots.
"""
mpfu.use_mac_chinese_font()
#StockChart.plot('TSLA', interval='1wk')
#StockChart.plot('羅昇', interval='1wk')
#symbols = ['羅昇', '昆盈', '穎漢', '光聖', '所羅門']
#RelativeStrengthLines.plot(symbols, interval='1d')
#symbols = ['^NDX', '^DJA', '^RUI', '^SOX']
symbols = ['SOXX', 'DVY', 'IWB','IWM', 'IWV', 'IJR',
'ITB', 'IHI', 'IYC', 'ITA', 'IAK']
RelativeStrengthLines.plot(symbols, interval='1d',
color_cycle=plt.cm.Paired.colors)