在市场中,经常存在交易性机会,这是指股价在短期内可能受到某些消息的影响,或者某些市场内在因素的改变从而产生剧烈波动带来的价差投资机会。其中一个经典的交易性策略就是资金流模型,该模型使用资金流流向来判断股票在未来一段时间的涨跌情况。如果是资金流入的股票,则股价在未来一段时间可能会上涨;如果是资金流出的股票,则股价在未来一段时间可能会下跌。那么,根据资金流向就可以构建相应的投资策略。
1、基本概念
资金流是一种反映股票供求关系的指标。传统的量价无法区分市场微观结构中的流动性和私有信息的影响,而根据委托测算的资金流,能够有效地观察微观市场交易者的真实意图及对股价造成的影响。
资金流定义如下:证券价格在约定的时间段中处于上升状态时产生的交易额是推动指数上涨的力量,这部分成交额被定义为资金流入;证券价格在约定的时间段中下跌时的成交额是推动指数下跌的力量,这部分成交额被定义为资金流出;若证券价格在约定的时间段前后没有发生变化,则这段时间中的成交额不计入资金流量。当天资金流入和资金流出的差额可以认为是该证券当天买卖两者力量相抵之后,推动价格变化的净作用量,被定义为当天资金净流量。
数量化定义如下:
M
o
n
e
y
F
l
o
w
=
∑
i
=
1
n
(
V
o
l
u
m
e
i
)
×
P
i
P
i
−
P
i
−
1
∣
P
i
−
P
i
−
1
∣
MoneyFlow=\sum_{i=1}^n(Volume \ i)\times P_i\frac{P_i-P_{i-1}}{|{P_i-P_{i-1}}|}
MoneyFlow=i=1∑n(Volume i)×Pi∣Pi−Pi−1∣Pi−Pi−1
其中
V
o
l
u
m
e
Volume
Volume为成交量,
P
i
P_i
Pi为
i
i
i时刻收盘价,
P
i
−
1
P_{i-1}
Pi−1为上一时刻收盘价。
严格意义上讲,每一个买单必须有一个相应的卖单,任何证券当日的买入金额总等于卖出金额,因此资金净流量并不表示当日真正新买进证券的资金量,而仅表示当日推升或压低股票价格的买卖力量对比。
2、资金流测算方法
测算资金流的方法很多,主要区别一是在时间频率上,二是在参考价格上。
招商证券的研究员开发了一款CMSMF指标,采用高频数据进行资金流测算,主要出于以下两方面考虑:一是采用高频数据进行测算,可以尽可能反映真实的市场信息;二是采取报价(最近买价、卖价)作为比较基准,成交价大于等于上期最优卖价视为流入,成交价小于等于上期最优买价视为流出。
3、策略模型
逆向选择理论
在非强势有效的A股市场,普遍存在信息不对称的问题。机构投资者与散户投资者在对同一信息的评估能力上存在差异。在大部分情况下,散户投资者缺乏专业的投资能力和精力,那么根据“搭便车”理论,希望借助机构投资者对股价的判断进行投资,一旦机构投资者率先对潜在市场信息做出反应,羊群效应的散户投资者则追涨杀跌,往往导致在很多情况下市场对潜在信息反应过度。这样根据逆向选择理论,能够准确评估信息价值的投资者便会对反应过度的股价做出交易,买入低估的、卖出高估的股票,从而纠正这种信息反应过度行为。
根据市场对潜在信息反应过度的结论及市场投资者的行为特征,可以采取逆向选择模型理论来构建选股模型,即卖出前期资金流入、价格上涨的股票,买入前期资金流出、价格下跌的股票。按照这个思路,对一些指标参数进行回测分析,可以得到稳定的选股模型。
策略模型
根据资金流各个指标的特点,在选股模型中采用比较简单的方法,即以指标排序打分的方式来筛选股票,首先通过对各个资金流指标进行排序打分,然后将股票各个指标的得分进行求和,最后以总得分值大小来筛选股票。具体步骤如下:
(1)确定待选股票池,在选择组合构建时,剔除上市不满一个月的股票;剔除调仓期涨跌停及停牌的股票,防止因涨跌停无法交易;剔除信息含量小于10%的股票,因为这部分股票信号不明显,无法取得有效信息。
(2)构建股票组合。
- 指标打分:首先将待选股票池中的股票按照各个指标进行排序(指标即为前面介绍的GSMS和CMSMF指标),然后采用百分制整数打分法进行指标打分,即以股票在各个指标中所处位置的百分数作为股票对于该指标的得分,前1%得分为1,依次递减,最后1%得分为100%。
- 求和排序:将股票相对于各个指标的得分进行求和,将和值从小到大排序,进行分组比较;另外,选择排名靠前的N只股票构建组合。
- 股票权重:采用等量权重
(3)组合定期调整,调整时间为1~3个月不等,持有到期后,利用更新后的指标数据重新确定待选股票池,重复步骤(2)的打分求和过程,并将股票按照指标得分从小到大排序,将原来分组中跌出组合的股票剔除,调进新的股票,同时将新组合内样本股的权重调整到相等。
(4)统计检验,分布计算各组合的收益率情况,考察组合效果。
4、实战应用
以上为书本上介绍的资金流模型及使用方法,但是在实际的量化编程中并不好用,首先就是CMSMF这个指标,我在网上基本搜不到相关详细的策略模型,而且关于资金流量这个数据也很难获取,所有我在这里找了一个其他的指标作为替代:MFI指标
(1)MFI指标含义
MFI指标(Money Flow Index):也可以叫资金流量指标,1989年3月由JWellesWilder's首次发表MFI指标的用法。MFI指标实际是将RSI加以修改后,演变而来。RSI以成交价为计算基础;MFI指标则结合价和量,将其列入综合考虑的范围。可以说,MFI指标是成交量的RSI指标。
MFI又称为资金流向指标,其指标源码如下:
典型价格:
T
Y
P
=
H
I
G
H
+
L
O
W
+
C
L
O
S
E
3
;
典型价格:TYP=\frac{HIGH+LOW+CLOSE}{3};
典型价格:TYP=3HIGH+LOW+CLOSE;
V
1
:
=
∑
i
=
1
n
(
I
F
(
T
Y
P
>
R
E
F
(
T
Y
P
,
1
)
,
T
Y
P
∗
V
O
L
,
0
)
)
∑
i
=
1
n
(
I
F
(
T
Y
P
<
R
E
F
(
T
Y
P
,
1
)
,
T
Y
P
∗
V
O
L
,
0
)
)
V_1:=\frac{\sum_{i=1}^n(IF(TYP>REF(TYP,1),TYP*VOL,0))}{\sum_{i=1}^n(IF(TYP<REF(TYP,1),TYP*VOL,0))}
V1:=∑i=1n(IF(TYP<REF(TYP,1),TYP∗VOL,0))∑i=1n(IF(TYP>REF(TYP,1),TYP∗VOL,0))
M
F
I
=
100
−
100
1
+
V
1
;
MFI=100-\frac{100}{1+V_1};
MFI=100−1+V1100;
具体是说:
1.先计算一定期限内(一般14天)每天的典型价格(即TYP),它是当天最高价,最低价和收盘价三者的均值。也有给收盘价更大权值再算三者均值的算法。
2.如果当天的典型价格大于昨天的则定义为流入,反之为流出,流入流出金额为典型价格乘以当天交易量。这样把14天每天结果计算出来,然后再把流出额和流入额分别加总,得到14天内的流入总额和流出总额,接着前者除以后者,大于1则14天内的资金为流入,反之为流出。V1就是代表这个比值。
3.MFI就是在V1的基础上,为了更好地在坐标上显示出来,进行的数据处理。
(2)MFI买入信号
1.MFI<20时,代表资金短期冷却讯号.但是,必须等待MFI指标再度向上突破20时,才能确认资金转向.
2.MFI在20左右的水平,出现一底比一底高,和股价“背离”的现象时,可视为中期反转上涨的讯号.
3.MFI指标连续二次向上交叉其平均线时,视为买进讯号.(平均线一般设定为6天).
(3)MFI卖出信号
1.MFI>80时,代表资金短期过热讯号.但是,必须等待MFI指标再度向下跌破80时,才能确认资金转向.
2.MFI在80左右的水平,出现一顶比一顶低,和股价“背离”的现象时,可视为中期反转下跌的讯号.
3.MFI指标连续两次向下交叉其平均线时,视为卖出讯号.(平均线一般设定为6天).
(4)使用技巧
MFI指标的“背离”讯号,比RSI指标的“背离”讯号,更能忠实的反应股价的反转现象.一次完整的波段行情,至少都会维持一定相当的时间,反转点出现的次数并不会太多.如果指标出现反转讯号的次数太频繁,发生假讯号的可能性必然增加.基于此指标参数的周期,最好不要设得太短,以免产生指标陷阱过多的困扰.
将MFI指标的参数设定为14天时,其背离讯号产生的时机,大致上都能和股价的波段高低点吻合.因此,实际使用MFI指标时,在参数设定方面,应尽量维持14天的原则.
理论上,价涨量增及价跌量缩是一种惯性作用.股价进行波段涨升时,成交量必须伴随上升.MFI指标爬升至80以上时,代表短期内资金有消耗过量的疑虑,但是,这只是一种警告而已,未来必须视MFI指标是否持续下降,才能确认资金已经退潮.当然,一个资金已经退潮的行情,不仅不利于股价的推升,更容易造成股价回档.相反的,当MFI指标下降至20以下的水平时,代表短期内资金已达冷却的效果.但是,虽然股价经常在资金冷却至一定极限后开始弹升.不过,也有可能因为市场情绪过度沮丧的原故,造成股价变成一滩死水,形成在底部区盘整的局面.
因此,当MFI指标到达资金超买的状况时,不一定需要立刻做出反应,等待资金再稍微退潮一点,确认能量已经消失时,再执行卖出的动作不迟.
底部区的资金状况与头部区不同.股价的涨升虽然必须伴随成交量.但是,底部区成交量的扩增,却不一定能立刻促使股价上涨.因此,MFI指标在底部区的讯号表现会比较迟缓.应等待MFI指标,形成一底比一底高的“背离”走势,再确认买进的动作.注意MFI指标确认反转的讯号,主要运用的头部区/底部区的反转确认,可靠性较低。
(5)采用talib数据库获取股票的MFI指标
在实际的使用过程中,我们可以采用talib金融数据库中的MFI函数,直接得到对应股票的MFI指标。该函数的输入参数为:最高价、最低价、收盘价以及成交量。
以海康威视为例,我们获取一下从2024年5月31日前100个交易日的数据构建MFI曲线,结果如下:
from gm.api import *
import talib
import numpy as np
import matplotlib.pyplot as plt
"""
### MFI - Money Flow Index 资金流量指标
> 函数名:MFI
名称:资金流量指标
简介:属于量价类指标,反映市场的运行趋势
分析和应用:[百度百科](https://baike.baidu.com/item/mfi/7429225?fr=aladdin)
[同花顺学院](http://www.iwencai.com/school/search?cg=100&w=MFI)
NOTE: The ``MFI`` function has an unstable period.
python
real = MFI(high, low, close, volume, timeperiod=14)
"""
# 与数据库进行通信,将账号密码加密后发送给服务器
set_token("自己的token码")
timeperiod=14
# 获取历史交易数据
data = history_n(symbol="SZSE.002415",frequency="1d",count=100,end_time="2024-05-31",fields="high, low, close, volume",fill_missing="last",adjust=ADJUST_PREV,df=True)
# 从dataframe中提取数据
high = data["high"].values
low = data["low"].values
close = data["close"].values
# ta-lib是一个金融技术分析库,它要求所有输入数据都是双精度浮点型(double)
volume = np.array(data["volume"].values, dtype=np.double)
MFI_index = talib.MFI(high, low, close, volume, timeperiod=timeperiod)
MFI_index = np.nan_to_num(MFI_index)
MFI_index = MFI_index[timeperiod:] #前timeperiod都为0,因此需要剔除
print(MFI_index)
#绘制MFI曲线
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title("MFI")
ax.set_xlabel("count")
ax.set_ylabel("MFI Date")
ax.plot(MFI_index,"b")
close = close[timeperiod:] #需要和MFI_INDEX保持一致
close_guiyi = (close-min(close))/(max(close)-min(close))*100 #归一化
ax.plot(close_guiyi,"r")
plt.show()
#求MFI和收盘价之间的相关系数
corr =np.corrcoef(-close,MFI_index)
print(corr[0][1])
得到的结果如下,可以看出股价的运动趋势基本和MFI的趋势相同;相关系数为0.5624,为正相关。
根据MFI制定买卖策略
(1) 股票的资源池,选择沪深300的指数成分股作为股票资源池
(2)指标打分:将股票池中的股票按照MFI指标进行从小到大排序,选择排名靠前的N只股票构建组合。
(3)股票权重:采用等量权重
(4)组合定期调整,调整时间为1~3个月不等,持有到期后,利用更新后的指标数据重新确定待选股票池,重复步骤(2)将股票按照指标得分从小到大排序,将原来分组中跌出组合的股票剔除,调进新的股票,同时将新组合内样本股的权重调整到相等。
(4)统计检验,分布计算各组合的收益率情况,考察组合效果。
具体实现代码如下:
建立一个py文件存储函数,文件名:MFI_strategy.py
from gm.api import *
import talib
import numpy as np
"""
### MFI - Money Flow Index 资金流量指标
> 函数名:MFI
名称:资金流量指标
简介:属于量价类指标,反映市场的运行趋势
分析和应用:[百度百科](https://baike.baidu.com/item/mfi/7429225?fr=aladdin)
[同花顺学院](http://www.iwencai.com/school/search?cg=100&w=MFI)
NOTE: The ``MFI`` function has an unstable period.
python
real = MFI(high, low, close, volume, timeperiod=14)
"""
def MFI_strategy(index,time,MFI_MIN=20,MFI_MAX=80,timeperiod=14,hold_num = 6):
A_share_main = {"600", "601", "602", "603", "000", "001", "002", "003"} # 沪深主板股票代码的开头
symbols_data = stk_get_index_constituents(index, trade_date=time)
symbol_list = symbols_data[['index', 'symbol']] # 新建一个dataframe表格用于存储需要用到的数据
symbol_list.insert(len(symbol_list.columns), 'MFI', '') # 在指定位置添加空白列,存储MFI值
for row in symbol_list.index:
# print(symbol_list.loc[row]['symbol'])
data = history_n(symbol=symbol_list.loc[row]['symbol'], frequency="1d", count=16, end_time=time,
fields="high, low, close, volume", fill_missing="last", adjust=ADJUST_PREV, df=True)
# 从dataframe中提取数据
high = data["high"].values
low = data["low"].values
close = data["close"].values
# ta-lib是一个金融技术分析库,它要求所有输入数据都是双精度浮点型(double)
volume = np.array(data["volume"].values, dtype=np.double)
MFI_index = talib.MFI(high, low, close, volume, timeperiod=timeperiod)
MFI_index = np.nan_to_num(MFI_index)
MFI_index = MFI_index[timeperiod:] #前timeperiod都为0,因此需要剔除
symbol_list.at[row, 'MFI'] = MFI_index[-1] #将最新的MFI值存储到dataframe中
MFI_symbol_list = symbol_list.sort_values("MFI", ascending=True) # 根据选择的因子对股票进行从小到大排序
MFI_symbol_list = MFI_symbol_list.dropna() # 删除无效数据
MFI_symbol_list = MFI_symbol_list.reset_index(drop=True) # 重置索引值
buy_list = MFI_symbol_list
print(buy_list)
# 对列表中的股票进行再一次的筛选,选择MFI值小于MFI_MIN的加入到购买股票清单中
for i in range(0, len(buy_list)):
if buy_list.at[i, 'MFI'] >= MFI_MIN or buy_list.at[i, 'symbol'][5:8] not in A_share_main: # 对MFI值大于阈值或者非主板股票进行舍弃
buy_list = buy_list.drop([i])
if len(buy_list) >= hold_num: # 如果最终股票清单数量大于要买入的股票数,则提取前n个股票进行买入
target_list = buy_list["symbol"].values # 获得排序后的股票代码清单
target_symbol_top = target_list[:hold_num] # 取列表中前n只作为标的股票
else: # 如果股票清单数小于要购买的股票数,则有多少算多少,都列入股票清单
target_symbol_top = buy_list["symbol"].values # 获得排序后的股票代码清单
print(buy_list)
print(target_symbol_top)
return target_symbol_top
之后再建立一个py文件建立买卖策略,文件名:MFI_strategy_test.py
# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *
import datetime
import numpy as np
import talib
import pandas as pd
import MFI_strategy
'''
示例策略仅供参考,不建议直接实盘使用。
行业轮动策略
逻辑:以场内ETF基金代表,按照月收益率进行排序,选择收益率最高的10只ETF基金进行买入,每月进行一次调仓换股
'''
def init(context):
# 用于统计数据的天数
context.timeperiod = 14
#股票池指数
# context.index = "SHSE.000922" # 中证红利指数
context.index = "SHSE.000300" # 沪深300指数
#买入股票的MFI阈值
context.MFI_MIN = 20
#卖出股票的MFI阈值
context.MFI_MAX = 80
# 持股数量
context.hold_num = 6
# 每日定时任务
schedule(schedule_func=algo, date_rule='1m', time_rule='09:30:00')
# schedule(schedule_func=algo_zhiyingzhisun, date_rule='1d', time_rule='09:30:00') #根据盈利亏损值止盈止损
schedule(schedule_func=algo_MFIzhisun, date_rule='1d', time_rule='09:30:00') #根据MFI值止盈止损
def algo(context):
# 当天日期
now_str = context.now.strftime('%Y-%m-%d')
# 获取上一个交易日及上一周交易日
print(now_str + "执行策略1")
last_day = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]
target_symbol_top = MFI_strategy.MFI_strategy(index=context.index, time=last_day, MFI_MIN=context.MFI_MIN, MFI_MAX=context.MFI_MAX, timeperiod=context.timeperiod)
# (如果收益率大于0,再执行买入操作),收益率小于0,直接清仓
if len(target_symbol_top) > 0:
# 获取购买股票清单
to_buy = list(target_symbol_top)
# 计算权重
percent = 1.0 / len(to_buy)
# 获取当前所有仓位
positions = get_position()
# 平不在标的池的股票(注:本策略交易以开盘价为交易价格,当调整定时任务时间时,需调整对应价格)
for position in positions:
symbol = position['symbol']
if symbol not in to_buy:
# 开盘价(日频数据)
new_price = \
history_n(symbol=symbol, frequency='1d', count=1, end_time=now_str, fields='open', adjust=ADJUST_PREV,
adjust_end_time=context.backtest_end_time, df=False)[0]['open']
# # 当前价(tick数据,免费版本有时间权限限制;实时模式,返回当前最新 tick 数据,回测模式,返回回测当前时间点的最近一分钟的收盘价)
# new_price = current(symbols=symbol)[0]['price']
order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit,
position_side=PositionSide_Long, price=new_price)
# 买入标的池中的股票(注:本策略交易以开盘价为交易价格,当调整定时任务时间时,需调整对应价格)
for symbol in to_buy:
# 开盘价(日频数据)
new_price = \
history_n(symbol=symbol, frequency='1d', count=1, end_time=now_str, fields='open', adjust=ADJUST_PREV,
adjust_end_time=context.backtest_end_time, df=False)[0]['open']
# # 当前价(tick数据,免费版本有时间权限限制;实时模式,返回当前最新 tick 数据,回测模式,返回回测当前时间点的最近一分钟的收盘价)
# new_price = current(symbols=symbol)[0]['price']
order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Limit,
position_side=PositionSide_Long, price=new_price)
else:
order_close_all() # 全部清仓上一阶段股票
print("没有股票可以买入")
def algo_zhiyingzhisun(context):
now_str = context.now.strftime('%Y-%m-%d')
print(now_str+"执行策略2")
positions = get_position()
for position in positions:
symbol = position['symbol']
price = position["price"] #当前行情价格
vwap = position["vwap"] #持仓均价
# print(position)
if (price - vwap)/vwap < -0.1: #当亏损大于10%,止损
order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
position_side=PositionSide_Long)
print(symbol+"止损"+now_str)
elif (price - vwap)/vwap > 0.4: #当盈利大于30%,止盈
order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
position_side=PositionSide_Long)
print(symbol+"止盈"+now_str)
def algo_MFIzhisun(context):
now_str = context.now.strftime('%Y-%m-%d')
print(now_str+"执行策略3")
positions = get_position()
for position in positions:
symbol = position['symbol']
data = history_n(symbol=symbol, frequency="1d", count=16, end_time=now_str,
fields="high, low, close, volume", fill_missing="last", adjust=ADJUST_PREV, df=True)
# 从dataframe中提取数据
high = data["high"].values
low = data["low"].values
close = data["close"].values
# ta-lib是一个金融技术分析库,它要求所有输入数据都是双精度浮点型(double)
volume = np.array(data["volume"].values, dtype=np.double)
MFI_index = talib.MFI(high, low, close, volume, timeperiod=context.timeperiod)
MFI_index = np.nan_to_num(MFI_index)
MFI_index = MFI_index[context.timeperiod:] #前timeperiod都为0,因此需要剔除
MFI_COUNT = MFI_index[-1]
if MFI_COUNT > context.MFI_MAX:
order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
position_side=PositionSide_Long)
def on_order_status(context, order):
# 标的代码
symbol = order['symbol']
# 委托价格
price = order['price']
# 委托数量
volume = order['volume']
# 目标仓位
target_percent = order['target_percent']
# 查看下单后的委托状态,等于3代表委托全部成交
status = order['status']
# 买卖方向,1为买入,2为卖出
side = order['side']
# 开平仓类型,1为开仓,2为平仓
effect = order['position_effect']
# 委托类型,1为限价委托,2为市价委托
order_type = order['order_type']
if status == 3:
if effect == 1:
if side == 1:
side_effect = '开多仓'
else:
side_effect = '开空仓'
else:
if side == 1:
side_effect = '平空仓'
else:
side_effect = '平多仓'
order_type_word = '限价' if order_type == 1 else '市价'
print('{}:标的:{},操作:以{}{},委托价格:{},委托数量:{}'.format(context.now, symbol, order_type_word, side_effect,
price, volume))
def on_backtest_finished(context, indicator):
print('*' * 50)
print('回测已完成,请通过右上角“回测历史”功能查询详情。')
if __name__ == '__main__':
'''
strategy_id策略ID,由系统生成
filename文件名,请与本文件名保持一致
mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
token绑定计算机的ID,可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
backtest_match_mode市价撮合模式,以下一tick/bar开盘价撮合:0,以当前tick/bar收盘价撮合:1
'''
run(strategy_id='自己的策略ID',
filename='MFI_strategy_test.py',
mode=MODE_BACKTEST,
token='自己的token码',
backtest_start_time='2021-03-01 09:00:00',
backtest_end_time='2023-03-01 15:00:00',
backtest_adjust=ADJUST_PREV,
backtest_initial_cash=1000000,
backtest_commission_ratio=0.0001,
backtest_slippage_ratio=0.0001,
backtest_match_mode=1)
回测结果如下,超额收益58%,回撤20%,算是一个比较不错的结果