import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import akshare as ak
# 下载股票数据
def download_stock_data(ticker, start_date):
"""
下载股票数据
:param ticker: 股票代码
:param start_date: 开始日期
:return: 包含股票数据的 DataFrame
"""
stock_data = ak.stock_zh_a_hist(ticker, start_date=start_date)
return stock_data
# 计算移动平均线
def calculate_moving_average(data, window):
"""
计算移动平均线 (MA)
:param data: 股票数据
:param window: 窗口大小
:return: 移动平均线数据
"""
return data['收盘'].rolling(window=window).mean()
# 计算相对强弱指数(RSI)
def calculate_rsi(data, window):
"""
计算相对强弱指数 (RSI)
RSI = 100 - (100 / (1 + RS))
RS = 平均涨幅 / 平均跌幅
:param data: 股票数据
:param window: 窗口大小
:return: RSI 数据
"""
delta = data.diff()
gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
return rsi
# 计算布林带
def calculate_bollinger_bands(data, window=20):
"""
计算布林带 (Bollinger Bands)
中轨 = 移动平均线 (MA)
上轨 = MA + 2 * 标准差
下轨 = MA - 2 * 标准差
:param data: 股票数据
:param window: 窗口大小
:return: 布林带数据
"""
data['MA20'] = data['收盘'].rolling(window=window).mean()
data['STD20'] = data['收盘'].rolling(window=window).std()
data['Upper Band'] = data['MA20'] + (2 * data['STD20'])
data['Lower Band'] = data['MA20'] - (2 * data['STD20'])
return data
# 计算 MACD
def calculate_macd(data, short_window=12, long_window=26, signal_window=9):
"""
计算 MACD (移动平均收敛散度)
MACD = 短期EMA - 长期EMA
Signal Line = MACD 的 EMA
Histogram = MACD - Signal Line
:param data: 股票数据
:param short_window: 短期窗口大小
:param long_window: 长期窗口大小
:param signal_window: 信号线窗口大小
:return: MACD 数据
"""
data['EMA12'] = data['收盘'].ewm(span=short_window, adjust=False).mean()
data['EMA26'] = data['收盘'].ewm(span=long_window, adjust=False).mean()
data['MACD'] = data['EMA12'] - data['EMA26']
data['Signal Line'] = data['MACD'].ewm(span=signal_window, adjust=False).mean()
data['MACD Histogram'] = data['MACD'] - data['Signal Line']
return data
# 计算随机指标
def calculate_stochastic_oscillator(data, window=14):
"""
计算随机指标 (Stochastic Oscillator)
%K = 100 * (当前收盘价 - 最低价) / (最高价 - 最低价)
%D = %K 的移动平均
:param data: 股票数据
:param window: 窗口大小
:return: 随机指标数据
"""
data['Lowest Low'] = data['最低'].rolling(window=window).min()
data['Highest High'] = data['最高'].rolling(window=window).max()
data['%K'] = 100 * ((data['收盘'] - data['Lowest Low']) / (data['Highest High'] - data['Lowest Low']))
data['%D'] = data['%K'].rolling(window=3).mean()
return data
# 计算 ATR
def calculate_atr(data, window=14):
"""
计算平均真实波幅 (ATR)
True Range = max(最高价 - 最低价, |最高价 - 前收盘价|, |最低价 - 前收盘价|)
ATR = True Range 的移动平均
:param data: 股票数据
:param window: 窗口大小
:return: ATR 数据
"""
data['High-Low'] = data['最高'] - data['最低']
data['High-PrevClose'] = abs(data['最高'] - data['收盘'].shift(1))
data['Low-PrevClose'] = abs(data['最低'] - data['收盘'].shift(1))
data['True Range'] = data[['High-Low', 'High-PrevClose', 'Low-PrevClose']].max(axis=1)
data['ATR'] = data['True Range'].rolling(window=window).mean()
return data
# 计算 OBV
def calculate_obv(data):
"""
计算能量潮指标 (OBV)
OBV = 前一日 OBV + 当日成交量 * 价格变化方向
:param data: 股票数据
:return: OBV 数据
"""
data['OBV'] = (np.sign(data['收盘'].diff()) * data['成交量']).fillna(0).cumsum()
return data
# 计算 CCI
def calculate_cci(data, window=20):
"""
计算商品通道指数 (CCI)
TP = (最高价 + 最低价 + 收盘价) / 3
SMA TP = TP 的移动平均
Mean Deviation = TP 的平均绝对偏差
CCI = (TP - SMA TP) / (0.015 * Mean Deviation)
:param data: 股票数据
:param window: 窗口大小
:return: CCI 数据
"""
data['TP'] = (data['最高'] + data['最低'] + data['收盘']) / 3
data['SMA TP'] = data['TP'].rolling(window=window).mean()
data['Mean Deviation'] = data['TP'].rolling(window=window).apply(lambda x: np.mean(np.abs(x - np.mean(x))))
data['CCI'] = (data['TP'] - data['SMA TP']) / (0.015 * data['Mean Deviation'])
return data
# 生成移动平均线交叉策略信号
def moving_average_crossover_strategy(data, short_window, long_window):
"""
生成移动平均线交叉策略信号
:param data: 股票数据
:param short_window: 短期窗口大小
:param long_window: 长期窗口大小
:return: 策略信号数据
"""
signals = pd.DataFrame(index=data.index)
signals['signal'] = 0.0
signals['short_mavg'] = calculate_moving_average(data, short_window)
signals['long_mavg'] = calculate_moving_average(data, long_window)
signals.loc[short_window:, 'signal'] = np.where(
signals['short_mavg'][short_window:] > signals['long_mavg'][short_window:], 1.0, 0.0
)
signals['positions'] = signals['signal'].diff()
return signals
# 绘制股票数据和技术指标
def plot_strategy(data, signals):
"""
绘制股票数据和技术指标
:param data: 股票数据
:param signals: 策略信号数据
"""
sns.set_style("darkgrid")
fig, axes = plt.subplots(8, 1, figsize=(14, 24), sharex=True)
# 绘制收盘价和移动平均线
axes[0].plot(data['日期'], data['收盘'], label='Close Price', color='dodgerblue', linewidth=2)
axes[0].plot(data['日期'], data['MA20'], label='20-Day MA', color='orange', linestyle='--', linewidth=1.5)
axes[0].plot(data['日期'], data['MA50'], label='50-Day MA', color='green', linestyle='--', linewidth=1.5)
# 标注买入和卖出信号
buy_signals = signals.loc[signals.positions == 1.0]
sell_signals = signals.loc[signals.positions == -1.0]
axes[0].plot(data['日期'].iloc[buy_signals.index], data['收盘'].iloc[buy_signals.index], '^', markersize=10, color='lime', label='Buy Signal')
axes[0].plot(data['日期'].iloc[sell_signals.index], data['收盘'].iloc[sell_signals.index], 'v', markersize=10, color='red', label='Sell Signal')
axes[0].set_title('Stock Price and Moving Averages with Buy/Sell Signals', fontsize=14, fontweight='bold')
axes[0].legend(loc='upper left', fontsize=12)
axes[0].grid(True, linestyle='--', alpha=0.7)
# 绘制 RSI
axes[1].plot(data['日期'], data['RSI'], label='RSI', color='purple', linewidth=2)
axes[1].axhline(70, color='red', linestyle='--', linewidth=1.5, label='Overbought (70)')
axes[1].axhline(30, color='green', linestyle='--', linewidth=1.5, label='Oversold (30)')
axes[1].set_title('Relative Strength Index (RSI)', fontsize=14, fontweight='bold')
axes[1].legend(loc='upper left', fontsize=12)
axes[1].grid(True, linestyle='--', alpha=0.7)
# 绘制布林带
axes[2].plot(data['日期'], data['收盘'], label='Close Price', color='dodgerblue', linewidth=2)
axes[2].plot(data['日期'], data['Upper Band'], label='Upper Band', color='red', linestyle='--', linewidth=1.5)
axes[2].plot(data['日期'], data['Lower Band'], label='Lower Band', color='green', linestyle='--', linewidth=1.5)
axes[2].set_title('Bollinger Bands', fontsize=14, fontweight='bold')
axes[2].legend(loc='upper left', fontsize=12)
axes[2].grid(True, linestyle='--', alpha=0.7)
# 绘制 MACD
axes[3].plot(data['日期'], data['MACD'], label='MACD', color='blue', linewidth=2)
axes[3].plot(data['日期'], data['Signal Line'], label='Signal Line', color='red', linestyle='--', linewidth=1.5)
axes[3].bar(data['日期'], data['MACD Histogram'], label='MACD Histogram', color='gray', alpha=0.5)
axes[3].set_title('MACD', fontsize=14, fontweight='bold')
axes[3].legend(loc='upper left', fontsize=12)
axes[3].grid(True, linestyle='--', alpha=0.7)
# 绘制随机指标
axes[4].plot(data['日期'], data['%K'], label='%K', color='blue', linewidth=2)
axes[4].plot(data['日期'], data['%D'], label='%D', color='red', linestyle='--', linewidth=1.5)
axes[4].axhline(80, color='red', linestyle='--', linewidth=1.5, label='Overbought (80)')
axes[4].axhline(20, color='green', linestyle='--', linewidth=1.5, label='Oversold (20)')
axes[4].set_title('Stochastic Oscillator', fontsize=14, fontweight='bold')
axes[4].legend(loc='upper left', fontsize=12)
axes[4].grid(True, linestyle='--', alpha=0.7)
# 绘制 ATR
axes[5].plot(data['日期'], data['ATR'], label='ATR', color='blue', linewidth=2)
axes[5].set_title('Average True Range (ATR)', fontsize=14, fontweight='bold')
axes[5].legend(loc='upper left', fontsize=12)
axes[5].grid(True, linestyle='--', alpha=0.7)
# 绘制 OBV
axes[6].plot(data['日期'], data['OBV'], label='OBV', color='blue', linewidth=2)
axes[6].set_title('On-Balance Volume (OBV)', fontsize=14, fontweight='bold')
axes[6].legend(loc='upper left', fontsize=12)
axes[6].grid(True, linestyle='--', alpha=0.7)
# 绘制 CCI
axes[7].plot(data['日期'], data['CCI'], label='CCI', color='blue', linewidth=2)
axes[7].axhline(100, color='red', linestyle='--', linewidth=1.5, label='Overbought (100)')
axes[7].axhline(-100, color='green', linestyle='--', linewidth=1.5, label='Oversold (-100)')
axes[7].set_title('Commodity Channel Index (CCI)', fontsize=14, fontweight='bold')
axes[7].legend(loc='upper left', fontsize=12)
axes[7].grid(True, linestyle='--', alpha=0.7)
plt.tight_layout()
plt.show()
# 主函数
def main():
ticker = '001379'
start_date = '2024-02-01'
short_window = 20
long_window = 50
# 下载股票数据
stock_data = download_stock_data(ticker, start_date)
# 计算技术指标
stock_data['MA20'] = calculate_moving_average(stock_data, short_window)
stock_data['MA50'] = calculate_moving_average(stock_data, long_window)
stock_data['RSI'] = calculate_rsi(stock_data['收盘'], window=14)
stock_data = calculate_bollinger_bands(stock_data)
stock_data = calculate_macd(stock_data)
stock_data = calculate_stochastic_oscillator(stock_data)
stock_data = calculate_atr(stock_data)
stock_data = calculate_obv(stock_data)
stock_data = calculate_cci(stock_data)
# 生成策略信号
signals = moving_average_crossover_strategy(stock_data, short_window, long_window)
# 将结果保存到 CSV 文件
stock_data.to_csv(r'D:\dowload\stock_data_with_indicators.csv', index=False)
# 绘制策略图表
plot_strategy(stock_data, signals)
if __name__ == "__main__":
main()
移动平均线(Moving Average,简称MA)
移动平均线(Moving Average,简称MA)是一种常用的技术分析工具,用于平滑价格数据,帮助识别趋势。移动平均线通过计算一定时间周期内的平均价格来实现这一点。常见的移动平均线有简单移动平均线(Simple Moving Average,SMA)和指数移动平均线(Exponential Moving Average,EMA)。
简单移动平均线(SMA)
简单移动平均线是通过将某个时间段内的收盘价相加,然后除以该时间段的天数来计算的。公式如下:
其中:
- Pi 是第 i 天的收盘价
- n 是时间周期
指数移动平均线(EMA)
指数移动平均线对最近的价格给予更多的权重,使其更能反映最近的价格变化。公式如下:
其中:
- Pt 是第 t 天的收盘价
- EMA(t−1) 是前一个时间点的 EMA
- k 是平滑因子,通常计算为 k=2/n+1
RSI 相对强弱指数(Relative Strength Index,RSI)
RSI 相对强弱指数(Relative Strength Index,RSI)是一种常用的技术分析指标,用于评估股票、期货、外汇等金融工具的价格变动强度和速度。RSI 通过比较一段时间内价格上涨的平均值与价格下跌的平均值来计算,通常使用 14 个交易日作为默认的时间周期。
RSI 的计算公式
-
计算平均收益(Average Gain):
- 平均收益是过去 N 个周期内所有正收益的平均值。
- 例如,如果使用 14 个交易日,则计算这 14 个交易日内所有价格上涨的天数的平均值。
-
计算平均损失(Average Loss):
- 平均损失是过去 N 个周期内所有负收益的绝对值的平均值。
- 例如,如果使用 14 个交易日,则计算这 14 个交易日内所有价格下跌的天数的平均值的绝对值。
-
计算相对强度(Relative Strength, RS):
- RS = 平均收益 / 平均损失
-
计算 RSI:
- RSI = 100 - (100 / (1 + RS))
RSI 的解释
- RSI 值范围:RSI 的值介于 0 到 100 之间。
- 超买和超卖:
- 通常,当 RSI 值超过 70 时,认为市场处于超买状态,可能即将回调。
- 当 RSI 值低于 30 时,认为市场处于超卖状态,可能即将反弹。
- 中心线:RSI 值在 50 附近波动时,表示市场处于平衡状态。
布林带(Bollinger Bands)
布林带(Bollinger Bands)是一种常用的技术分析工具,用于衡量市场波动性。它由三条线组成:
- 中轨(Middle Band):通常是价格的简单移动平均线(SMA)。
- 上轨(Upper Band):中轨加上两倍的标准差。
- 下轨(Lower Band):中轨减去两倍的标准差。
布林带的计算步骤如下:
- 计算中轨:通常使用20天的简单移动平均线(SMA)。
- 计算标准差:计算过去20天价格的标准差。
- 计算上轨和下轨:
- 上轨 = 中轨 + 2 * 标准差
- 下轨 = 中轨 - 2 * 标准差
MACD(移动平均收敛/发散指标)
MACD(移动平均收敛/发散指标)是一个常用的技术分析指标,用于衡量股票价格的趋势和动量。它由三部分组成:快线(MACD线)、慢线(信号线)和MACD柱状图。
以下是计算MACD的步骤:
-
计算快线(MACD线): 快线是短期指数移动平均线(EMA)与长期指数移动平均线之间的差值。通常,短期EMA使用12天,长期EMA使用26天。
EMA_12 = 计算EMA(收盘价, 12)
EMA_26 = 计算EMA(收盘价, 26)
MACD线 = EMA_12 - EMA_26
-
计算慢线(信号线): 慢线是MACD线的9天指数移动平均线。
信号线 = 计算EMA(MACD线, 9)
3. 计算MACD柱状图: MACD柱状图是MACD线与信号线之间的差值。
MACD柱状图 = MACD线 - 信号线
随机指标(Stochastic Oscillator)
随机指标(Stochastic Oscillator)是一种技术分析工具,用于衡量某个资产的收盘价与特定时间周期内的价格范围之间的关系。它可以帮助交易者识别市场上的超买或超卖状态。
随机指标通常由两个线组成:
- %K 线:主要线,表示当前收盘价在最近一段时间内的价格范围中的位置。
- %D 线:信号线,通常是 %K 线的移动平均线。
计算公式如下:
%K 线 :%K=(收盘价−最低价)/(最高价−最低价)×100
其中,最高价和最低价是过去 n 个周期内的最高价和最低价。
%D 线 : %D=SMA(%K,m)
其中,SMA 表示简单移动平均,m 是移动平均的周期。
ATR
是 "Average True Range"(平均真实波幅)
ATR
是 "Average True Range"(平均真实波幅)的缩写,它是一种技术分析指标,用于衡量市场波动性。计算 ATR 的步骤如下:
-
计算每个周期的真实波幅 (True Range, TR)
TR
是以下三个值中的最大值:- 当前最高价与当前最低价之差
- 当前最高价与上一周期收盘价之差的绝对值
- 当前最低价与上一周期收盘价之差的绝对值
-
计算 ATR
ATR
是TR
的移动平均值,通常使用简单移动平均 (SMA) 或指数移动平均 (EMA)。
OBV(On-Balance Volume,平衡交易量)
OBV(On-Balance Volume,平衡交易量)是一种技术分析指标,用于衡量成交量的变化对股价的影响。OBV 的计算方法如下:
- 初始化 OBV:选择一个起始点,通常是从数据集的第一个交易日开始,将 OBV 设为 0。
- 计算每日的 OBV 变化:
- 如果当天的收盘价高于前一天的收盘价,则当天的 OBV 增加当天的成交量。
- 如果当天的收盘价低于前一天的收盘价,则当天的 OBV 减去当天的成交量。
- 如果当天的收盘价等于前一天的收盘价,则当天的 OBV 保持不变。
CCI 是“商品通道指数”(Commodity Channel Index)
CCI 是“商品通道指数”(Commodity Channel Index)的缩写,它是一种技术分析指标,用于衡量资产价格相对于其平均价格的偏离程度。CCI 通常用于识别超买和超卖条件,以及价格趋势的变化。
在 Python 中,计算 CCI 的步骤如下:
- 计算典型价格(Typical Price, TP):典型价格是每个周期的最高价、最低价和收盘价的平均值。
- 计算典型价格的简单移动平均(Simple Moving Average, SMA):通常使用 14 个周期。
- 计算每个周期的典型价格与 SMA 的偏差。
- 计算这些偏差的平均绝对偏差(Mean Absolute Deviation, MAD)。
- 计算 CCI:使用以下公式: