-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 增加backtestlog模块,用于输出回测日志时,将时间替换为回测时间 * 增加行情预取功能 * 增加回测报告中绘制自定义指标功能(仅支持Scatter)
- Loading branch information
1 parent
2745b44
commit 81629d4
Showing
14 changed files
with
483 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"attachments": {}, | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"\n", | ||
"Nirvana Systems把Conners RSI称作终极技术指标,并在它的网站上发布了关于如何生成和使用这一指标的文章。著名的回测框架 backtrader也在文档和示例中引用了这篇文章。TradingView也内置了这一指标。\n", | ||
"\n", | ||
"为什么Conners RSI被称为终极技术指标,它有哪些优势?成功背后的原理是什么,又该如何实现这一指标呢?这篇文章就为你介绍。\n", | ||
"\n", | ||
"Conners RSI是在标准RSI的基础上,混合了另外两个指标得到的。\n", | ||
"\n", | ||
"第一个指标就是Streaks。它本质上是统计连续上涨或者下跌的周期数,并将该指标的RSI作为一个分数。我们知道,股票连涨周期数越长,则越可能反生反转(即下跌);反之亦然。大家可以按照我们在《数据分析与Python实现》那几节课中,介绍的PDF/CDF的方法,来自行估计当某个标的连续上涨N天后,接下来继续上涨的概率有多大。\n", | ||
"\n", | ||
"下图显示了每日收盘价,及由此计算的streaks指标:\n", | ||
"\n", | ||
"![50%](https://i.stack.imgur.com/SIQUq.png)\n", | ||
"\n", | ||
"第二个指标就是今天的涨跌幅,在过去一段时间内的的涨跌幅中的排名。它从另一个维度,描绘了当前市场的强弱。如果在过去20天内,只有3天的涨幅低于今天,那么今天的相对强弱就是15%,次日反转可能性大;如果17天的涨幅低于今天,那么今天的相对强弱就是85%,次日下跌的可能性变大。\n", | ||
"\n", | ||
"这两个指标加上经典RSI,就合成了Conners RSI:\n", | ||
"\n", | ||
"$$\n", | ||
" CRSI(3, 2, 100) = [RSI(3) + RSI(Streak, 2) + PercentRank(100)] / 3\n", | ||
"$$\n", | ||
"\n", | ||
"与单一的RSI只计算了上涨与下跌的空间相比,streaks指标则强调了上涨和下跌的时间周期 -- 只有一轮趋势的空间和时间都到位,才有更有可能发生反转;而PercentRank往往可以寻找到行情的加速阶段:一旦出现明显的涨跌异常 -- 这也是一段行情进入尾声的标志之一。正因为如此,Conners RSI才被认为是更有效的摆动类指标,可以比较精准地捕捉顶和底。\n", | ||
"\n", | ||
"Conners RSI的实际表现如何?我们拿最近1000天的沪指进行测试:\n", | ||
"\n", | ||
"![50%](https://images.jieyu.ai/images/2023/08/corners_rsi.png)\n", | ||
"\n", | ||
"backtrader的回测表明,最近4年以来(近似于1000个交易日),沪指仅上涨5.76%,但通过cornner's RSI策略抄底逃顶,我们在指数上竟然获得超过44%的收益。如果是对个股进行操作,收益很可能是数倍。\n", | ||
"\n", | ||
"由于这段时间毕竟沪指是上涨的,如果遇到行情极端不好,访策略表现如何?近两年来恒生指数就宛如下跌的飞刀,假设我们要强接飞刀,表现又是怎样?\n", | ||
"\n", | ||
"![50%](https://www.taindicators.com/blog/2022/connors/2800hk.png)\n", | ||
"<caption>图片来源: www.taindicators.com</caption>\n", | ||
"\n", | ||
"结论是,还是不要去接下跌的飞刀。但即使如此,即使是遇到下跌的飞刀,corner's RSI的表现也大大超过指数。" | ||
] | ||
}, | ||
{ | ||
"attachments": {}, | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"from coursea import *\n", | ||
"await init()\n", | ||
"\n", | ||
"class ConnerRSIStrategy(BaseStrategy):\n", | ||
" def __init__(self, url: str, sec: str, **kwargs):\n", | ||
" self.sec = sec\n", | ||
" self.pstreak = kwargs.get(\"pstreak\", 2)\n", | ||
" self.prank = kwargs.get(\"prank\", 20)\n", | ||
" \n", | ||
" self.low_watermark = kwargs.get(\"low_watermark\", 13)\n", | ||
" self.high_watermark = kwargs.get(\"high_watermark\", 58)\n", | ||
" super().__init__(url, **kwargs)\n", | ||
" \n", | ||
" \n", | ||
" async def predict(self, frame: Frame, frame_type: FrameType, i: int, barss, **kwargs):\n", | ||
" bars = barss[self.sec]\n", | ||
" close = bars[\"close\"]\n", | ||
" \n", | ||
" rsi = ta.RSI(close, 6)\n", | ||
" streak = Streak(close)\n", | ||
" rsi_streak = ta.RSI(streak, self.pstreak)\n", | ||
" \n", | ||
" pclose = close[-self.prank:]\n", | ||
" prank = fsum(x < pclose[-1] for x in pclose)/ pclose\n", | ||
" \n", | ||
" crsi = (rsi[-1] + rsi_streak[-1] + prank)/3.0\n", | ||
" \n", | ||
" print(\"crsi is\", crsi)\n", | ||
" if crsi <= self.low_watermark:\n", | ||
" await self.buy(self.sec, money=self.cash, order_time=tf.combine_time(frame, 14, 56))\n", | ||
" elif crsi >= self.high_watermark:\n", | ||
" avail = self.available_shares(sec, frame)\n", | ||
" await self.sell(self.sec, vol = avail, order_time=tf.combine_time(frame, 14, 56))\n", | ||
" \n", | ||
"start = datetime.date(2023, 1, 4)\n", | ||
"end = datetime.date(2023, 4, 14)\n", | ||
"\n", | ||
"cs = ConnerRSIStrategy(cfg.backtest.url, \"002344.XSHE\", start=start, end=end, frame_type=FrameType.DAY, baseline=\"399300.XSHE\")\n", | ||
"await cs.backtest(portfolio = [\"002344.XSHE\"], n=30)\n", | ||
"await cs.plot_metrics()" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"language_info": { | ||
"name": "python" | ||
}, | ||
"orig_nbformat": 4 | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
""" | ||
回测时,打印时间一般要求为回测当时的时间,而非系统时间。这个模块提供了改写日志时间的功能。 | ||
使用方法: | ||
```python | ||
from omicron.core.backtestlog import BacktestLogger | ||
logger = BacktestLogger.getLogger(__name__) | ||
logger.setLevel(logging.INFO) | ||
handler = logging.StreamHandler() | ||
# 通过bt_date域来设置日期,而不是asctime | ||
handler.setFormatter(Formatter("%(bt_date)s %(message)s")) | ||
logging.basicConfig(level=logging.INFO, handlers=[handler]) | ||
# 调用时与普通日志一样,但要增加一个date参数 | ||
logger.info("this is info", date=datetime.date(2022, 3, 1)) | ||
``` | ||
上述代码将输出: | ||
``` | ||
2022-03-01 this is info | ||
``` | ||
使用本日志的核心是上述代码中的第3行和第9行,最后,在输出日志时加上`date=...`,如第15行所示。 | ||
注意在第9行,通常是`logging.getLogger(__nam__)`,而这里是`BacktestLogger.getLogger(__name__)` | ||
如果上述调用中没有传入`date`,则将使用调用时间,此时行为跟原日志系统一致。 | ||
!!! warning | ||
当调用logger.exception时,不能传入date参数。 | ||
""" | ||
|
||
import datetime | ||
import logging | ||
from typing import Union | ||
|
||
from coretypes import Frame | ||
|
||
|
||
class BacktestLogger(object): | ||
logger = None | ||
|
||
def __init__(self, name): | ||
self._log = logging.getLogger(name) | ||
|
||
@classmethod | ||
def getLogger(cls, name: str): | ||
if cls.logger is None: | ||
cls.logger = BacktestLogger(name) | ||
|
||
return cls.logger | ||
|
||
@classmethod | ||
def debug(cls, msg, *args, date: Union[str, Frame, None] = None): | ||
cls.logger._log.debug( | ||
msg, *args, extra={"bt_date": date or datetime.datetime.now()} | ||
) | ||
|
||
@classmethod | ||
def info(cls, msg, *args, date: Union[str, Frame, None] = None): | ||
cls.logger._log.info( | ||
msg, *args, extra={"bt_date": date or datetime.datetime.now()} | ||
) | ||
|
||
@classmethod | ||
def warning(cls, msg, *args, date: Union[str, Frame, None] = None): | ||
cls.logger._log.warning( | ||
msg, *args, extra={"bt_date": date or datetime.datetime.now()} | ||
) | ||
|
||
@classmethod | ||
def error(cls, msg, *args, date: Union[str, Frame, None] = None): | ||
cls.logger._log.error( | ||
msg, *args, extra={"bt_date": date or datetime.datetime.now()} | ||
) | ||
|
||
@classmethod | ||
def critical(cls, msg, *args, date: Union[str, Frame, None] = None): | ||
cls.logger._log.critical( | ||
msg, *args, extra={"bt_date": date or datetime.datetime.now()} | ||
) | ||
|
||
@classmethod | ||
def exception(cls, e): | ||
cls.logger._log.exception(e, extra={"bt_date": ""}) | ||
|
||
@classmethod | ||
def log(cls, level, msg, *args, date: Union[str, Frame, None] = None): | ||
cls.logger._log.log( | ||
level, msg, *args, extra={"bt_date": date or datetime.datetime.now()} | ||
) | ||
|
||
@classmethod | ||
def setLevel(cls, level): | ||
cls.logger._log.setLevel(level) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.