问题描述
解决方案
强力指数 (Force Index) 交易策略
强力指数(Force Index,简称 FI)是由亚历山大·埃尔德(Alexander Elder)博士开发的指标。它结合了价格变动和成交量来衡量市场移动背后的力量。
策略原理
强力指数的核心逻辑是:
- 价格变动表明了多空双方力量的对比。
- 成交量表明了这种力量的强度。
计算公式:
$$FI_1 = (Close_{today} - Close_{yesterday}) \times Volume_{today}$$
原始的 FI 波动非常剧烈,通常需要使用指数移动平均(EMA)进行平滑。
经典的 2 日 EMA 强力指数策略(Elder's 2-day EMA Force Index)
这是一个经典的“顺势回调买入”策略:
- 趋势判断:使用较长周期的均线(如 22 日 EMA)判断主趋势。
- 当收盘价 > 22日 EMA,视为上升趋势。
- 当收盘价 < 22日 EMA,视为下降趋势。
- 强力指数:计算 FI 的 2 日 EMA。
- 买入信号:
- 趋势向上(收盘价 > 22日 EMA)。
- FI(2) < 0(表示主力资金在洗盘或短期回调,是买入良机)。
- 卖出信号:
- 趋势反转(收盘价 < 22日 EMA)。
- 或者当 FI(2) 创出新高后回落(止盈,本策略演示简化为趋势反转卖出)。
策略代码实现
以下是在 JoinQuant 平台上实现的完整策略代码。该策略选取单只标的(如平安银行)进行演示,您可以根据需要修改为多只股票。
# -*- coding: utf-8 -*-
import pandas as pd
import numpy as np
from jqdata import *
def initialize(context):
"""
初始化函数,设定基准、手续费、滑点及策略参数
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定手续费:股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定要操作的股票,这里以平安银行为例
g.security = '000001.XSHE'
# 策略参数
g.trend_ema_span = 22 # 用于判断长期趋势的EMA周期 (如22日)
g.fi_ema_span = 2 # 强力指数的平滑EMA周期 (通常为2日或13日)
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
"""
每日交易逻辑
"""
security = g.security
# 1. 获取历史数据
# 我们需要足够的数据来计算EMA,这里取60天以确保EMA计算稳定
# fields包含收盘价和成交量
h_data = attribute_history(security, 60, '1d', ['close', 'volume'], skip_paused=True)
# 如果数据不足(例如刚上市),则跳过
if len(h_data) < g.trend_ema_span + 2:
return
# 2. 计算趋势指标 (22日 EMA)
# 使用pandas的ewm方法计算指数移动平均
close_prices = h_data['close']
trend_ema = close_prices.ewm(span=g.trend_ema_span, adjust=False).mean()
current_trend_ema = trend_ema[-1]
current_price = close_prices[-1]
# 3. 计算强力指数 (Force Index)
# FI = (今日收盘 - 昨日收盘) * 今日成交量
# diff() 计算今日与昨日的差价
price_diff = close_prices.diff()
# 计算原始 Force Index
raw_fi = price_diff * h_data['volume']
# 对 Force Index 进行 EMA 平滑 (2日 EMA)
fi_ema_series = raw_fi.ewm(span=g.fi_ema_span, adjust=False).mean()
current_fi = fi_ema_series[-1]
# 4. 获取当前账户资金和持仓
cash = context.portfolio.available_cash
position = context.portfolio.positions[security].closeable_amount
# 5. 生成交易信号
# 判断趋势:收盘价在22日均线之上为上升趋势
is_uptrend = current_price > current_trend_ema
# 买入条件:
# 1. 处于上升趋势 (Price > EMA22)
# 2. 强力指数小于0 (FI < 0),代表短期回调,是买入机会
# 3. 当前无持仓 (简化逻辑,避免重复加仓)
if is_uptrend and current_fi < 0 and position == 0:
if cash > 0:
# 全仓买入
order_value(security, cash)
log.info("买入信号触发: 价格(%.2f) > 趋势均线(%.2f) 且 FI(%.2f) < 0" % (current_price, current_trend_ema, current_fi))
# 卖出条件:
# 1. 趋势反转 (Price < EMA22)
# 2. 当前有持仓
elif not is_uptrend and position > 0:
# 清仓
order_target(security, 0)
log.info("卖出信号触发: 趋势反转,价格(%.2f) < 趋势均线(%.2f)" % (current_price, current_trend_ema))
# 记录数据以便在回测图中查看
record(Price=current_price, Trend_EMA=current_trend_ema, FI=current_fi)
代码解析
-
数据获取 (
attribute_history):- 我们需要
close(收盘价) 和volume(成交量)。 - 获取 60 天的数据是为了消除 EMA 计算初期的不稳定,确保计算结果准确。
- 我们需要
-
计算趋势 EMA:
- 使用
pandas.Series.ewm(span=22, adjust=False).mean()计算价格的 22 日指数移动平均线。这是用来界定主趋势的“过滤器”。
- 使用
-
计算强力指数 (FI):
price_diff = close_prices.diff():计算 $Close_{today} - Close_{yesterday}$。raw_fi = price_diff * h_data['volume']:计算原始强力指数。fi_ema_series = raw_fi.ewm(span=2, ...):对原始 FI 进行 2 日平滑处理,得到最终指标。
-
交易逻辑:
- 买入: 只有当价格位于长期均线之上(上升趋势),且 FI 指标跌破 0 轴(表示短期卖压释放,价格回调)时买入。这符合“顺大势,逆小势”的原则。
- 卖出: 当价格跌破长期均线时,认为趋势结束,进行止损或止盈。
常见问题 (Q&A)
Q1: 为什么强力指数要乘以成交量?
A: 单纯的价格变化可能具有欺骗性。如果价格上涨但成交量很小,说明上涨动力不足(量价背离)。强力指数通过引入成交量,放大了伴随巨量成交的价格变动,过滤了无量波动的噪音。
Q2: 2日 EMA 和 13日 EMA 的 FI 有什么区别?
A:
- 2日 EMA FI:非常敏感,主要用于寻找上升趋势中的短期回调买点(即本策略使用的逻辑)。
- 13日 EMA FI:相对平滑,主要用于确认长期趋势的变化。如果 13日 FI 持续大于0,说明多头力量主导;持续小于0,说明空头力量主导。
Q3: 如何优化这个策略?
A:
- 止盈优化:目前的策略是趋势反转才卖出。可以优化为当 FI 达到某个极高的正值(表示买盘力量耗尽,情绪过热)时分批止盈。
- 多股票轮动:将策略应用到股票池中,选取 FI 最低(回调最深)且趋势向上的前 N 只股票进行轮动。
- 结合其他指标:结合 RSI 或 MACD 来过滤假信号。
Q4: 为什么使用 adjust=False 计算 EMA?
A: 在 Pandas 中,adjust=True 会计算递归的加权平均,而 adjust=False 更接近于传统的金融技术指标计算公式 $EMA_{today} = \alpha \times Price + (1-\alpha) \times EMA_{yesterday}$。在量化软件中通常使用 adjust=False 以匹配主流看盘软件的结果。