0%

signal模式入门

bt信号交易

大多数策略开发,本就是基于信号的,bt直接提供了基于信号的回测,且灵活度较高,可以极大的提高策略研发效率。
基础入门可参考这篇文章:https://mp.weixin.qq.com/s?__biz=MzAxNTc0Mjg0Mg==&mid=2653317634&idx=1&sn=e92fec0b0b5fd5f62805e7c2be5830f8
官方文档参考demo:https://www.backtrader.com/docu/signal_strategy/signal_strategy/

信号交易5种模式差异

add_signal(signal type, signal class, arg) 中的参数说明:
第 1 个参数:信号类型
分为 2 大类,共计 5 种信号类型:

开仓类

bt.SIGNAL_LONGSHORT:
多头信号和空头信号都会作为开仓信号;
对于多头信号,如果之前有空头仓位,会先对空仓进行平仓 close,再开多仓;
空头信号也类似,会在开空仓前对多仓进行平仓 close。

bt.SIGNAL_LONG:
多头信号用于做多,空头信号用于平仓 close;
如果系统中同时存在 LONGEXIT 信号类型,SIGNAL_LONG 中的空头信号将不起作用,将会使用 LONGEXIT 中的空头信号来平仓多头,如上面的多条交易信号的例子。
bt.SIGNAL_SHORT:
空头信号用于做空,多头信号用于平仓;
如果系统中同时存在 SHORTEXIT 信号类型,SIGNAL_SHORT 中的多头信号将不起作用,将会使用 SHORTEXIT 中的多头信号来平仓空头。

这部分该如何理解?
信号理解为发号施令的领导,而具体action取决于负责执行的人。
对于SIGNAL_LONGSHORT,可以看做负责执行的人充分实施领导命令
对于SIGNAL_LONG,可以看做负责执行的人,选择性执行领导的开多仓和平多仓,而忽略领导的开空仓(和平空仓)。
对于SIGNAL_SHORT,可以看做负责执行的人,选择性执行领导的开空仓和平空仓,而忽略领导的开多仓(和平多仓)。
“选择性执行”,也可看做市场允许的交易类型, 比如A股股票市场,不允许做空。还有就是比如现在长期均线朝上,或者月均线高于年均线(上涨趋势为主),此时也不应该做空,只能选择做多或空仓

平仓类

bt.SIGNAL_LONGEXIT:接收空头信号平仓多头;
bt.SIGNAL_SHORTEXIT:接收多头信号平仓空头;
上述 2 种信号类型主要用于确定平仓信号,在下达平仓指令时,优先级高于上面开仓类中的信号。
第 2 个参数:定义的信号指标类的名称,比如案例中的 SMACloseSignal 类 和 SMAExitSignal 类,直接传入类即可,不需要将类进行实例化;
第 3 个参数:对应信号指标类中的参数 params,直接通过 period=xxx 、p1=xxx, p2=xxx 形式修改参数取值。

这部分又该如何理解?
首先为何需要这个平仓信号,思考一个完整行情周期,
01,市场长期底部横盘,开始启动,连续突破最近1月,2月最高点,开仓信号。
02,市场持续上涨,但是速度(斜率)偏弱,涨幅小且回调频繁,虽然上涨依然大概率,但是概率较启动时小了,且积累了较大涨幅了,平多仓,止盈。
03,市场由于过分上涨,开始回调,长期看空,开空仓。
04,空仓预期释放,短期依然空,长期偏多,平空仓。
由于买卖信号并不完全对称。有些强力偏多的信号,则买入多。一旦强力多信号减弱到一定程度就要平多了。而此时,整体依然是多面大,只是为了避免波动,远离市场而已,而空头同样道理。假设市场合理价值为0。波动区间[-100,100],那么位于-80以及以下时,应当买多,而涨到-20时应当平多,市场在80以上,应该买空,而跌到20时,应该平空。所以买多和平多,与买空和平空,4个信号本来就是独立的(分别对应不同分值)。而非直观以为的,平多同时应该开空,因为信号不强烈时,极有可能长期波动横盘,导致资金效率低下且需承受不可避免的波动

疑点解析

信号只识别正负,不区分True或False

比如:ma1在ma2上,开多仓,否则空仓。则是signal=ma1-ma2,不能采用signal=ma1>ma2

开仓类信号只负责买入,平仓类只负责卖出?

CASE01:SIGNAL_LONG + SIGNAL_LONGEXIT
开仓:SIGNAL_LONG
平仓:SIGNAL_LONGEXIT(SIGNAL_LONG +/- 5day)
信号特征:非正即负
信号源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SMACloseSignal(bt.Indicator):
lines = ('signal',)
params = (('period', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=10)
sma2 = bt.indicators.SMA(period=30)
self.lines.signal = sma1 - sma2


class SMAExitSignal(bt.Indicator):
lines = ('signal',)
params = (('p1', 5), ('p2', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=10)
sma2 = bt.indicators.SMA(period=30)
self.lines.signal = (sma1 - sma2)(-5)

python ./signal_macd.py --plot --signal longonly --exitsignal longexit --fromdate 2013-01-10 --todate 2014-01-01

曲线图:
del02
解释:
竖线1:买入原因SMACloseSignal > 0
竖线2:卖出原因SMAExitSignal < 0
结论:
无持仓,盯着开仓信号线,开仓信号>0则买入(不管平仓信号状态),
**有持仓,盯着平仓信号线,平仓信号<0则平仓**(不管开仓信号状态)
图中,频繁开仓-平仓的锯齿部分(标记3),就是开仓信号线>0 但 平仓信号线<0,导致刚买入即卖出。

再次验证另一种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SMACloseSignal(bt.Indicator):
lines = ('signal',)
params = (('period', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=10)
sma2 = bt.indicators.SMA(period=30)
self.lines.signal = (sma1 - sma2)(-5)


class SMAExitSignal(bt.Indicator):
lines = ('signal',)
params = (('p1', 5), ('p2', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=10)
sma2 = bt.indicators.SMA(period=30)
self.lines.signal = (sma1 - sma2)
python ./signal_macd.py --plot --signal longonly --exitsignal longexit --fromdate 2013-01-10 --todate 2014-01-01

曲线图
del02
区域尾部的折线意味着开仓信号 > 0,平仓信号 < 0,会导致持续的开仓-平仓.
可见,上述结论依旧成立。

CASE02:SIGNAL_LONG + SIGNAL_SHORTEXIT
修复源码中一个bug再测试

1
2
3
4
5
6
7

EXITSIGNALS = {
'longexit': bt.SIGNAL_LONGEXIT,
'shortexit': bt.SIGNAL_SHORTEXIT,
}

python ./signal_macd.py --plot --signal longonly --exitsignal shortexit --fromdate 2013-01-10 --todate 2014-01-01

结果图:
del02
可见:SMAExitSignal对仓位无影响,全凭SMACloseSignal决定。这个也符合逻辑,根据其定义,SIGNAL_SHORTEXIT接收多头信号平仓空头;由于单向做多,所以不存在空头仓位,所以属于无意义参数。

CASE03:SIGNAL_LONGSHORT+SIGNAL_LONGEXIT+SIGNAL_SHORTEXIT
开仓:SIGNAL_LONGSHORT
平仓:SIGNAL_LONGEXIT + SIGNAL_SHORTEXIT

代码调整:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class SMACloseSignal(bt.Indicator):
lines = ('signal',)
params = (('period', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=10)
sma2 = bt.indicators.SMA(period=30)
self.lines.signal = bt.If((sma1 - sma2)>0,1,-1)


class SMAExitSignal(bt.Indicator):
lines = ('signal',)
params = (('p1', 5), ('p2', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=10)
sma2 = bt.indicators.SMA(period=30)
self.lines.signal = bt.If((sma1 - sma2)>0,1,-1)(-5)

if args.exitsignal is not None:
cerebro.add_signal(bt.SIGNAL_LONGEXIT,
SMAExitSignal,
p1=args.exitperiod,
p2=args.smaperiod)
cerebro.add_signal(bt.SIGNAL_SHORTEXIT,
SMAExitSignal,
p1=args.exitperiod,
p2=args.smaperiod)

python ./signal_macd.py --plot --signal longshort --exitsignal shortexit --fromdate 2013-01-10 --todate 2014-01-01

结果
del02
分析:
竖线1区间:持有空头,原因:起点SMACloseSignal为负数,持有空头,由于SIGNAL_SHORTEXIT信号为负数,所以持续空头持仓
竖线2区间:SMACloseSignal负转正,触发持仓从空头变多头,变成多头后,由于SIGNAL_LONGEXIT信号依然为负,所以马上平仓,平仓后由于SMACloseSignal为正,会再次买入多头持仓,如此持续反复多次。
竖线3区间:SMACloseSignal稳定正,且SIGNAL_LONGEXIT信号也维持正,所以稳定持有多头
竖线4区间:SMACloseSignal正转负,引发空头持仓,但是SIGNAL_SHORTEXIT为正,所以马上清空空头持仓,清空后由于SMACloseSignal为负,会再次买入空头持仓,由于如此持续反复多次。
综上所述:
如果仓位为空:看信号SIGNAL_LONGSHORT,SMACloseSignal
如果仓位为多:看信号SIGNAL_LONGEXIT,决定是平多(归0)
如果仓位为空:看信号SIGNAL_SHORTEXIT,决定是平空(归0)
除此之外,SIGNAL_LONGSHORT的转向,依然会导致仓位切换,而忽略SIGNAL_LONGEXIT和SIGNAL_SHORTEXIT的屏蔽效应。

CASE03:SIGNAL_LONGSHORT+SIGNAL_LONGEXIT
开仓:SIGNAL_LONGSHORT
平仓:SIGNAL_LONGEXIT
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SMACloseSignal(bt.Indicator):
lines = ('signal',)
params = (('period', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=10)
sma2 = bt.indicators.SMA(period=30)
self.lines.signal = bt.If((sma1 - sma2)>0,1,-1)


class SMAExitSignal(bt.Indicator):
lines = ('signal',)
params = (('p1', 5), ('p2', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=10)
sma2 = bt.indicators.SMA(period=30)
self.lines.signal = bt.If((sma1 - sma2)>0,1,-1)(-5)

python ./signal_macd.py --plot --signal longshort --exitsignal longexit --fromdate 2013-01-10 --todate 2014-01-01

结果图:
del02
可见:SIGNAL_LONGSHORT ,信号决定了大体调仓方向,而Longexit信号造成锯齿类型的平仓记录。

开仓类信号只负责买入,平仓类只负责卖出?源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def _next_signal(self):
if self._sentinel is not None and not self.p._concurrent:
return # order active and more than 1 not allowed

sigs = self._signals
nosig = [[0.0]]

# 第一部分:注册为多空市场的信号
# Calculate current status of the signals
ls_long = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONGSHORT] or nosig)
ls_short = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONGSHORT] or nosig)

# 第二部分:注册为单看多市场的信号
l_enter0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONG] or nosig) # >0表示看多
l_enter1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONG_INV] or nosig) # _INV反向信号,<0,负数表示看多
l_enter2 = all(x[0] for x in sigs[bt.SIGNAL_LONG_ANY] or nosig) # _ANY只要非0就是true
l_enter = l_enter0 or l_enter1 or l_enter2

s_enter0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORT] or nosig)
s_enter1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORT_INV] or nosig)
s_enter2 = all(x[0] for x in sigs[bt.SIGNAL_SHORT_ANY] or nosig)
s_enter = s_enter0 or s_enter1 or s_enter2

# 第三部分:多头退出信号
l_ex0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONGEXIT] or nosig)
l_ex1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONGEXIT_INV] or nosig)
l_ex2 = all(x[0] for x in sigs[bt.SIGNAL_LONGEXIT_ANY] or nosig)
l_exit = l_ex0 or l_ex1 or l_ex2

# 第四部分:空头退出信号
s_ex0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORTEXIT] or nosig)
s_ex1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORTEXIT_INV] or nosig)
s_ex2 = all(x[0] for x in sigs[bt.SIGNAL_SHORTEXIT_ANY] or nosig)
s_exit = s_ex0 or s_ex1 or s_ex2

# Use oppossite signales to start reversal (by closing)
# but only if no "xxxExit" exists
# self._longexit=bool(_obj._signals[bt.SIGNAL_LONGEXIT])
# 未设置独立的多头退出信号,且空头进入信号为true,long_reverse信号为true
l_rev = not self._longexit and s_enter
# 未设置独立的空头退出信号,且多头进入信号为true,short_reverse信号为true
s_rev = not self._shortexit and l_enter

# 这一部分是第一部分的反信号
# Opposite of individual long and short
l_leav0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONG] or nosig)
l_leav1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONG_INV] or nosig)
l_leav2 = all(x[0] for x in sigs[bt.SIGNAL_LONG_ANY] or nosig)
l_leave = l_leav0 or l_leav1 or l_leav2

# 这一部分是第二部分的反信号
s_leav0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORT] or nosig)
s_leav1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORT_INV] or nosig)
s_leav2 = all(x[0] for x in sigs[bt.SIGNAL_SHORT_ANY] or nosig)
s_leave = s_leav0 or s_leav1 or s_leav2

# 未设置独立的多头退出信号,且多头leave为true
# Invalidate long leave if longexit signals are available
l_leave = not self._longexit and l_leave
# 未设置独立的空头退出信号,且空头leave为true
# Invalidate short leave if shortexit signals are available
s_leave = not self._shortexit and s_leave

# Take size and start logic
size = self.getposition(self._dtarget).size
if not size: # 没有持仓,只能开仓操作,判断是否有满足的开仓条件
if ls_long or l_enter:
self._sentinel = self.buy(self._dtarget)

elif ls_short or s_enter:
self._sentinel = self.sell(self._dtarget)

elif size > 0: # current long position,当前多头持仓,判断是否满足平仓,开空仓条件
if ls_short or l_exit or l_rev or l_leave:
# closing position - not relevant for concurrency
self.close(self._dtarget)

if ls_short or l_rev:
self._sentinel = self.sell(self._dtarget)

if ls_long or l_enter:
if self.p._accumulate:
self._sentinel = self.buy(self._dtarget)

elif size < 0: # current short position,当前空头持仓,判断是否满足平仓,开多仓条件
if ls_long or s_exit or s_rev or s_leave:
# closing position - not relevant for concurrency
self.close(self._dtarget)

if ls_long or s_rev:
self._sentinel = self.buy(self._dtarget)

if ls_short or s_enter:
if self.p._accumulate:
self._sentinel = self.sell(self._dtarget)

这一部分逻辑看起来有点难以理解,先只考虑单向做多市场相关信号,分3类:
第一类:多头进入信号(开仓信号),l_enter = l_enter0 or l_enter1 or l_enter2
第二类:多头退出信号(平仓信号),l_exit = l_ex0 or l_ex1 or l_ex2
第三类:多头进入非有效(失效)信号,l_leave = l_leav0 or l_leav1 or l_leav2
关于第一类,第二个类信号,非常显著,
l_enter只关联self.buy(self._dtarget)
l_exit只关联了self.close(self._dtarget)
而l_leave和l_exit类似,只关联了self.close(self._dtarget),所以l_leave和l_exit可看做邻近信号,二者或的关系(实际上,只会有一个有效,因为leave信号生效前提是未设置专用的 exit信号,而exit类信号必须是设置了专用exit信号)

longshort类型交易,面对金叉和死叉会形成点状持仓还是状态持仓

代码:

1
2
3
4
5
6
7
8
9
10
class TestCrossover(bt.Indicator):
lines = ('signal',)
params = (('pfast', 5),('pslow', 20))

def __init__(self):
# 定义一个长期均线和短期均线
sma1 = bt.ind.SMA(period=self.p.pfast)
sma2 = bt.ind.SMA(period=self.p.pslow)
# 创建均线交叉(买入卖出)信号
self.lines.signal = bt.ind.CrossOver(sma1, sma2)

测试命令:

1
python ./signal_template.py --plot --market_type longshort --open_signal TestCrossover --fromdate 2022-01-10 --todate 2023-01-01

结果:
del01

结论:最下面一列是金叉死叉信号,可见信号为点状信号(大部分0,1和-1是点),但持仓为状态持仓(也就是不是状态为1的那一天有持仓,而是出现-1信号前都保有持仓)

累积和订单并发【Accumulation and Order Concurrency 】

上面显示的示例信号将不断发出longshort指示,因为它只是从收盘价减去SMA值,该值将始终为> 0和<0(0为数学上可能,但不太可能真正发生)
这将导致连续生成订单,从而产生两种情况:
积累:即使已经在市场上【即本身账户存在持仓情况】,信号也会产生新的订单会增加市场占有率。
并发:将生成新订单【之后】,而无需等待其他订单的执行订单

为了避免这种情况,默认行为是:不累积,不允许并发
从这点来讲,由于策略的过滤即便再严格,一天能够触发的成交信号肯定也是有很多,在这里bt默认在手里持仓没有被卖出之前,是不会再继续交易,直到手里的持仓结束以后即卖出以后,才考虑再执行下一个订单。

信号策略SignalStrategy和参数调优

Genetic Optimization:https://community.backtrader.com/topic/186/genetic-optimization/22?_=1630611041858
Strategy with Signals:https://community.backtrader.com/topic/462/strategy-with-signals/2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import backtrader as bt

class SmaCross(bt.SignalStrategy):
params = (('pfast', 10), ('pslow', 30),)
def __init__(self):
sma1, sma2 = bt.ind.SMA(period=self.p.pfast), bt.ind.SMA(period=self.p.pslow)
self.signal_add(bt.SIGNAL_LONG, bt.ind.CrossOver(sma1, sma2))

cerebro = bt.Cerebro()
data = bt.feeds.YahooFinanceData(dataname='YHOO', fromdate=datetime(2011, 1, 1),
todate=datetime(2012, 12, 31))
cerebro.adddata(data)
cerebro.addstrategy(SmaCross)
cerebro.run()
cerebro.plot()

编写策略及优化方案的实例 – BACKTRADER中文教程:https://www.itbook5.com/11757/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import backtrader as bt
class St0(bt.SignalStrategy):
def __init__(self):
sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=30)
crossover = bt.ind.CrossOver(sma1, sma2)
self.signal_add(bt.SIGNAL_LONG, crossover)
class St1(bt.SignalStrategy):
def __init__(self):
sma1 = bt.ind.SMA(period=10)
crossover = bt.ind.CrossOver(self.data.close, sma1)
self.signal_add(bt.SIGNAL_LONG, crossover)
class StFetcher(object):
_STRATS = [St0, St1]
def __new__(cls, *args, **kwargs):
idx = kwargs.pop('idx')
obj = cls._STRATS[idx](*args, **kwargs)
return obj
def runstrat(pargs=None):
args = parse_args(pargs)
cerebro = bt.Cerebro()
data = bt.feeds.BacktraderCSVData(dataname=args.data)
cerebro.adddata(data)
cerebro.addanalyzer(bt.analyzers.Returns)
cerebro.optstrategy(StFetcher, idx=[0, 1])
results = cerebro.run(maxcpus=args.maxcpus, optreturn=args.optreturn)
strats = [x[0] for x in results] # flatten the result
for i, strat in enumerate(strats):
rets = strat.analyzers.returns.get_analysis()
print('Strat {} Name {}:\n - analyzer: {}\n'.format(
i, strat.__class__.__name__, rets))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for strategy selection')
parser.add_argument('--data', required=False,
default='../../datas/2005-2006-day-001.txt',
help='Data to be read in')
parser.add_argument('--maxcpus', required=False, action='store',
default=None, type=int,
help='Limit the numer of CPUs to use')
parser.add_argument('--optreturn', required=False, action='store_true',
help='Return reduced/mocked strategy object')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()

本想尝试signalStrategy结合optstrategy,实际发现

1
self.signal_add(bt.SIGNAL_LONG, DoubleMA_11,short_period=5,long_period=10)  

此函数不支持传参,所以无法使用optstrategy进行参数透传调优

bt入门的学习资料整理

01,官方中文文档

https://www.heywhale.com/mw/project/63857587d0329ee911dcd7f2

4.4.3. 线的长度
len和buflen之间的区别
len报告已处理了多少条
buflen报告已为交易数据加载的柱线总数
如果两个都返回相同的值,则要么没有数据被预加载,要么当前处理已消耗了所有预加载的数据(除非系统连接到实时交易数据,否则将意味着处理结束)。
4.5. 索引0和-1
框架认为最后一项为(读写当前点的前一个点)索引值为-1。 因此,在策略中比较当前收盘价与前一个收盘价是通过 0 vs -1的方式。

4.6. 切片
backtrader不支持对线对象进行切片,这是遵循[0]和[-1]索引方案的设计决策
4.6.1. 获取切片
可以获得具有最新值的数组,语法:

1
2
3
4
5
6
# 显示默认值    
myslice = self.my_sma.get(ago=0, size=1)
返回一个数组,该数组的大小为1,当前时刻为0,向后获取。
要从当前时间点获取10个值(即:最后10个值):
# ago的默认值为0
myslice = self.my_sma.get(size=10)

4.7. 线的延迟索引
[]运算符可用于在next逻辑阶段提取单个值
假设一条逻辑是将先前的收盘价与简单移动平均线的实际值进行比较。无需在每次next迭代中进行手动操作,而是可以生成预定义的lines对象:

1
2
3
4
5
6
7
8
9
class MyStrategy(bt.Strategy):   
params = dict(period=20)

def __init__(self):
self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)
self.cmpval = self.data.close(-1) > self.sma
def next(self):
if self.cmpval[0]:
print('上一个收盘价高于当前移动平均值')

这里使用”()”延迟符号:
这提供了收盘价的副本,但延迟了-1
比较self.data.close(-1)> self.sma 会生成另一个line对象,如果条件为True,则返回1,否则为0

4.8. 线(群)的耦合
日交易数据每年约有250条
周交易数据每年有52条

于是()表示法(空调用)可用于解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyStrategy(bt.Strategy):   
params = dict(period=20)
def __init__(self):

# data0 是日交易数据
sma0 = btind.SMA(self.data0, period=15) # 15天sma
# data1 是周要以数据
sma1 = btind.SMA(self.data1, period=5) # 5周sma

self.buysig = sma0 > sma1()

def next(self):
if self.buysig[0]:
print('每日sma大于每周sma1')

在这里,较大的时间范围指标sma1通过sma1()与每日时间范围耦合。这将返回与更大数量的sma0兼容的对象,并复制sma1产生的值,从而有效地将52个周数据分散为250个日数据
4.9. 通过操作符构造对象
4.9.1. 情景1-操作符创建对象(init阶段构造指标)
4.9.2. 情景2-逻辑操作符 (next阶段构造指标)
4.9.3. 一些不可重载的运算符/函数
Python不允许重载所有内容,因此提供了一些功能函数来应对这种情况。
注意:仅适用于情景1,以创建对象供后面使用。
操作符:

1
2
and -> And
or -> Or

逻辑控制:

1
if -> If

函数:

1
2
3
4
5
6
7
any -> Any
all -> All
cmp -> Cmp
max -> Max
min -> Min
sum -> Sum
reduce -> Reduce

02,量化NPC

数据源:insight,华泰免费2个月
sma策略,高低点突破策略

【backtrader保姆教学】高低轨突破策略:https://blog.csdn.net/weixin_38132951/article/details/129472199
【backtrader保姆级教学】日内区间突破型策略:https://blog.csdn.net/weixin_38132951/article/details/129477688
【backtrader保姆级教学】投资组合回测:https://blog.csdn.net/weixin_38132951/article/details/129765384
【backtrader保姆级教学】趋势选股策略:https://blog.csdn.net/weixin_38132951/article/details/129965349

03,量化投资与机器学习:Backtrader系列ok

gitee,刘时忞 / learn_backtrader(包含,各个文章要点):https://gitee.com/simon_lsm/learn_backtrader
github,QIML Backtrader系列:https://github.com/QuantWorld2022/backtrader
微信公众号,文章:https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=2380299870701420545
csdn,Backtrader系列教程⑦:可视化篇(重构):https://blog.csdn.net/qq_41578115/article/details/122535202
Backtrader 来了
Backtrader 数据篇
Backtrader 指标篇
Backtrader 交易篇(上)
Backtrader 交易篇(下)
Backtrader 策略篇
Backtrader 可视化篇(重构)
Backtrader 常见问题汇总
Backtrader 常见案例汇总

notify_order,notify_trader区别
策略里,notify_order, notify_trade两个函数,一个是订阅提交时触发,一个是交易产生时触发,这个在策略调试时特别有用,而且是通用的,直接写在基类里,另外就是基数实现log函数

03 “模块化算子”
这个才是“积木式”策略开发的精华。
我们连上面的代码都不想写,想直接从“仓库”里复用。
我们把策略逻辑拆解成:选标的、按信号筛选,排序,仓位分配等等。
先从最简单的“等权重买入并持有,每个季度动态再平衡”,这个经典的资本配置策略开始。
这里拆分成“算子”会是:

当前时点(今日):datetime 2019-01-02 close 51.12077805
往前推1天(昨日):datetime 2021-01-28 close 54.91980265
往前推2天(前日) datetime 2021-01-27 close 55.5952978
注意:此处时间循环了,应该19年的,取到了21年。

如何对齐不同周期的指标
__init__()和once() 区别

这里的房子,指的是公寓,而非住宅,租房享受的就是单纯的居住价值,对标的资产应该是公寓,这样才是对等的比较。 如果未来一天租房也可以享受落户和学区,那么租房和住宅价格才有比对基础。

分析目标:买公寓划算还是租房划算(由于租房不享受学区和户籍,所以和公寓才是公平的)

先看一个小例子

基础参数

项目 数额 含义
初始资金 100w 当前手头现金100w
年租金 5w (100w的房产)每年按照出租11个月计算(1个月空置),得到月租金4.5k。
利率 5% 闲置资金的投资收益率(可认为是存银行利息)
房产寿命 30年 30年后房价保持不变(还是100w)

不妨计算,用100万,模拟买房和租房策略下的收益(**静态视角分析(未来利率,租金等均保持稳定)**,现金流折现利率也按照5%计算)。

可见,不论买房还是租房,100万本身是没变化的,只是变化了表现形式。一个转为为存单形式,享受银行利息收益。一个转换为房产形式,享受房产居住价值。简单的理解方式:钱本身没变化(不增不减,就在那里),只是换一个方式陪伴我们而已(理解这一点很重要,否则就会有现金迷恋症,本人之前就有这个问题)。

未来趋势倾向性分析

利率,租金等因素未来变化趋势,对那个策略有优劣势。

项目 未来走势 租房 买房
利率 利率大概率继续下降,随着人民币国际化和社会的发展,融资成本大概率走低的,当下发达国家利率,也可作为佐证(0利率,负利率),未来利率大概率继续走低 ×(利息变少) -(无影响)
房租 房租和人口以及工资水平挂钩,就成都而言,人口流入趋势短期不会变,收入长期层面大概率也上涨的,所以房租大概率也会涨。 ×(租房支出变多) -(无影响)
30年后房价 30年后,认为房价还是100w,乍一看感觉高估了,没考虑房产折旧,但是实际上,目前100w对应10年工资,未来社会发展,30年后,可能就对应2-3年工资。其实同价格已经充分考虑到房产折旧因素了。相对价格降低很多了。 -(无影响) √(正影响,30年后大概率多余100w)

整理看,未来发展对买房方有利,对租房方不利。

关于成都未来人口
成都人口:2部分:川内流入和一线城市挤出,川内流入应该会变少,一线挤出得看未来一线房价,成都作为西部较好地区,地位还是蛮稳固的

一些观点

01,当下买房不划算,租房划算
这句话完整的来讲是,当下买住宅不划算,租房划算,原因是住宅租售比不合理,这个结论是没问题的。但公寓租售比还是可以的,目前成都大多数公寓价格是住宅一半,具体划算不,可通过公式简单计算,月租金1130年 如果比房价高就合适,反之则不合适(30年主要是偏保守估计,混凝土不偷工减100年也是撑得住,不考虑电梯配套老化等因素)。

02,有人反驳上面举例,比如租房可以降低支出,比如租小点差点之类的,这样计算出的结果不就比买房划算了么?
这种比较就不对等了,等于一方降低生活体验的,不在此分析范畴内。因为即使买房,亦可出租出去,然后转租小房子,一样可以得到一笔正向现金流。

03,有存款可以用来投资啊,有获取更高投资收益的潜力,买成房产后就无法投资了。
即使买成公寓,也一样可以抵押出去获得现金,而且利率更低(消费贷3.6%的利率,而且可以做到按月随借随还),这么算,反而更合适。等价于买入公寓同时,再抵押出来,等于不花钱(当然抵押比例不可能达到100%),白住房子(当然3.6%的年利率要支付)。这也是当下的最优策略。
说到底,如果有好的投资渠道,公寓一样不影响获取收益的能力

04,租房很自由啊,买房就锁死了
这是一种粗放的想法,这里的买房是一种投资视角的策略。也没说一定要自住(当然不建议买多套,毕竟房产税还在路上)。再说了买房一样可以再转租出去的,不耽搁自由

05,当下选择租房也有些潜在的未来利好
a,短期价格波动,会有买入抄底机会,比如未来房产税等 落实。可能导致大量住宅出清,公寓也可能也被拖累(但也不会太多,因为本来价格就是住宅一半,除非住宅跌掉5成,且公寓租售比并不算高有租金托底),手持现金就有检漏机会
b,随着未来预售制取消,现房制度下,房屋品质大概率会有所提升,也就是更可能买到高质量的房子

06,公寓潜在的未来利好
户籍和学区未来大概率和房产脱钩,一旦脱钩,公寓住宅价格会靠拢,大概率以公寓涨,住宅跌的方式靠拢(考虑梯户比,小区等,同房龄地段的公寓很难比住宅贵),整体来说,公寓相比住宅有正面预期

综合来看

01,未来住宅大概率不涨了(既和收入脱钩又和租金脱钩),而公寓未必。公寓总价or均价本来就较低,且公寓是和租金挂钩,只要经济形势不出大问题,平均工资不下降的, 成都人口流入保持稳定,公寓价格就能维持坚挺。
02,未来房产税不论按套还是面积征收,大家都会保留优质房产自住,出清劣质房产(比如:老破大),所以即使推出房产税,高品质房产一样可能继续上涨。选择高品种公寓,为最佳策略

首先要搞明白,市场上关于公寓的主观槽点以及数据类论据。

定性篇

从网上巴拉的各种负面因素汇总

先谈谈对以上各优缺点的评判吧

年限(产权)

不会真有人以为房子可以住70年吧,目前成都95年房子如果没有学区,基本流通困难(这才28年而已,在等12年,基本没有流动性,沦为再便宜没人买的程度),尤其高层住宅,如果不进行大型维护,一样基本无法居住,而且基本小区所有配套都会存在各种问题。

没学位

硬伤,但相对与好学区的住宅才是硬伤,相对普通学区住宅,其实有无学区区别不大
学区角度看,住宅价格严重高估的(或者公寓严重低估),原因就是住宅均价对应的是一般学区的均价,比如商住5k(千),住宅1w(万),实际好学区可能2w或更多,普通学区,可能8k,普通学区相对商住单纯多了户口优势,并不值得这个价差。(简单来说,普通学区住宅和没学区,区别不大

没户口

硬伤,但北京上海等买住宅也未必有户口,二三线户口本就没难度
也就是说户口和学区这两个硬伤,是和“户口和学区有优势的城市内的好学区区块的住宅”相比的劣势。而大多数住宅对应的学区都是一般的学区,所以这个硬伤并没有看上去的那么“伤”

高首付(比例)

观点:购房者如果拥有50万首付,可以购买150万的住宅,公寓却只能购买100万。同样总价的公寓、住宅,住宅购房门槛反而比公寓低。
不同意见:三成首付,五成首付,纯属杠杆率的差异(可以简单理解为大额信用卡),在货币贬值时代,可以较大程度利用杠杆,用30万本金撬动100万资金,享受100万资金价格上涨优势,同时提高负债率也能较好的抵御通胀
但是,房价如果不上涨,那么这个杠杆其实意义不大,通缩时代,背负大额现金负债更糟
所谓首付门槛是伪命题,劳斯莱斯哪怕0成首付,你会买么?本来就是考虑需要什么买什么,而不是买得起什么就最大限度买什么。住宅门槛低由于低首付导致,并非真正的便宜,不论本金,还是利息终究还是要还的。中介会很强调这一点,无非是中介业绩和房屋总价挂钩的,所以肯定希望买总价贵的,推荐买方承受范围内的高总价资产。

贷款年限

观点:贷款年限短,导致公寓月供压力巨大。同样贷款100万,住宅物业最长能贷款30年,月供只需要5307元/月,公寓物业最长却只能贷款10年,月供要达到11041元/月,这给购房者带来极大资金压力。
不同意见:虽然10年还款额度大,但债务时间短,并且相对可控,10年工作稳定度肯定大于30年,而且,债务我国是无限责任,永远躲不掉,借款那么长从任何角度看,都不算很大的优势(只是感觉上,等于债主给自己展期的机会)
说到底,有多大能力吃多大饭,能力不够,100年期限贷款一样可能还不款,不同行业的职业发展路径不同,除了公务员,医生少数行业,大多数行业其实是不具备30年不失业的能力的

贷款利率

观点:公寓利息较高。住宅可以使用利息较低的公积金也可以使用商贷,最高利息4.9%;公寓物业基本要上浮10%-20%,利息高了住宅1个百分点,进一步压缩公寓的投资收益。
不同意见:利息差额2%相比本金和整体支付资金,并不算非常大,别忘了,类似品质的住宅和公寓价差基本1倍了

水电费

放图镇楼

小红书:https://www.xiaohongshu.com/explore/630997b500000000190295bb
珍藏版,成都民用和商用水电气价格差别,还是很大的。
用水按照年度的梯度用量,民用从3.03-6.51元/m³跨度收费,商用则一竿子打死的4.43元/m³。
用电量按月,民用电费是0.52-0.82元/度,商用则是0.84元/度。
用气量也是按年,民用是2.02-3.04元/m³,商用则统一的是3.36元/m³。
按照一个月用水17m³,用电188度,用气25m³来算,十年商用家庭比民用多花费1.2万左右
十年1万2,没人嫌多吧

物业费

商业差别很大有2.5的也有8的,民用一般2.5左右

用途

此处公寓可注册公司,但鸡肋,不考虑

拆迁补偿

低概率事件,不考虑

限购

公寓有优势,不限购

影响公寓价格的重大因素

落户,学区

能落户,有学区(老公寓有这个的,不过极少且不便宜)

水电气三通

三通且组好民用价格,水电一般都通,重要的是气,没有气的话,电费会比较高

小区环境

有类住宅小区环境,交通便利,大户型

地段

选择商圈成熟、交通便利的区域。一方面是因为便于出租,而且租金也有保障;另一方面,是因为涨值空间比较大,利于另作他用。

居住体验

容积率,朝向、采光通风、梯户比等
采光通风:公寓房大多都是高层建筑,一层可能有20多户,人口密度非常大,而且公寓楼一般是通道式结构,房间都是单面采光,房间光照时间短,通风效果也很受影响。公寓房的面积比较小,多为40-70平,不适合多人一起居住,会非常拥挤。
容积率,针对有小区环境的商住,很重要。
梯户比主要影响通勤时间,高峰期可能上下楼都得10分钟
以上因素需要注意下即可,并不是所有公寓在以上几个方面不佳

公摊面积

考虑高比例公摊造成的低价假象,考虑单价时按照套内考虑。

物业

小区的物业好坏直接影响公寓房的价格,买房时首先要保证小区内的配套要完善,尤其是商业配套。其次是小区的安保措施要有保障,对业主反馈的问题处理要快,这样你的租金才能有保障。

价格

最核心的是价格!价格!价格!毕竟主要用来出租的话,买入价就是成本,而租金取决于城市地段等外部因素,只要价格足够便宜,差点的公寓一样可以实现正收益。
投资是为了收取租金,赢得长期不断的收入,如同饲养一个会下金蛋的鸡一样,不要杀鸡取卵,认真看待这个世界的现实,让自己真正的在投资上成为一个收益嘎嘎的投资人,这才是核心!

户型

公寓大多小户型,出租有优势(均价较高),主要针对租房人群,所以对租房人群敏感
自住则是劣势,劣势邻居大概率也是租户,人员复杂。
大户型公寓,邻居即使租户也大概率长租,或者就是家庭,相对不稳定单身人口稍微稳定

“事到如今感觉可以回答一下了,公寓=居住价值,住宅包含了落户学区居住等价值。大家都知道住宅是好东西,再加上中国前几十年GDP飞速发展,平均增速9.6%,于是大家给了住宅一个非常高的估值,或者说叫泡沫。而公寓因为天天被黑,所以基本没啥大的泡沫,且给你提供稳定的租金,稳定的现金流。经济繁荣转滞胀的阶段,大家都靠泡沫赚钱,因此带泡沫的住宅估值越来越高。但现在经济是滞胀转衰退,市场上的钱渐渐减少了,由估值堆出来的住宅,渐渐会因为紧缩而被杀掉估值。反倒是现金流优秀的公寓,在大类估值被杀的时候,能保值,相当于股息率非常不错的股票,在熊市跌不到哪里去。阶段不同,投资策略不同罢了。

定量篇

房产估值方法,可类比为可转债,股票价格低时收利息,股票价格高时,转股后卖出。

01,交易价差

依靠买卖价差获取收益,典型的住宅,我国一线城市平均租售比为1:635(2022上半年),也就是基本50年回本,怎么算都不划算,但为何大家趋之若鹜,原因很简单,没人指望租金挣钱,都是指望未来价格更上一层楼来获取收益。以交易价差获取收益,对手续费会极其敏感。
交易价差不好估计,供需决定,典型的学区房!

02,租金折现

买入后,持续出租获取源源不断的租金收益现金流。为何靠出租而不是交易,交易成本高呗,涨价100万,国家收走50万,此时买家买入只能冲着自住或者长期出租的目的。所以公寓价格基本和租金水平挂钩的,相对来说可以体现更纯粹的居住价值
公寓价值相对容易估计,比如一套房子A,市场租金3000元每月,考虑不可能持续出租,所以估值是会按照每年11月估值,那么对应未来现金流
价格=3000*11个月*40年=132万
有人说你没考虑租金上涨啊。其实是考虑了的,只是做了简化处理,假如每年租金上涨5%,定期存款利率5%,则明年的租金(明年的1000块等于今年的1000/(1+存款利率),假如定存利率10%,那么今年的900块,约定于明年的1000块(900\1.1=990),有金融学背景应该都懂),折现到今年就是
(3000*11*1.05)/1.05=3000*11(这里没考虑3.3万的租金是分月到的,但是原理一样的)。上面的计算公式其实默认了未来租金涨幅和定存利率同步上涨的
简单来说,公寓价格是有兜底的,租金兜底,除非租金下跌,否则亏损概率不大,当然如果着急用钱,交易价格可能下跌。

03,公寓住宅交易费用差异


假设,张三在2015年分别购入了一套住宅和公寓,购入价均为50万。现在需要售出,假设售出价都为100万。那么,所需要承担的税费有:

住宅
如果满二、满五唯一、普通住宅的话,税费会少很多,只有1%的契税;
如果不满二、不满五不唯一、普通住宅的话,税费大概为7.3%,其中增值税占了大头(5.3%);
另外,关于个税,在目前房产增值的大背景下,一般都会选择核定计税。除非说,买入卖出价相差很小,才会去选择核实计税。
公寓
采用核定计税(也称全额征税),总税费约为卖出价的14.7%;
核实计税算法相对复杂,但总的规律是:房产升值越多,缴税(个税、土地增值税)越高;
如果房产增值不多,甚至没有增值,那选择核实计税会划算些,相反则选择核定计税。
有上述数据可见,公寓的交易成本非常高。

解惑篇

为何商住和住宅价格差异大

拍地时,商业用地的价格本身就比住宅用地,城镇用地要便宜,在固定土地成本的基础上盖出来的房子,自然也要以此为依据。

其他参考此篇文章,读完相信你会对公寓有不一样的认识
洞察:公寓和住宅的21个区别,以及十个常见不让你买公寓的真相:https://zhuanlan.zhihu.com/p/595945002

综述

投资住宅优势:流通成本低,价格上涨实打实收益,且高杠杆充分资金,吃足价格上涨优势,适合交易类投资,不适合收租类投资(总价高,租售比低),当前存在较高溢价。
投资公寓优势:公寓主要考出租,吃租金上涨优势,杠杆低且交易手续费高,所以不适合交易类投资(价差类投资),总价低,租售比高,价值有租金兜底
不要人云亦云,独立思考,并为自己的思考负责,才是紧握时代方向的。过去的经验未来未必适用,尤其是处于大概率变革的时代。

0528目前来看,应该到了入场时机

支持01:上证指数及无风险收益曲线交叉

参考博文:上证指数以及定投无风险收益率理财收益曲线比较:https://uqer.datayes.com/v3/community/share/6003a100a7014d01585f54f4
这条神奇的曲线,当下位置在这里(20220528),当然未来可能回踩,但整体来看,的确有较大概率构成底部
del01

支持02:破净股比例接近历史高位

历史角度,破净比例高于20%,基本上对应行情均为底部区域。而目前(图中对应最新日期为20220516),也基本达到此比例,当然,此数据有进一步升高(对应行情下挫)。但历史角度看,指数下跌空间已经很有限了。

支持03:总市值比GDP接近底部

这个指标看,有一定下行空间,但空间并不大 ,注意,我们重点关注的是深色部分(靠下部的区线),这个是比值,上部的指数,不需太关注。
del01

支持04:股债利差将降低

如下图:关注蓝色线,蓝色线到达2倍标准差顶部,回调概率较高。而回调对应着股价(这里是指数)上涨。
del01

支持05:市盈率位于底部

重点关注:下部的黄,蓝,绿色三条线,最上部红色,是指数,不关注。
可以看到,三者都靠近历史底部。
del01

支持06:全部A股等权重市盈率 中位数市盈率 接近底部

如下图黑色部分,目前已经很接近。此图最后日期为:20220526
del01

反对01:指标同质化,疫情+俄乌冲突+欧美对俄乌的扩大化,近几十年未遇之大变局

上述所有指标,均是价格衍生指标,可以说明价格处于历史底部。但历史也不大可能是简单重复。目前的疫情和俄乌冲突(近几十年没有这个量级的大国直接参与战争,而欧美态度,又使得冲突扩大话,战争造成多大影响尚无法估计)都对一些行业造成毁灭性打击,所以即使出现极端行情也无需感到奇怪。
另一个需要注意的问题时,目前刚位于底部,可能面临较长时间的磨底期,尤其是疫情和俄乌,大概率会压制行情难以大涨。

INNERID:uqer/上证及定投无风险收益比较_22042601.nb
缘起:文章:上证指数以及定投无风险收益率理财收益曲线比较

历史曲线

先看下人家的曲线
del01
注意到底部蓝色线红色线交汇点,是处于完美的底部区域。之后不久,大盘就转牛。这个了不得啊,下次到了底部,卖车卖房,满仓上,分分钟走上人生巅峰!
del01

当下位置

clone代码后直接运行,即可得到最新回测结果(当前日期:20220426),截图如下
del01
可见,现在已经到了我们梦寐以求的底部了。心动了没有?嘻嘻!
del01

个人见解

大概率是凑巧,
首先,大多行情线,基本存在一条线,可以将底部近似的串起来,由于我国市场,目前整体向上(所以这条线就容易存在,且看起来,拟合效果也会不错)。凑巧的是其数值,恰恰等同无风险收益率。(其实,不等于无风险收益率,也可以等于其他指标,比如:优质企业债券等收益率等,毕竟宏观经济指标太多了,总可以找到一个指标和这个斜率吻合的)。
但也并非全无意义,既然历史角度成立,所以,可能还是说明一些东西的。具体是啥,我也说不明白,简单来说,可以参考下,但也别太较真,千万别真的当做100%概率的事情(理论上,100%收益的事情,对应理论上,该采用所能达到的最上限度杠杆上,毕竟,稳赚不赔,钱多不压身。当然现实生活中好像没啥是100%的,金融市场,就更是如此了)。

不妨参考其他市场上的底部线,
del01

看到没,其实人家也挺精准的,尤其是孟买股市,吻合度很高。只要经济整体向上(向下也行,只要有大趋势),大概率会存在一条直线,将市场底部粗略串起来。但这只是后视角的概念,并不能作为预测使用!(请保持唯物!),类似抛硬币3次都是,正面,下一面反面概率也不大于正面!!

部分因素是假因素,比如公墓基金发型, 显然是结果,不能作为底部信号的判断依据。
中金:如何判别A股市场是否见底?历史上阶段性底部特征梳理:https://k.sina.com.cn/article_1638782947_61add7e3027018deq.html

重要底部信号梳理

1)基本面信号。阶段性底部通常对应盈利周期整体或结构性触底回升,市场见底常领先盈利增长的回升约1-2个季度或基本同时出现。但随着A股内部盈利周期的结构分化,以及新经济权重占比提升,各部分产业盈利周期分化也导致见底节奏差异,例如2012年和2016年分别仅为创业板和传统蓝筹的底部。随着经济结构调整和转型升级,金融数据领先实体经济的规律对不同产业的影响也有变化,要更重视科技创新和绿色发展等领域景气度的结构特征。
2)政策信号。宏观政策内生于经济形势,发力往往具有循序渐进的特征。从政策发力到逐步见效以及市场企稳回升的过程通常存在时滞,市场企稳时点与政策效果密切相关。历次市场调整过程后半段尤其是尾声期常有宏观和监管层面的支持和“维稳”措施,对缓解短期流动性压力、改善投资者预期等有较好效果,但市场中期调整能否结束往往需要观察政策应对市场调整核心矛盾的改善成效。
3)估值信号。历次阶段性底部往往对应投资者的预期和情绪较为悲观,估值下行往往超出预期,机构重仓的标的估值也多被压缩至较低水平。但历次底部估值水平高低存在差异,可同时结合一些具有均值回归特征的指标做综合评判,如宽基指数和主要风格指数的股权风险溢价等,极低的估值虽然无法单独印证市场是否见底,但往往对应中长期取得正收益的概率提升,并且在基本面因素边际好转时也可能给市场带来更大的修复弹性。
4)资金信号。A股阶段性底部区域往往伴随产业资本增持明显增加或净减持下降,以及市场交易情绪明显降温,底部时期常伴随1.5%以下的换手率水平或者成交额相比前期高点萎缩60%以上,也可结合公募基金发行和新增开户数等情况,作为辅助性参考。
5)行为信号。历史来看强势板块或者强势股补跌也可能是调整尾声阶段的重要行为信号。综合来看,有效的政策信号以及盈利预期改善的基本面信号是阶段性底部形成的重要条件,估值、资金和行为层面的信号可以起到辅助判断作用。从历次阶段见底后的市场表现看,多数底部低点出现后3个月内常出现消化负面因素的磨底期,超跌反弹主线并非该阶段的领涨行业,市场情绪谨慎时基本面逻辑清晰的板块有相对收益。
判断市场阶段性底部的指标体系可能也需要动态调整
  随着中国经济层面和资本市场基本面发生较多变化,“后地产时代”中国要寻找增长新动力、寻求增速新均衡,中国经济结构调整、转型升级也在进入攻坚阶段,百年一遇疫情叠加百年未有之大变局,外围环境影响不容忽视;资本市场中,新老经济持续分化,新经济占比逐年提升且市值占比逐步超过老经济。判断市场底部除了基于传统宏观经济指标以外,同样需要关注新产业(35.260, 0.00, 0.00%)趋势的与传统经济的周期异步,如2012年和2018年的阶段性底部;也需要重视外部变量影响,2018年和2020年的两次阶段底部与外围因素消化有关
本轮市场调整至今出现了哪些底部信号?
  年初以来引发市场下跌原因较为综合,外部包括俄乌局势演绎强化滞胀预期、国际关系如中概股退市的担忧,内部则包括市场担忧稳增长政策面临较多约束、房地产市场偏弱且信用风险尚待解决、国内局部疫情和产业监管等因素。而目前政策信号明确且有针对性,货币政策以及房地产等领域的支持政策相比前期更为积极,疫情的管控也在动态调整,对于中概股和平台经济问题,近期金融委会议也做出积极回应。目前来看基本面信号仍有待改善,大宗涨价对中下游盈利的挤压、房地产景气度偏弱拖累住房产业链需求以及消费受到压制等问题,尚待明确拐点出现。辅助信号中,目前市场估值接近历史低位可能已释放较多风险,而资金信号和行为信号尚需继续观察。

市场逐步进入磨底期,未来关注潜在四个方面因素
  综合看,引发市场调整的部分因素已经迎来政策层面的积极化解,基本面信号仍然偏弱,潜在拐点可能仍然需要等待政策发挥效力实现基本面改善,市场短线仍可能有反复,但类似前期大幅下跌的阶段可能已经结束,后续市场可能逐步进入磨底阶段。结合已调整的幅度、估值和负面因素的可能消化程度,我们认为中期维度市场机会大于风险。未来关注以下潜在因素:1)俄乌局势明朗和全球通胀压力缓解,带来“滞胀”担心边际缓解;2)“稳增长”政策继续发力,尤其是目前担忧较多的房地产等领域。3)国内疫情情况进一步明朗;4)中美关系边际趋稳,中概股问题相对明确等。结构上,当前“稳增长”主线可能依然有配置价值,中期随着增长逐步趋于稳定,宏观风险逐步化解,市场可能仍将聚焦更可持续增长的领域,高景气度的科技创新和制造升级等相关领域可能相对占优。   

底部持续时间和特征

历次底部持续时间和特征均有所差异。历次市场底部区域持续时间有较大差异,如2008年、2012年和2020年的市场底部仅持续不足半个月,市场开始明显反转,也包括2016年与2018年,市场出现1-3个月的“磨底期”,而2013-2014年市场在低位盘整近1年后才明显上涨脱离底部。而且市场见底之前的调整幅度与持续时间也相对缺乏规律,本轮回调目前幅度略小于以往的市场中期下跌,但持续时间已相对较长。
市场见底有较多的参考信号,但最为关键的可能是引发市场调整的因素出现转向或边际改善。历史上市场见顶初期往往出现全面或局部估值泡沫,宏观政策或监管转向引发市场估值回调,而随后基本面进入下行周期中,市场整体跌幅继续加大,此外若伴随海外环境的恶化,同样可能对市场造成一定调整压力。最终市场企稳回升也往往与前期引发市场回调的因素转向或边际预期改善有关,通常表现为如政策发力改善对基本面的预期,或者盈利周期已出现见底回升拐点的迹象,抑或压制市场表现的外生变量发生逆转,即使2020年3月由海外环境引发的调整,最终市场企稳也是在国内外流动性环境明显缓解之后;若压制基本面预期的因素未发生根本改变,市场更多表现为调整周期中的阶段反弹。
  “政策底—情绪底—增长底”为市场常见的“见底”模式。在周期下行阶段,基本面走弱至一定阶段后往往对应政策开始进行逆周期调节,而且调节的力度往往在周期下行的中后期逐步加大,即“政策底”在逐步确认;但投资者对基本面下行的谨慎预期可能难以马上改变,在政策发力并未明显改善需求阶段市场情绪难有较大改观,伴随交易层面的特征,历史上往往出现在调整末期跌幅加大,直到政策发力逐步见效带来投资者预期边际改善,市场的“情绪底”也逐步形成;政策供给往往对应未来需求,在增长企稳前可能“稳增长”政策仍将继续发力,最终“增长底”滞后于“政策底”出现。虽然是常见的周期见底模式,但三类底部的时间间隔可能存在不确定性,历史上“政策底”与“增长底”的间隔多为2-3个季度。
  估值、资金和市场情绪等方面的信号具有一定参考意义,主要用于上述关键因素变化后的辅助参考。虽然历次阶段性底部市场估值较低,但历次底部估值的下限往往差异较大,尤其在增长预期恶化阶段判断估值低点难度较高,但是在前述关键因素改善或出现转向后,估值对于市场底的判断有重要作用。同理,反映市场情绪的成交指标、产业资本增持或重要投资者的增持行为以及异常的投资者行为指标,也同样可作为辅助判断的信号

图表1:历次市场底部特征梳理:引发市场调整的因素出现转向或者预期边际改善
del01

图表2:A股历史上六次阶段性底部的见底过程
del01

图表3:长周期的指数表现、市场风格、经济增长和宏观政策的叠加
del01

疑问:非A金融盈利增速,数据怎么获得的?如果是各个公司的数据汇总的,那么怎么汇总计算的?隶属于财报中那些数据。
同比:是啥
A股价值指数,A股成长指数,具体那个标的,或者筛选标准。
宽基指数:指“没有行业区分的多只股票形成的组合指数叫宽基指数,其反映了这些股票综合的走势。像上证指数、沪深300指数这种指数样本标的来自不同的行业的指数称为宽基指数。如果多只股票都属于同一个行业,那组而成的指数就叫做窄基指数,也可以成为某个行业的指数。”

市场阶段性见底的信号梳理

基本面信号

阶段性底部通常对应周期中的盈利增长整体或结构性触底回升。除了2014年的阶段性底部以外,历次阶段性底部均对应基本面的企稳回升,通常市场见底领先盈利增长回升约1-2个季度,或者基本同时出现,即便是2014年市场见底回升,也隐含了改革带来传统行业基本面改善的预期。而2012年仅有创业板见阶段性底部,与传统经济普遍产能过剩和经济复苏偏弱,而受益新产业趋势崛起的创业板盈利进入上行周期有关;同样在2016年中国经济全面见底,尤其是传统行业明显强劲复苏,而创业板盈利增长从高位回落,因此2016年仅有蓝筹板块见阶段性底部,而中小创企业所处盈利周期不利则表现相对较差。
2010年以后A股内部的盈利周期呈现结构分化,盈利弹性影响见底后的市场弹性。随着中国经济结构转型,以及新经济在市场中的占比权重提升,A股在2010年以后内部整体呈现新老经济盈利能力分化和周期异步,同时市场中新经济权重逐年提升,因此判断盈利周期底部可能需对产业有所区分,最终各部分盈利恢复程度可能影响底部的可靠性以及股市表现弹性。例如前述2012年和2016年以后的市场结构分化,同时2018年和2020年的市场阶段性底部事实上也反映盈利的结构性见底与全面见底的差异。
金融数据对实体经济增长具有领先性,但对于不同产业影响有差异。历史上金融数据增长对实体经济增长具有明显的领先性,主要是融资环境转松后,企业获取信贷增加固定资产投资,居民部门加杠杆推升房地产销售增长,进而带动实体经济中上游环节以及消费改善,金融数据增长传导至实体经济回升可能需要1-2个季度左右。虽然金融数据是经济增长预期的良好观测指标,但随着中国经济结构调整、转型升级也在逐步进入攻坚阶段,以及中国进入“后地产时代”中国要寻找增长新动力、寻求增速新均衡以后,传统信贷投放的“稳增长”手段带来的效果也在发生变化。除了总量层面,更要重视结构增长的变化趋势,尤其是房地产领域的修复弹性,以及科技创新和绿色发展等趋势的景气度变化。
图表4:市场阶段性底部出现时间通常接近于整体或结构盈利增长拐点

资料来源:万得资讯,中金公司研究部
图表5:三大产业对中国GDP的贡献

资料来源:万得资讯,中金公司研究部
图表6:全部中国上市公司板块市值占比

资料来源:万得资讯,中金公司研究部
图表7:宽信用是经济增长预期回升的前瞻指标,新增社融拐点与市场拐点有一定关联性

资料来源:万得资讯,中金公司研究部
图表8:信贷脉冲领先中国股市6个月左右

资料来源:万得资讯,中金公司研究部
图表9:信贷脉冲与国债利率反向变动,滞后2个月左右

资料来源:万得资讯,中金公司研究部
图表10:金融数据对企业盈利具有领先性,但金融数据本身的弹性也在减弱

资料来源:万得资讯,中金公司研究部
图表11:新老经济成分的盈利周期异步,可能是不同板块底部出现异步的原因

资料来源:万得资讯,中金公司研究部

政策信号

历史上在市场阶段性底部出现的政策支持信号同样具有重要的意义,一方面政策发力可能对前期市场担忧因素进行针对性解决和扭转,另一方面政策持续加力可能逐渐改善经济基本面下行的预期。
政策发力到市场企稳回升通常存在时滞。历次经济周期下行和市场下跌过程中,由于政策内生于经济形势,“稳增长”政策的力度往往是伴随增长下行压力增加而加大,在政策发力初期,往往难以立即对周期下行产生立竿见影的效果,市场情绪在政策信号初现时期往往仍较为低迷。但随着政策发力逐步见到成效,周期下行的预期开始逐渐扭转,市场也将伴随预期差逐渐企稳。例如2008年、2012年、2016年、2018年等市场底部均明显呈现为政策底领先增长底1-2个季度的现象,2014年相关政策虽然并未带来经济增长企稳,但针对房地产市场偏弱和传统经济改革等重点问题的政策力度相对较大,同样逐步促进市场企稳回升。
此外,历史上相关监管政策支持也是市场调整至低位后的常见信号,如放开制度限制(如2014年放宽创业板再融资)、鼓励各类资金增持上市公司股票等,近年随着市场机制逐步成熟,监管政策在形式上也有动态调整。
历史来看,维稳措施是否有助于化解市场调整核心矛盾,也是市场能否阶段性见底的关键。历史上股市大幅调整后为维护资本市场的稳定发展,政策层可能出台部分有利于市场稳定的政策,政策发力往往会起到良好的效果,尤其是有助于改善市场调整主要矛盾的相关政策,对于投资者情绪会有较明显的改善支持。例如2018年底政策层定调支持民企发展,出台措施支持民营中小企业融资,信用风险问题逐步化解后,市场逐步企稳。
图表12:2018年四季度政策持续加力,市场的悲观预期逐步扭转并见底回升

资料来源:万得资讯,新华网,中国证券报,中金公司研究部
图表13: 2018年央行先后多次降准

资料来源:万得资讯,中金公司研究部
图表14:信用风险溢价回落与市场拐点基本同步

资料来源:万得资讯,中金公司研究部
图表15:2008年在系列维稳措施作用下,市场逐步企稳

资料来源:万得资讯,新华网,中国证券报,中金公司研究部
图表16:2014年多项改革扭转市场对于老经济的悲观预期,市场在一系列政策催化下见底回升

资料来源:万得资讯,新华网,中国证券报,中金公司研究部

估值信号

历史上的阶段性底部往往对应市场较为悲观的情绪,估值较为充分甚至过度反映盈利预期下行。主要宽基指数的历次估值底部的绝对水平高低存在差异。估值虽非市场调整见底的决定因素,尤其在宏观层面因素仍有较大不确定性或者面临流动性风险的背景之下,很难简单依据历史估值低点判断底部,但估值位置对市场具备较强参考价值,且中长期来看,估值位置和长期持股收益率具有较强相关性,市场低估值往往意味着中长期投资价值显现
部分估值相关指标具有均值回归特征,也具备一定的参考意义。如沪深300的股权风险溢价具有一定的均值回归特征,历史上阶段性底部的股权风险溢价基本都位于均值上方一倍标准差左右
在历史阶段性底部区域,往往机构重仓的成长蓝筹估值也被压缩至较低水平。虽然A股机构重仓的成长蓝筹历史估值相对较高,且相比市场整体表现更有韧性,但是在市场对未来盈利悲观且情绪不佳的阶段,这一类成长蓝筹最终在下跌后期也难免补跌并压缩估值。我们所构造的外资持仓前100的龙头公司市盈率指标,历史上在均值(18.8X)和均值下方一倍标准差(13.6X)的位置值得重点关注。
综上,我们认为历史上股权风险溢价与未来股市收益率有高度正相关关系,股权风险溢价高位所对应未来6个月取得正收益的概率相对较高,极低的估值虽然无法单独判断市场是否见底,但是可能对应未来取得高收益的概率提升。而且,当估值逐步进入历史低位水平,基本面因素的边际好转可能给市场带来更大的修复弹性。
图表17:宽基指数历次估值底部的水平均有差异

资料来源:朝阳永续,中金公司研究部
图表18:沪深300非金融具备一定估值中枢

资料来源:朝阳永续,中金公司研究部
图表19:股权风险溢价均值上方1倍标准差是较好的底部监测指标

资料来源:朝阳永续,中金公司研究部
图表20:机构投资者重仓的成长蓝筹历史上阶段性底部的估值较为接近

资料来源:FactSet,中金公司研究部
图表21:历史上阶段性底部的个股市盈率情况分布

资料来源:万得资讯,中金公司研究部
图表22:历史上股权风险溢价高位所对应未来6个月取得正收益的概率相对较高

资料来源:朝阳永续,万得资讯,中金公司研究部

资金信号

A股阶段性底部区域往往出现产业资本增持明显增加或净减持下降。大股东和公司高管对于企业价值的认知可能更为充分,因此在历史上产业资本较集中的增持行为往往成为阶段性底部判断的重要参考,若结合市场成交额看,2012、2014、2016和2018年的四次阶段性底部均出现阶段产业资本增持/A股成交额超过0.3%,以及净减持金额明显下降。而2019年以来随着市场明显扩容,投资者结构多元化和估值中枢抬升,产业资本增持规模整体下降,且净减持整体增加,因此该指标并未再度触碰过阈值,如果考虑到部分股东2019年以后选择通过股票回购来支持股价,将上市公司增持与回购加总所统计的指标整体也低于以往平均水平,指标有效性可能在下降。
市场阶段性底部区域往往出现情绪明显降温。市场在阶段性底部出现时或者前后短时期内往往出现区间内较低的换手率,背后逻辑是市场调整到一定水平后多空双方力量相对平衡,抛压力量被消耗殆尽,导致交易情绪充分降温。以自由流通市值计算的换手率为基准,1.5%以下往往是较为可靠区域,但如果前期成交较为活跃可能对应未来换手率低点也难以降至如此低的水平,经验上阶段性底部的换手率通常相比前期成交高点萎缩 60%以上。此外,偏股型公募基金发行规模、交易所新增开户数对于衡量市场情绪也有较好效果,例如公募基金新成立份额连续数周处于冰点以及新增开户数相比前期减半等,但需要注意交易情绪充分降温对于阶段性底部而言同样并非充分条件,而是作为辅助判断指标,非历史底部区域也可能出现交易情绪明显降温。
图表23:产业资本增持潮在历史上对辅助底部判断有一定效果

资料来源:万得资讯,中金公司研究部
图表24:2019年以前临近阶段性底部,资本净减占比往往明显收窄甚至转正

资料来源:万得资讯,中金公司研究部
图表25:A股上市公司股票回购对判断底部的有效性有限

资料来源:万得资讯,中金公司研究部
图表26:2020年以来上市公司(增持+回购)/市场成交额整体低于以往

资料来源:万得资讯,中金公司研究部
图表27:历史阶段性底部往往对应情绪明显降温,市场换手率降至阶段较低水平

资料来源:万得资讯,中金公司研究部
图表28:公募基金发行规模在历史底部区域均出现明显降温

资料来源:万得资讯,中金公司研究部
图表29:市场新增开户数往往在市场底部区域降至较低水平,以往经验相比阶段高点降低约一半

资料来源:万得资讯,中金公司研究部

行为信号

强势板块或者强势股补跌历史上也是调整末期的常见行为信号。与市场临近顶部前常出现的落后板块或缺乏逻辑的板块补涨相反,市场临近底部时常出现无差异调整,前期相对抗跌或有逻辑支撑的板块出现调整,或者热门的机构重仓股出现补跌。背后的逻辑是在市场调整末期,场内部分前期配置相对灵活的资金由于持有强势板块和强势股前期受损相对较小,但随着股票和板块估值分化到一定程度,导致这部分资金出现调仓需求,但市场情绪悲观可能导致场外几乎没有增量资金入场,最终场内资金的调仓换股往往导致前期强势股出现较大的跌幅。前述阶段性底部在最后下跌阶段均出现过前期热门板块最后阶段领跌的现象(如图表30)。
图表30:强势板块或者强势股补跌也可能是调整末期的重要行为信号

资料来源:万得资讯,中金公司研究部

阶段性底部信号的综合梳理

引发市场下跌的基本面因素反转是历史阶段性底部的重要信号,尤其是盈利和政策信号。市场在大幅调整过程中,可能出于政策事件,或者流动性支持而出现阶段性反弹,但如果引发市场调整的基本面主要矛盾未发生根本改变或边际改善,盈利预期不佳将持续压制市场估值。从历史上阶段性底部的复盘看,最终市场企稳回升的过程,通常表现为政策发力逐步扭转基本面预期,并最终改善盈利周期,或者是新的驱动盈利周期上行的因素出现。
估值、资金和行为层面的信号有辅助判断的作用。从历史底部经验看,历史上估值绝对水平高低相对缺乏规律,资金和行为信号可能在调整过程中可能多次出现,需要合理使用辅助信号,在重要信号出现好转迹象后,结合辅助信号可能加大阶段性底部印证的概率。尤其是估值信号,当市场估值接近历史极低水平后,基本面因素的边际好转往往能带来更大的市场弹性。
要根据实际基本面情况动态调整判断市场阶段性底部的盈利和政策信号。中国经济层面和资本市场的基本面近10年以来发生了较多变化,经济层面“后地产时代”中国要寻找增长新动力、寻求增速新均衡,中国经济结构调整、转型升级也在逐步进入攻坚阶段,传统稳增长政策对于经济增长质量的改善作用可能也在发生变化;资本市场中,新老经济持续分化,新经济占比逐年提升且市值占比逐步超过老经济,市场结构与经济结构差异变大,新产业趋势对于市场盈利的结构性影响加大,判断市场底部除了基于传统宏观经济指标以外,同样需要关注新产业趋势的盈利周期。
外部因素对国内市场影响可能加大。经济基本面层面,百年一遇疫情叠加百年未有之大变局,供应链及地缘风险冲击时有发生,大国之间的竞争也在增加,中国在全球经济中的占比逐年提升,外围环境对中国经济的影响可能更加复杂;市场层面,外资逐渐成为中国市场重要的投资者,外围风险扰动对国内市场的影响也在加大。从2018年和2020年的两次阶段性底部看,市场的止跌企稳均与外围因素有关,在讨论市场底部经验时同样需要考虑外部变量对基本面的影响。
历史上阶段性底部低点出现后的市场特征
历史上阶段性底部低点出现后的3个月内常出现磨底期。阶段性底部出现之前往往伴随负面因素集中暴露,投资者情绪较为悲观,因此在市场出现政策利好或者前期引发调整的因素边际好转过程中,投资者往往需要反复确认或通过数据验证,市场也会因为交易惯性而容易出现反复。因此在低点第一次出现后市场经常出现一段时间的磨底期,历史来看多在3个月内,随后引发脱离底部区域的因素较多是来自市场担忧因素进一步明朗和化解。例如2008年、2018年和2020年市场均在社融和信贷数据明显超预期扩张,市场担忧因素逐步化解后加快上涨和脱离底部。
超跌反弹主线并非领涨行业,而是基本面逻辑最清晰的板块。虽然一般前期可能已有较多稳定市场或基本面的政策出台,市场在临近阶段性底部初期,由于情绪仍然相对脆弱,市场反弹主线往往是基本面逻辑最清晰的板块,而非前期跌幅最大的板块,我们将历次市场初次见低点后3个月的行业表现进行梳理,也发现类似特征。例如2008年10月市场见低点后,率先企稳回升的是受益政府加大投资力度的建材和电力设备新能源等基建领域,而非同受政策大力度支持但仍面临风险的金融和房地产;2014年市场见低点后的三个月内涨幅最大的仍是前期高景气的TMT板块,而非老经济和金融;2020年疫情期间,领涨市场的是受损相对较小或有所受益的必选消费和医药。
图表31:历史阶段性底部区域中,多数在见底后3个月内继续磨底,市场偏离底部幅度多在10%以内

资料来源:万得资讯,中金公司研究部
图表32:新增社融明显增长常成为底部区域市场扭转市场悲观预期的催化剂

资料来源:万得资讯,中金公司研究部
图表33:市场见低点后3个月,领涨的行业与前期超跌关联度不高,而可能是基本面逻辑最清晰的行业

资料来源:万得资讯,中金公司研究部

本轮市场调整至今出现了哪些底部信号?

年初市场调整原因简析
年初至2月上旬的调整更多来自国内增长环境,外部有边际影响,具体原因包括:1)市场担忧稳增长政策力度面临较多约束,经济增长压力较大,高频数据反映房地产销售相对较弱且缺乏起色,信用风险挥之不去。2)2021年较为强势的“制造成长”领域,股价及估值处于相对高位、仓位不低,短期缺乏催化剂。3)随着美国通胀压力加大,美联储加息预期升温,与此同时美债利率大幅上升,美股回调。4)中美关系方面,美国商务部将部分中国单位列入“未经核实名单(UVL)”引发市场担忧,相关的生物医药、科技硬件和新能源汽车产业链受此影响较大。
3月以来市场调整的主要矛盾和前期有所变化,海外和国内多重超预期因素主导市场回调,北上资金大幅流出。主要海外因素包括:1)俄乌局势进一步演绎,以原油和农产品(6.000, 0.00, 0.00%)为代表的商品价格大涨,市场担心供应风险可能进一步演绎甚至逐步带来增长压力,中期“滞胀”情形的概率可能在加大。2)美国对俄罗斯展开制裁,2022年还有较多国家举行大选,地缘政治风险上升。3)前期SEC对中概股监管收紧,引发中概股退市的担忧导致中概股和港股调整,北上资金曾7个交易日累计净流出670亿元。国内因素包括:1)疫情最近在局地出现反复且影响范围加大。2)市场对于“稳增长”的政策力度仍有较多担忧,2月金融数据低于预期反映信贷需求仍然相对疲弱,而且外围局势复杂,给“稳增长”带来更大的挑战。3)投资者普遍担忧产业监管范围的扩大。4)连续的回调之后可能也有投资者止损或有资金赎回,导致负向反馈出现。
图表34:年初以来公募基金收益率整体不佳,尤其是重仓制造成长和消费领域的基金

资料来源:万得资讯,中金公司研究部
图表35:年初以来仅煤炭上涨,去年涨幅较大的制造成长明显回调

资料来源:万得资讯,中金公司研究部

当前市场出现哪些见底信号?

经历年初以来市场大幅度的调整后,市场是否见底成为焦点问题,我们基于前述维度梳理当前市场各维度所出现的信号特征(图表36)。
当前政策信号明确且有针对性,基本面信号尚待改善。目前市场基本面仍面临部分压力,尤其是结构层面大宗商品涨价对中下游利润率的挤压,房地产投资和销售偏弱叠加疫情影响对于住房产业链以及整个消费行业的压制,基本面信号在结构层面仍然不够清晰。当前政策层面信号较为明确,并且较前期更有针对性,除了应对总量增长偏弱的一系列稳增长政策以外,对房地产领域的支持政策相比前期更为积极,疫情的管控也在发生积极的调整,对于市场担忧的中概股和平台经济问题,金融委会议也做出积极回应。目前引发市场调整的部分因素得到政策层面的积极化解,但基本面信号仍然偏弱,潜在拐点可能仍然需要等待政策发挥效力以及周期自身运行到位后出现。
辅助信号中目前估值信号在逐步提供支撑。当前市场估值回调虽然从各维度相比其它历史阶段性底部仍有部分空间,但已接近历史估值低位或价值区域,估值层面的中期风险已得到较为充分释放。而且随着未来基本面信号拐点进一步明确,当前估值充分回调也将为市场回升创造更大的弹性和空间。资金层面和行为层面信号目前仍不明显。
图表36:当前关于市场历史底部信号的最新情况梳理

资料来源:万得资讯,中金公司研究部

未来市场潜在转机

综合来看,虽然短期判断底部有较大不确定性,但我们基于前述分析认为市场担忧因素部分得到政策积极化解,尚有基本面担忧因素在改善过程之中,市场短线仍可能有反复,但类似前期大幅下跌的阶段可能已经结束,后续市场可能逐步进入磨底阶段,经验上成交量可能会有所萎缩。中期来看,结合中国市场从去年2月见顶回调时间已经达到13个月,是中国股市历史上较长的调整期之一,累计调整幅度也不小;另一方面,市场估值也达到了历史上相对偏低的水平,且累积消化内外部负面因素已经较多,我们认为中期的角度机会与风险相比,可能更偏向机会。未来市场重点关注以下压制市场基本面的因素出现潜在转机:
1)俄乌局势和全球通胀压力缓解。针对近期俄乌地缘风险带来供应风险加大,以及当前全球通胀问题,我们在《“滞胀”复盘及对当下的启示》报告指出海外“滞胀”情形概率可能在加大,是从1970年代的经验看,通胀预期可能是判断市场节奏的关键因素,市场表现可能与通胀预期反向变化,即使在中期期滞胀环境下,短周期通胀预期回落改善基本面和政策面,市场整体尤其是中下游行业仍然会迎来转机。对于当前而言,俄乌局势演绎仍具有较高的不确定性,短期大宗商品价格和通胀预期对市场仍有一定压制作用,若未来地缘风险形势进一步明朗,大宗商品价格逐步接近高点并回落,中国市场尤其是中下游行业可能逐步迎来转机。
2)“稳增长”逐步见效,尤其是目前担忧较多的领域。从去年底中央经济工作会议以来,“稳增长”基调明确且政策逐渐加力,但随着中国经济结构调整和转型升级进入攻坚期,逐步进入“后地产时代”,叠加外围供应风险冲击,新阶段的稳增长政策面临更为复杂的局面。年初以来,房地产市场销售偏弱且信用风险仍在化解之中,居民消费需求依然不强等特征也反映了传统稳增长模式可能面临较多约束和挑战,而这可能意味着前期政策发力到见效的时滞拉长,随着稳增长政策在市场担忧的房地产和消费问题逐步发挥作用,经济总量和结构层面的下行压力均有望得到缓解。
3)国内疫情逐步得到控制,对经济活动的限制减轻。近期国内多地疫情快速上升,疫情影响所涉及省份占全国经济体量较大,为整体增长尤其是消费领域的复苏带来一定挑战。但是从近期抗原检测的批准、治疗方案的变化以及特效药的推进,均有较为积极的进展,而且根据海外国家以及中国香港的抗疫经验,我们预计疫情高点出现的时间可能不会太远,如果未来国内疫情逐步得到缓解和对经济活动限制减轻,也有助于经济基本面恢复。
4)中美关系边际趋稳,中概股问题相对明确等。近期金融委会议对该问题有积极表态,中美两国元首的视频通话也释放相对积极信号。
结构上,当前“稳增长”主线可能依然有配置价值,但我们预期中期随着增长逐步趋于稳定,宏观风险逐步化解,并且传统盈利增长弹性可能在下降,市场可能仍将聚焦更可持续增长的领域,可能对于成长风格相对有利,维持高景气度的科技创新和制造升级等相关领域可能相对占优。并且考虑到新经济领域在资本市场中占比已明显提升,科技创新产业未来增长的持续性和弹性对于市场中期转机也较为重要。

主要参考文章:
研究中进行市场底部特征分析:https://www.joinquant.com/view/community/detail/376c92456d4c71e6c80e991b0edefaa1?type=1
【华创金工】市场底部特征研究:https://mp.weixin.qq.com/s/sXGUJ3DXk_iIlsiVjXQ3Fg

价格类

低价股比例

逻辑是牛市消灭仙股,熊市产生仙股
原理:牛市消灭仙股,熊市产生仙股,仙股原意是指价格已经低于1元的股票,这里放大了仙股价格区间,设定为小于2元的股票,统计了从07年开始,低价股数量占比的时间序列。可是,考虑到时间跨度超过10年,那是当年可以买碗牛肉拉面的两块钱,为了抵消通货膨胀的影响,所以这里做了些改动,将条件按每年4%的无风险收益进行增长,到目前,这个低价格的阈值已经是3.2元
图示:
del01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#返回股价低于2元的股票个数占比
def f1(self,date_list):
bei = len(date_list)//13
se_price_2 = []
for i in range(1,13):
e = date_list[i*bei]
s = date_list[(i-1)*bei]
if i == 0:
s = date_list[0]
elif i == 12:
e = date_list[-1]
all_stock = list(get_all_securities(types='stock',date=e).index)
def f1_1(x):
x=x.dropna(axis=0)
return sum(x)
df = get_price(all_stock,start_date=s,end_date=e,fields=['close'],fq=None)['close']
df_1 = df<=2*(1.04)**i
#print(2*(1.04)**i)
se1 = df_1.apply(f1_1,axis=1)
l2 = [len(get_all_securities(types='stock',date=i)) for i in se1.index ]
se2 = pd.Series(l2,index=se1.index)
#se2 = df_1.apply(f1_1[1],axis=1)
se_price_2.append(se1/se2)
return pd.concat(se_price_2)

破净股比例

打折促销,领先指标。
原理:破净是指股价跌破净资产值,破净股具体就是指股票的每股市场价格低于它每股净资产价格,破净的特点和逻辑,和低价股类似。这一点从下面的图中看确实如此,从历史走势来看,破净股数据的变化情况,与股票市场的顶底区域是同步的。针对这个问题,我们找出市场中对应的几个位置特别位置和该指标数据进行对比,这里是2018年2月9号的具体表现
图示:
del01
注意到,这个指标和低价股是强相关的, 曲线非常类似,个人觉得这个可解释性更佳!

1
2
3
4
5
6
7
8
9
10
11
#返回破净股比例
def f2(self,date_list):
pb = []
for d in date_list:
df_temp = get_fundamentals(query(valuation.code,valuation.pb_ratio,valuation.circulating_market_cap,valuation.pe_ratio),date=d)
a = len(df_temp)
b = len(df_temp[df_temp['pb_ratio']<=1])
#b = len(df_temp[(df_temp['pb_ratio']<=1)&amp;(df_temp['pb_ratio']>=0)])
pb.append(b/a)
df_2 = pd.DataFrame(pb,index=date_list,columns=['pb_ratio'])
return df_2['pb_ratio']

流动性类

全市交易额有没有触底?全市场人气。

“大底时全市成交额,是高点全市成交额的10%。目前已经符合大底的标准。”
下面的图是将沪深两市每日成交额进行合计,惊奇的发现,2015年市场成交额数据竟然如此高不可攀,直逼2万亿,达到所有历史行情之最,让人不得不感慨这2015年杠杆牛的威力,明处券商两融业务做得风生水起,暗处场外配资遍地开花。虽然点数没有突破历史,但是成交金额却创下了历史记录。
将近几年的成交额数据放大,可以发现近期成交额基本处于15年以来成交额2千多万的底线水平,占最高点成交额的11%
del01

1
2
3
4
5
6
7
#全市场成交额
#输出两个市场成交额数据变化
def f5(self,date_list):
s,e = date_list[0],date_list[-1]
pl = get_price(['399001.XSHE','000001.XSHG'],start_date=s,end_date=e,fields=['money'])['money']
pl['money'] = pl['399001.XSHE']+pl['000001.XSHG']
return pl['money']

个股流动性有没有触底?大底中大部分个股流动性枯竭。

市场整体热度不够,个股自然不会好到哪里。目前从图中看到,个股的成交已经是在上次股灾值附近
这里以100万/天的成交额作为判断标准,来衡量市场个股是否冷淡。同样,由于时间跨度较大,也要考虑到市场整体的上涨对该判断带来的影响,不过这里是以M2增速的标准进行处理,我们也参考这个标准进行处理:
M2在2005年6月底数据为275,785.53,到2018年6月底数据为1,770,200.00,按复利增长计算,基本上是每年14%的速度上升,将该速度套用在2008年每天100万成交额的基础上,得出了2018年418万成交额的条件。图中看到貌似目前还未能满足该条件,未做比例统计

del01

1
2
3
4
5
6
7
8
9
10
11
12
#个股成交的冷淡
#返回个股成交金额
def f6(self,date_list):
l_money_mean = []
for i in date_list:
all_stock = list(get_all_securities(types='stock',date=i).index)
df_money = get_price(all_stock,end_date=i,count=1)['money']
df_money = df_money.dropna(axis=1)
money_mean = sum(df_money.T)/df_money.shape[1]
l_money_mean.append(money_mean.values)
df_money_mean = pd.DataFrame(l_money_mean,index=date_list,columns=['money_mean'])
return df_money_mean

动量类

个股区间最大跌幅中位数

大底是跌出来的,大底的水准是高点腰斩,在腰斩。
下面的图是沪深300的成分股最大回撤中位数数据,可以看到,在2008年创下了最高纪录73%,与原文提到的74.44%相当接近,2014年牛市启动之前曾达到58%,最近的一次高点,也就是在2016年2月股灾2.0时,该数据达到了54.48%,而现在该值在54%左右徘徊,已经符合该底部特征的条件。
del01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#个股区间最大跌幅中位数
def f7(self,date_list):
date = date_list[-1]
#all_stock = list(get_all_securities(types='stock',date=date).index)
all_stock = get_index_stocks('000300.XSHG')
df_temp = get_price(all_stock,start_date='2005-4-1',end_date=date,fields=['close'])['close']
se_l = []
for i in date_list:
df_temp_1 = df_temp[df_temp.index<i]
#回撤
se = 1-df_temp_1[-1:]/df_temp_1.max()
#格式为series
se_l.append(se.T.quantile(0.5))
return pd.concat(se_l)

外部类

M2/总市值中位数

相当于每一元的市值,有多少M2支撑和刺激?可以理解为“施肥率”。
M2(广义货币)= 流通中的现金(即流通于银行体系之外的现金)+企业活期存款+准货币(定期存款+居民储蓄存款+其他存款+证券公司客户保证金)
简单粗暴的来讲就是指现金+活期存款+定期存款,M2越大越有钱,有了钱就可以大胆的买买买了,买酒、买药、买榨菜、买面条……M2被比作市场的肥料,这里将M2/总市值中位数进行了指标构建,相当于每平方米的施肥率。而选择中位数却不用总市值,是想弱化权重股对这个指标的过度影响,这里也是可以理解,毕竟中小盘股的参与度要高于超级大盘股。
del01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def f3_1(self,date_list):
M2_date = pd.read_excel('M2.xls')
M2_date = M2_date[(M2_date['time']>date_list[0]) &amp; (M2_date['time']<date_list[-1])]
M2_date.index = M2_date['time'].values
date_list_new = M2_date.time
market = []
for d in date_list_new:
df_temp = get_fundamentals(query(valuation.code,valuation.circulating_market_cap),date=d)
c = df_temp['circulating_market_cap'].quantile(0.5)
market.append(c)
df_3 = pd.DataFrame(market,index=date_list_new,columns=['market_50%'])
df_3['m2'] = M2_date['M2']
df_3['M2/总市值中位数'] = df_3['m2']/df_3['market_50%']
df_3 = df_3.sort_index(ascending=1)
return df_3#['M2/总市值中位数']

PE中位数和十年国债收益率倒数

比较股票和国债收益率的高低,经典的股债轮动指标。
先说说国债收益率和股市的关系,国债作为固收类产品,其收益率就是我们常说的无风险收益率,稳赚不赔,国债收益率较高的时候,会吸引资金 从高风险领域不断流入低风险的国债,造成流动性紧张,其他资产价格就会下跌;国债利率低,就会导致更多资金流入高风险领域,如间接持有股票型基金,或直接入市等,导致市场热度上升,这就是股债轮动存在的依据
下图就是市场PE中位数和十年国债收益率倒数的走势情况。
“第一、就是PE中位数随着小盘股占比越来越高,PE中位数也水涨船高。现在的PE中位数和08年PE中位数,内涵已经差了很多。”
目前,看到PE中位数已经接近前低,假设PE是以线性增长的话很明显已经跌破了之前两次低点的连线,PE中位数和国债收益倒数非常接近,但还未产生交叉
“第二、蓝色线和灰色线的交叉,注意,通常不止一次。”
前几次的数据曲线中我们看到,红线(PE中位数)几次下跌都是在熊市中下穿蓝线(10年国债收益率倒数),在市场回暖的过程中红线势必突破蓝线,我们可以看到一轮完整的牛熊市切换貌似都要触及一次蓝线,构成市场的左侧信息和右侧信号,中间即是买票上车的时间。
del01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#PE中位数和十年国债收益率倒数的比较
def f4_1(self,date_list):
deb_date = pd.read_excel('10年国债数据.xls')
deb_date = deb_date[(deb_date['time']>date_list[0]) &amp; (deb_date['time']<date_list[-1])]
deb_date.index = deb_date['time'].values
date_list_new = deb_date.time
pe = []
cir_market_ratio = []
for d in date_list_new:
df_temp = get_fundamentals(query(valuation.code,valuation.pe_ratio,valuation.circulating_market_cap),date=d)
a = len(df_temp)
b = len(df_temp[df_temp['circulating_market_cap']<45])
#print(df_temp['circulating_market_cap'].quantile(0.5))
c = df_temp['pe_ratio'].quantile(0.5)
cir_market_ratio.append(b/a)
pe.append(c)
df_4 = pd.DataFrame(pe,index=date_list_new,columns=['pe_50%'])
df_4['returns'] = deb_date['returns']
df_4['1/returns'] = 100/deb_date['returns']
df_4['cir_market_ratio'] = cir_market_ratio
#df_4['pe中位数/10年国债收益倒数'] = df_4['pe_50%']/df_4['1/returns']
return df_4[['pe_50%','1/returns','cir_market_ratio']]

人气指标:次新股的破发率。

除了全市场的估值低,各个行业也一片惨淡,几无例外。
次新股,一般是指上市一年以内的股票,可以通过以下代码获取近一年上市的所有股票,共计241只,占全市场股票6.8%,取出全市场换手率最高的前100只股票,其中次新股却有57只,对广大股民来讲,次新确实有着绝对的人气。
下面是次新股和全市场股票的PE中位数的数据,就最近一次的牛市行情来看,次新是不一样的,在2015股灾之后,依然走出了一波行情,直到2016年年底才开始跳水,这与IPO加速都有着直接的关系。数据往前推几年,牛熊交替的过程中我们看到,市场好的时候,大家各有各的花样,市场不好的时候,看起来都是一个样。最低点时次新股与全市场估值接近,就是所谓通杀。就目前来看,次新还是有着自己的想法。
del01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import datetime
#获取所有的股票信息
df = get_all_securities()
#筛选出近一年的股票
df[df['start_date']>datetime.date(2017,7,20)]


#获取次新股和全市场股票的PE中位数
def f8(self,date_list):
sec_new_pe = []
pe = []
p_ratio_l = []
all_stock = get_all_securities(types='stock',date=date_list[-1])
all_stock['fir_open'] = [get_price(stock,end_date=all_stock.loc[stock]['start_date'],count=1,fields=['open'])['open'].values[0] for stock in all_stock.index]
for d in date_list:
df_temp = get_price('000001.XSHG',end_date=d,count=252)
year_1,month_1,day_1 = int(str(df_temp.index[0])[:4]), int(str(df_temp.index[0])[5:7]), int(str(df_temp.index[0])[8:10])
year,month,day = int(str(df_temp.index[-1])[:4]), int(str(df_temp.index[-1])[5:7]), int(str(df_temp.index[-1])[8:10])
sec_new_stock_df = all_stock[(all_stock['start_date']>datetime.date(year_1,month_1,day_1)) &amp; (all_stock['start_date']<datetime.date(year,month,day))]
sec_new_stock = sec_new_stock_df.index
df_temp = get_fundamentals(query(valuation.code,valuation.pe_ratio).filter(valuation.code.in_(sec_new_stock)),date=d)
df_temp_1 = get_fundamentals(query(valuation.code,valuation.pe_ratio),date=d)
pe_sec = df_temp['pe_ratio'].quantile(0.5)
pe_all = df_temp_1['pe_ratio'].quantile(0.5)
sec_new_pe.append(pe_sec)
pe.append(pe_all)
#获取次新股当前价格
sec_new_price = get_price(list(sec_new_stock),end_date=d,fields=['close'],count=1)['close'].T
sec_new_price.columns=['fir_open']
p_ratio = sec_new_price['fir_open']/all_stock.loc[sec_new_price.index,:]['fir_open']
po_ratio = len(p_ratio[p_ratio<1])/len(p_ratio)
p_ratio_l.append(po_ratio)
df_pe = pd.DataFrame(sec_new_pe,index=date_list,columns=['sec_pe'])
df_pe['pe'] = pe
df_pe['p_ratio'] = p_ratio_l
return df_pe

市场大底,行业一片惨淡,无一例外

关于股票估值有很多的说法,就结果而言,至少到目前为止并没有一个可靠的办法,都是试图无限接近真实价值,但是结果本身甚至都不具收敛性,只能是挖掘相对好一点的方法。
比较常见的方法会以每股收益为基础,估计价格,以股票当前的价格比上每股收益,得出市盈率,其中每股收益是公司本身决定的,是个确定的值,而股票价格,就是参与者来决定的随机值,根据经验,我们可以找出历史的PE值在牛熊市、特定行业中的表现,pe = p/e转换为p=pe*e,从而得到估计的值,对当前价格进行指导。
熊市的时候,大部分行业集中低估,行业估值的80%分位数,和全市场估值水平差不多。这一特征,这里并没有进行详细的数据演示,下面展示的是不同市值、不同板块的指数成分股50%分位数的时间序列,行业的话可以参考前面的代码,把股票池更换成行业成分股即可,调整下中位数刻度,进行对比验证了。

del01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a = f()
#由于部分指数上市较晚,这里开始时间从2015年开始
date_list = a.get_tradeday_list('2015-7-17','2018-7-17')
df9 = a.f9(date_list)
df9 = df9.sort_index(ascending=1)
df9.plot(figsize=(10,7))
9.png
#市场大底,行业一片惨淡,无一例外
#行业PE估值
def f9(self,date_list):
#指数用成分股,pe值做市值加权处理
index_list = ['000001.XSHG','000300.XSHG','000016.XSHG','000905.XSHG','000852.XSHG','399006.XSHE','399678.XSHE','399005.XSHE']
index_name = ['上证','沪深300','上证50','中证500','中证1000','创业板','次新股','中小板']
pe_fin = []
for i in date_list:
pe_index = []
for j in index_list:
stock_list = get_index_stocks(j,i)
df_temp = get_fundamentals(query(valuation.code,valuation.pe_ratio).filter(valuation.code.in_(stock_list)),date=i)
pe = df_temp['pe_ratio'].quantile(0.5)
pe_index.append(pe)
pe_fin.append(pe_index)
pe_df = pd.DataFrame(pe_fin,index=date_list,columns=index_name)
return pe_df

todo

板块分化

可以进一步分析板块信息,比如下跌,龙头板块的下跌幅度,可能更具有代表性,毕竟部分板块可能一直不温不火,即使大跌,可能也没跌多少。可能导致数据失真,偏差。比如每次下跌,龙头板块下跌2/3,才到近似底部等结论。

文章大多摘录自:量化定投,工薪族逆袭之路,原文过于冗长,且和代码交织在一起。重新提炼了下重点内容。
需要详细了解代码的,参考原文。

定投原理

市场下跌时,随着定投的进行,你的平均持仓成本不断降低,等到市场回暖,很快就会超过你的平均成本,账户就有了浮盈。
需要注意的是浮盈不等于盈利,完整的定投策略需要包含买入和卖出。只有买入的方法不构成交易策略。

定投的好处是什么

绕过了择时这个大难题。
我们都清楚,如果谁能准确地预测第二天某只股票或者基金会上涨,那么即使只上涨1%,只要你投入足够的本金,也能在这一次投资中实现财富极大增大。然而难就难在谁都不敢打包票一定会涨,谁也不能确定哪一天就是最适合进场的时机。所以,与其去猜测和跟风,不如踏踏实实地采用定投的方式。
注意,定投不是没有风险,而是在长期的区间内,降低因股市波动造成投资者心理压力大、跟风投资、高买低卖等导致的亏损风险

基金定期定额投资具有类似长期储蓄的特点,能积少成多,平摊投资成本,降低整体风险。它有自动逢低加码,逢高减码的功能。

定投的内因:行情的波动与周期

社会环境的经济会以衰退-萧条-复苏-繁荣四种形式往复呈现
经济周期曲线图:
del04
由于不同国家的国情不尽相同,这种周而复始的周期曲线表现得并不完美
对应到A股票上,上证指数1990年12月19号成立,之后的经历了4次牛市,分别1993年2月、2001年6月、2007年10月、2015年6月。
del05

通过观察得到如下结论:
01,指数的起伏是好像有一定的周期性规律
02,各牛市间的时间间隔并不匀称
那问题来了,各牛市顶点的时间间隔大概在什么样的取值范围呢?未来大盘的趋势是否会符合某种规律呢?接下来我们计算一下各牛顶时间节点的平均间隔时间与偏差。
直接抛结论:

1
2
3
4
牛顶平均间隔时间: ['3042 days', '2313 days', '2800 days']
平均时间间隔 2718.33 天,即 7.45 年
平均时间间隔偏差 303.16 天,即 0.83 年
下个牛顶时间范围 2022年1月 ~ 2023年9月

通过上面的统计与可视化,可得出以下结论:
01,一轮牛熊的平均时间间隔为7.5年左右
02,4轮牛熊的时间偏差在0.8年左右
03,依此估算的下个牛顶的时间范围在2022年1月 ~ 2023年9月之间。

行情的趋势:整体向上

上证指数的价格整体趋势是向上的
del06

无脑定投收益情况:正收益高回撤

构建一个以定期定额方式投资指数的模型,看看最终的投资效果如何。

得到结论如下:
01,累计收入大于0,所以整体正收益为正(毕竟大行情趋势向上)。
02,目前离历史资产最高点差距颇大(2008年最高点,组合收益取得最大值),最近12年基本上没有超过。
说明什么?
01,定投长期看(10年级别,不考虑在最高点买入的情况),是不亏钱的,整体持续正收益
02,无脑定投,可能出现长期的大幅度回撤比如08年最高点买入,那么现在依然赔本,(十多年了,想想有多惨吧,而且是在这么些年都来,还得保持持续加仓状态)

择时波段定投:PE和PB

简单来说就是评估当前处于高位还是低位。高位时以卖出为主,低位时已买入为主
评估依据:PE,PB
可视化PE/PB曲线图

将PE/PB趋势与指数趋势一起展示,以作观察

首先注意到:
01,PE,PB正相关的
del03

02,观察二者分布,可以看到
a,PE 与 PB 有两个峰值,PE 的值主要集中在 2439 倍区间,PB 的值主要集中在 2.13.6 倍之间。
b,牛市顶时对就的 PE 与 PB 值数量相当少,并且与中间区间的值的距离相对比较远,以至于在箱线图上成为了离群点。通过这一点可以说明牛市顶一闪而过,时间非常短,产生的数据量也非常少。

刻画PE整体趋势的中等分位区间(40%~60%)
del03
PE 40 到 60 的估值范围为 32.811 ~ 49.856 之间,这里我们设定此区间为适中估值区间。

择时波段定投,简单低买高卖

模型的描述如下:
01,当 PE 处于适中估值区间时,不做任何操作;当月准备的定投金归入回收资金中。
02,当 PE 低于适中估值区间时,持续定投。
03,当 PE 高于适中估值区间时,持续卖出;卖出的金额与当月准备的定投金归入回收资金中。

del03
累计投入: 133000元
累计收益: 49155.86259384008元
最终本息累积: 182155.86259384008元
绝对收益率为: 36.95929518333841%

各年投入金额
del03
各年的收益

从上面的模型来看,整个投资区间,回收资金过少,即不能很好的在市场上涨的时候将钱落袋为安
由于买入与卖出都是按一个基准来操作的,因此,这里设想,是否可以越跌则买的越多,而越涨越卖出得越多呢

择时波段定投,杠杆低买高卖

当 PE 处于适中估值区间时,不做任何操作;当月准备的定投金归入回收资金中。
当 PE 低于适中估值区间时,持续定投;每低一个10%分位,则增加一倍倍投入。
当 PE 高于适中估值区间时,每高一个10%分位,则增加一倍卖出。在上面分析过程中发现低估值区间是高估值区间的11倍左右,因此,这里还在卖出的原倍数上乘以11.
del03
累计投入: 319000元
累计收益: 288684.23619299044元
最终本息累积: 607684.2361929904元
绝对收益率为: 90.49662576582772%
各年投入金额
del03

各年的收益

模型有了很大的进步,总体说来,资金的增长主要靠的是持续的定投与高位的加倍卖出举动
但该模型在使用了从过支到当下计算的估值区间,我们期望可以使用一种动态追踪的估值数字,来指导我们做定投。
设想:将表态的pe按照近一段时间,来评估当下pe占过去历史百分位的高度,此区间随着时间的移动,一来可以发生动态变化,二来可以不受太旧历史数据的影响。
那这个历史区间设置多久呢?在上面的计算中,我们发现一个牛熊的运动大概在7.5年左右。因此,我们在这里设置这个时间区间为7.5年。

pe动态分位图与指数高低位的比较

模型描述:
以近7.5年的pe分位来指导定投操作;
当分位值低于适中估值区间时,按倍增法买入;
当分位值处于适中估值区间时,不做任何操作;
当分位值高于适中估值区间时,按照立方指数数倍卖出。

各年投入金额

各年的收益
del03

结论

01,定投策略买入上看,属于放弃择时的买入。纯粹的无脑买入可以大概率做到不亏本(持有够久,同时避免在最高点入场),得益于我国的牛短熊长特征,这一点大概率成立。同时熊市期间,资产回撤比较严重
02,定投做一定优化,比如,根据PE,PB分位判断牛熊,然后牛市卖,熊市买,可以取得比无脑更好的效果。具体程度视策略细节而定。
第二种方式最大问题时,统计的数据基于过于,未来未必适用。包括市场整体向上,这个目前成立的,但也未必是真理,没人给打包票。

参考

基金定投原理(第②篇)价值研习所:https://zhuanlan.zhihu.com/p/113334488
基金定投的原理:https://zhidao.baidu.com/question/83460022.html
量化定投,工薪族逆袭之路(附定投模型):https://www.joinquant.com/view/community/detail/9f42d389791cc9dbd186c9f4a99c6cf4?type=1

一般理财入门书籍都会推荐采用定投方式投资,如果不知道什么是定投可以自行百度下。
现在有了quant平台,可以通过的历史数据对其进行验证核实。
本文课题:定投盈利来源归因,定投收益率和时间成正比?
如果对代码不感兴趣,可直接阅读结论部分。贴出代码主要为了让某些读者方便的进行修改回测。

获取月初价格和定投收益信息

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import pandas as pd
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

# 获取价格信息
debug = True
price_df = get_price('510300.XSHG', start_date='2015-01-30',end_date='2021-10-30',frequency='daily', fields=['close'])
debug and print('get price_df:\n',price_df.head(5))

# 获取月初价格(模拟按月定投,此处模拟每月初,买入一次)
price_df=price_df.reset_index()
price_df['yestday']=price_df['index'].shift(periods=1)
price_df=price_df.dropna()

# 判断是否月初(月份发生变化说明是月初)
def change_month(date1,date2):
return not (date1.replace('-','')[4:6]==date2.replace('-','')[4:6])
debug and change_month('2015-02-02','2015-01-30')
price_df['month_firstday']=price_df[['index','yestday']].apply(lambda x: change_month(str(x['index']),str(x['yestday'])),axis=1)
price_df=price_df[price_df['month_firstday']].drop(columns=['yestday','month_firstday'])
debug and print('keep month_firstday:\n',price_df.head())

# 每个月入金1w元
price_df['incash']=10000
# 入金转换为基金份额
price_df['fundshare']=price_df['incash']/price_df['close']
# 累计本金
price_df['incash_cumsum']=price_df['incash'].cumsum()
# 累计基金份额
price_df['fundshare_cumsum']=price_df['fundshare'].cumsum()
# 总资产=累计份额*收盘价
price_df['total_asset']=price_df['fundshare_cumsum']*price_df['close']
# 净利润=总资产-总入金
price_df['profit']=price_df['total_asset']-price_df['incash_cumsum']
# 净收益率 = 净利润/总入金=(总资产-总入金)/总入金
price_df['profit_rate']=price_df['profit']/price_df['incash_cumsum']
# 0轴,参考线
price_df['zero']=0
# 绘图,总资产,总入金,尽利润,0轴参考
debug and print('price_df:\n',price_df.head(5))
price_df[['total_asset','incash_cumsum','profit','zero']].plot()

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
get price_df:
close
2015-01-30 3.126
2015-02-02 3.056
2015-02-03 3.128
2015-02-04 3.092
2015-02-05 3.056
keep month_firstday:
index close
1 2015-02-02 3.056
16 2015-03-02 3.278
38 2015-04-01 3.744
59 2015-05-04 4.354
79 2015-06-01 4.633
price_df:
index close incash fundshare incash_cumsum fundshare_cumsum total_asset profit profit_rate zero
1 2015-02-02 3.056 10000 3272.251309 10000 3272.251309 10000.000000 0.000000 0.000000 0
16 2015-03-02 3.278 10000 3050.640635 20000 6322.891943 20726.439791 726.439791 0.036322 0
38 2015-04-01 3.744 10000 2670.940171 30000 8993.832114 33672.907436 3672.907436 0.122430 0
59 2015-05-04 4.354 10000 2296.738631 40000 11290.570746 49159.145026 9159.145026 0.228979 0
79 2015-06-01 4.633 10000 2158.428664 50000 13448.999409 62309.214264 12309.214264 0.246184 0

字段解释:
index:日期,每月第一个交易日加仓
close:当日收盘价,
incash:当日汇入现金(定投增加本金)
fundshare:基金总份额
incash_cumsum:累计汇入现金(定投的总投资额,定投金额*定投期数)
total_asset:总资产,就是总份额*基金当前净值
profit:总利润,总资产-总入金
profit_rate:收益率,(总资产-总入金)/总入金

总资产,总入金,总收益对比图:

先解释这张图含义:
总资产:最上面的蓝线:总资产,看起来很诱人,45度向上,诱人的主要原因是不断追加的资金(橙线的45度直线,那代表总投入资金线)。
总入金(定投资金):次之的橙线,由于不断投入的总资金,固定斜率斜线(固定资金定投)。
总盈利:红线,总资产-总投入=盈利部分
底部0轴:盈亏平衡轴,轴上部表示盈利,下部表示亏损(主要为了上下好比对)。

结论:定投hs300,从15年到今年,整体上是盈利的

在补充一张图,这张图是沪深300的定投收益率图
回测区间:start_date=’2007-06-30’,end_date=’2022-06-09’
收益率:相对当时总投入本金收益率
总体来看,正收益没毛病,但早期有高达40%的回撤幅度,哪怕在横轴右侧1/5处,相比开始定投,已经过了12年了,小孩都可以打酱油了,还位于盈亏平衡点上。意味着那几天资金和总投入资金相等(相比存银行的话,等价于亏本了)。这是相当凄惨的情况,相当于12年资金免费让人家用了。当然这是比较极端的例子,意思就是别把定投当做灵丹妙药
del03

定投盈亏归因分析

那么定投盈利主要来源来自哪里?得益于最近几个月行情大涨?还是几年前的某一笔英明的投资
代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 盈亏归因分析
price_df['close'].plot()
# 01,笔视图,各笔交易盈亏图
# 每笔盈利=最新价格*购买份额-成本
new_price=price_df['close'].values[-1]
price_df['deal_profit']=new_price*price_df['fundshare']-10000
price_df[['deal_profit','zero']].plot()

# 02,时间视图,各月份盈利图
# 各月份盈亏情况=总资产.diff(1)
price_df['total_asset_diff']=price_df['total_asset'].diff(1)
price_df[['total_asset_diff','zero']].plot()

输出:
基础行情曲线

每一笔角度的投资收益视图:从左到右的每一个点,表示当时那一笔投资到今天的收益
del01

各个时间的投资收益视图(类似各月的涨跌情况):

结论:
第一图,从每一笔交易的视角来看,买入价格越低越好(废话,不解释),所以远期的买入价还是有点重要的。每笔交易视角:盈亏=当前点价格-买入点价格。
第二图,从时间的视角来看,越往后价值波动范围越大,由于持有份额增大了,所以同样的基础标的价格变化,导致资产变动更大
结论:对定投组合价值影响最大的是当下价格,其次是远期价格

宏观上看,定投类似时间加权的买入策略时间在哪个价格(区间)停留最多,持股成本就最接近那个价格。如果把行情线看做铁丝的话,那么定投成本类似铁丝的质心
从这个角度,我们可以大概预估定投的收益情况,不需要复杂的模拟,对一段行情的定投收益有大致预估。
显然对于不断下跌的行情,由于持续下跌,持续加仓,整体质心持续位于当前行情的上部,会持续保持亏损,且亏损额会原来越大

收益率正比时间?

那么大家耳熟能详的,定投适用于长期投资,投资期间越长收益越高,正确吗
不妨统计下,投资时长和定投收益是否正相关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 全量分析

price_df=price_df.set_index('index')
profit_df=pd.DataFrame(columns=price_df.index.values)
for start_date in price_df.index.values.copy():
# 累计本金
price_df['incash_cumsum']=price_df['incash'].cumsum()
# 累计基金份额
price_df['fundshare_cumsum']=price_df['fundshare'].cumsum()
# 总资产=累计份额*收盘价
price_df['total_asset']=price_df['fundshare_cumsum']*price_df['close']
# 净利润=总资产-总入金
price_df['profit']=price_df['total_asset']-price_df['incash_cumsum']
# 净收益率 = 净利润/总入金=(总资产-总入金)/总入金
price_df['profit_rate']=price_df['profit']/price_df['incash_cumsum']
# change01:profit=>profit_rate
profit_df.loc[start_date]=price_df['profit']
# profit_df.loc[start_date]=price_df['profit_rate']
price_df.drop([start_date], axis=0,inplace=True)

代码

1
profit_df

输出:

这是一个81行81列的大表格,
纵轴:定投开始时间
横轴:定投结束时间
中间的数值:从“定投开始时间”到”定投结束时间”期间 的 总收益
暂不关注具体盈亏金额细节,只关注盈亏是否和时间正相关,以及持有多久盈利概率最大,最长持续亏损时间等。
代码:

1
2
3
4
5
6
# 统计各个定投时间对应的收益情况
periodic_profit=[set() for i in range(profit_df.shape[0])]
# 将profit_df中的value填充到periodic_profit对应定投时长的set集合中
for x in range(profit_df.shape[0]):
for y in range(x+1,profit_df.shape[1]):
periodic_profit[y-x].add(profit_df.iloc[x,y])

代码

1
periodic_profit[1]

输出:

1
2
3
4
5
6
7
8
9
10
{-1722.4260738182602,
-1671.8266253869951,
-1221.590909090908,
-1181.929181929183,
-989.3114591101184,
-910.2402022756032,
-883.0880311024739,
-821.3820078226818,
-746.6943220119283,
-646.8842729970347,,,,,}

为了使用seaborn绘图,对periodic_profit的数据呈现方式做转换,转成pandas的数据
比如:

1
2
3
4
5
6
7
8
periodic_profit=[set(),set(1.1,1.2,1.3),set(1.6.1,7)]
=>
dataframe
1,1.1
1,1.2
1,1.3
2,1.6
2,1.7

代码

1
2
3
4
5
6
7
8
# change01:profit=>profit_rate
periodic_profit_df=pd.DataFrame(columns=['priod','profit'])
# periodic_profit_df=pd.DataFrame(columns=['priod','profit_rate'])
for i in range(len(periodic_profit)):
if periodic_profit[i]:
for x in periodic_profit[i]:
periodic_profit_df.loc[len(periodic_profit_df)] = [int(i),x]
periodic_profit_df.tail()

输出

1
2
3
4
5
6
priod	profit
3235 78.0 297937.758369
3236 78.0 282156.112183
3237 79.0 285189.085626
3238 79.0 288350.483911
3239 80.0 291429.268872

代码

1
2
3
4
import seaborn as sns
# change01:profit=>profit_rate
sns.boxplot(x = 'priod', y= 'profit', data = periodic_profit_df)
# sns.boxplot(x = 'priod', y= 'profit_rate', data = periodic_profit_df)

输出:

图片说明:
x轴是定投的周期数,月为单位(1表示定投一个周期(月),20表示定投20个周期(月))
y轴是一个收益金额的分布

1
为何是分布,因为定投一个月的,和定投20个月的都对应了一个收益集合,比如第1月开始定投,到第21月结束会得到1个收益率数据。从第2月开始到第22月结束,也会得到1个收益率数据。所以对于每个x轴数据,都对应了一连串的收益率数据(当然,x越小对应的收益率数据越多,x越大,比如80,就只有第1个月开始和第81个月结束的收益数据,只有1份数据)  

这个角度来看,乍一看,好像是成立的,随着时间增加,收益均值在增大
但是别忘了,随着时间增大,定投总金额在增加,所以总收益增大是正常的。我们需要考虑的是相对总本金的收益率,这个漏洞在change01中将修复。
而且还要留意,越长期的统计,远期由于数量不足,计算进来的权重越小,比如,70个月的,只会有:从第1月到第70个月,第2月到第71个月等这些组合。
一方面样本偏少,统计意义较低。
另一方面,由于近期表现好(且远期表现够差),所以70月,71月等都较高,使得长期统计结果退化为近期行情表现(当然是相对远期,如果远期更强势,目前依然负值,但近期表现整体占高影响力不变,毕竟总资金量是越来越大的)

change01:改为收益率

profit_rate 代替profit
change01:profit=>profit_rate
结果图:

结论:宏观结果上看是正确的(定投时长和收益成正比),但由于标的本身是宏观上涨的标的。所以基本上持有型策略都会录得较好表现。可以说明在整体趋势向上,或者当前价格较高时,定投收益和时间成正比。但对于整体行情下跌的行情,这个大概率是不成立的。这个结果个人觉得体现不出定投的优越性
定投本质而言,时间角度是放弃了择时(由于按时间定投的)。另一方面,由于越后期总资金越多,所以最终收益受近期行情的影响较高(这里的近期也是相对的, 比如2年的定投,可能近半年都算近期了)。我们重点是有这个概念,定投的卖出点尤为重要。这个不在解释。找到高点跑路,否则一旦跌下来,可能之前定投浮盈全部作废了。但那个才算是高点呢?所以定投也未必就是懒人策略,如果挣点小钱钱,可以设定目标,比如年化10%就跑路也行。总之有明确目标,不能持有不动,一旦牛市来临,兑现目标就立场。等下一个漫漫熊市再建仓。 本质上近似看做一个抄底型策略,低买高卖的波动策略

结论

定投盈利来源归因:从权重来看,近期价格影响力更大,当然,远期价格也很重要。当前价格相对行情曲线质心位置,也就是说现价占了很大权重
定投收益率和时间成正比:行情趋势向上时成立(废话对吧),如果行情水平宽幅震荡,原始的定投无法产生额外收益(后面突破了就是另一回事了)。

定投只是省事了而已(相对网格等策略,定投更简单。同时,也避免了单次买入并持有面临的买入纠结问题(本质是:买入并持有策略中,买入时点非常重要,而定投通过分散买入点解决了这个问题,或者说缓解了这个问题)),并没有传说中的那么神奇,既无法保证高额收益,也无法保证一定会盈利(行情稳定向上时可以保证,但简单的买入并持有一样能保证)。

说明定投本身适合问稳定向上的标的(听起来向废话),避免高波动标的,所以本身更适合基金等标的(不会破产倒闭)。
最大优势,在于定投符合现实中现金流,尤其打工人的。
综合结论,适合(短期不确定,但)长期看好的标的,比如2年前的黄金(当前已涨上去了,不赶趟了),当前的粮食(气候极端化)等。 选择低波动性标的,没有倒闭清盘风险的标的。

其他定投文章

定投真的有效吗?沪深300定投15年测试:https://www.joinquant.com/view/community/detail/6eba65eb8c5406580978ec040b100de4?type=1
总结
经典的傻瓜定投确实不太聪明
只买不卖是不行滴
展望
好的定投并非是无脑买的,而应该是越低越买,越高越卖(波动行情这样有额外优势,单边行情这样会完蛋,除非资金无限大并且标的不能归零)
成于时间,败于资金,量化模型揭开基金定投的真相:https://www.joinquant.com/view/community/detail/a4479781568585f70e2cf82d71d47693?type=1
基金定投理论的前提条件,一是时间足够长,二是资金足够多。
基金定投是时间给我们的承诺,但是并没有承诺这段时间有多长。如果在定投的过程中,无法再继续投入资金而中断,就可能出现上面的情况。红色箭头就是投资中断的时间点,后面价格下跌拉低成本的机会,完全享受不到,但是下跌带来的资金回撤可是一分钱没少地承受着。而这种情况下,在绿线下面的任何一个时间点投入,都是跑赢定投方式的。资金意外的出现,让定投的盈利价值大幅缩水,而且还用大幅回撤考验我们的心理承受能力。在后面的8个月时间内,遗憾地止损退场,也是有可能发生的。
因此,如果没有足够多的钱,基金定投很可能不那么美好。

基金定投完全不考虑择时问题,可以无脑定投的,并不是一个客观的观点。这个结论存在的前提还是资金,你有足够的资金,不需要考虑流动性的资金。最后,请大家体会下面几句话。
基金定投是我们和时间签下的契约,但是并没有约定期限
时间给我们盈利的承诺,时间也带给我们风险
只有足够的资金才能抵抗这个风险

基金定投的真相-收益与风险:https://www.joinquant.com/view/community/detail/d1adceb39b045a15f2ec119cfeedbb9c?type=1
结论:
定投的收益不如直接持有,这和前文的逐年结果是一致的
定投的波动和回撤都明显小于直接持有
从夏普角度看,除了13和14年外,定投的效果没有显著优于直接持有指数
定投的频率对结果影响不是特别大,以每月以下的频率定投为佳