回测好,为什么实盘不靠谱?

用IT技术玩金融系列文章,将介绍如何使用IT技术,处理金融大数据。在互联网混迹多年,已经熟练掌握一些IT技术。单纯地在互联网做开发,总觉得使劲的方式不对。要想靠技术养活自己,就要把技术变现。通过“跨界”可以寻找新的机会,创造技术的壁垒。

金融是离钱最近的市场,也是变现的好渠道!今天就开始踏上“用IT技术玩金融”之旅!

关于作者:

  • 张丹(Conan), 程序员R,Nodejs,Java
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/finance-backtest

前言

经常看到做量化的朋友,晒出各种漂亮的回测曲线,准备一夜发家,但开始真金白银地去交易时,就会亏得一塌糊涂。

回测好,为什么实盘不靠谱?这里其实有很多的坑,不用钱买点教训,你是不会明白的。有经验的量化交易员,都是用钱磨炼出来。把你的回测慢慢贴近实盘,让回测结果越来越可靠。

本文为量子金服约稿文章。

目录

  1. 实例复盘
  2. 问题在哪?
  3. 量化理论和模型

1. 实例复盘

回测好,真的是因为策略好吗?

我们举个例子,你可能用到某个回测工具或平台,顺手复制了一个demo的代码,一点运行,就能跑出10%的收益率。接下来,你花了一个晚上彻夜研究,把参数用机器学习的方法来优化,黎明时,终于把收益率提高到了40%。虽然一夜没睡,但心里却是无限地兴奋,觉得多年所学的IT技术终于可以实现赚钱的理想了,金融市场不过如此。明天就先把1个月工资赚出来,下个月就辞职,再也不用看S*领导的脸色,真是浪费生命了。

有过上面经历的同学,我想不在少数吧。第二天,就把打工2年多辛辛苦苦攒到的10万块投到了股市中。谁想股市风云变幻,不仅市场不仅没按照模型的方向走,而且又赶上严监管、去杠杆、大股东减持等等一系列的样本外事件发生,2个月后不仅没有赚到当初设想的钱,甚至亏损到了20%,感情上已经受不了,拒绝了之前定下的止损的规则,又经历了几周的连续下跌,最后亏损到达50%。

每天心都在滴血,连续3个月都是吃不好、睡不好,最后一咬牙全部割肉了。开始全盘怀疑自己,自信心被打击到了负值,封账号,再也不碰股市了。

2. 问题在哪?

那么,为什么回测好的策略实盘就不这么不靠谱呢?可能有以下几点原因。

2.1 算错了

当你的回测出现有显著的盈利时,最大的可能是你算错了。比如,在计算时写错了正负号、不应该用年化的时候用了年化的值,没有严格区别复权数据与非复权数据的区别、交易的周期没对齐、无风险收益率取值过小、四舍五入时保留位数过少、使用向理计算时出现的问题、NA值没有处理、使用了来自互联网的未经验证的数据等等。

总之各种的细节,都会让你的回测出错,而且如果你不理解每个指标的金融含义,你甚至都不知道自己错了。

2.2 未来函数

如果每个计算细节你都了解了,回测结果依然非常好,还是先别激动,检查一下是不是用到未来的函数。
使用到未来函数也是很常见的一个问题,而且通常都是不知不觉的。比如,我们会经常听到股评分析师说:“在牛市开始时建仓买入,在到达最高点时卖出”,这其实就是用到了未来函数。在实际的交易过程中,我怎么会知道,什么时候是牛市的开始,又怎么会知道最高点是3600点还是5700点?如果我真的知道了,我还做什么量化交易,早就环游世界去了。

我们很多时候会都用已经知道的市场信息做回测,但实际交易时,你并不知道市场是什么样子的,会向什么方向变。比如,我们现在来看2017年上半年招商银行涨的很好,那么我就针对银行股开始做回测,而且给招商银行加大权重。在一切数据都算对的情况下,回测的资金曲线相当的漂亮,半年获得了30%以上的收益率,而且最大回撤控制在3%以内,夏普、詹森Alpha也都很不错,这些指标都表示了我的主动管理能力很强,我是个牛逼的基金经理。

真的是这样吗?你在不经意用到了未来函数,才使得你发现了招商银行,然后再对银行股做了回测,获得了较好的资金曲线。所以,这不是能力,也不是运气,是犯规了。

2.3 过拟合

从IT程序员转到金融的量化分析师们,在很多情况下都会用纯IT的方法,来解决金融建模的问题。比如,做了5年推荐系统的推荐算法专家,非常擅长用机器学习的方法,来找到数据之间的关系。于是就以纯数据的方式来切入,脱离金融的投资学理论,导致了数据的过度使用。通过历史数据试图预测未来,而且找到一条完美的投资曲线,穿过所有的样本点,最后将导致过拟合。

从IT转行到金融的朋友,通常有个特点,就是动手能力强,数据来就先丢到模型里,才不管到结果底能不能解释,反正我的回测曲线很漂亮。特别是深度学习,增强学习等方法的崛起,让程序员群体一下子高大上起来,通过一种算法,升维升维再升维,就能通吃所有的单一分类算法模型。这样的结果就是过拟合。回测曲线必然是非常漂亮的,但到实际环境一运行,就只能用惨不忍睹来形容了。

2.4 策略周期

从投资的角度,每种策略都有自己适应的场景。在合适的场景下,选到了适合的资产,那么你的策略会表现的非常棒。但是实际的金融市场是轮动的,资产配置随大的金融周期轮动,股票市场随着行业板块轮动。有可能你在回测的时候选对了风口,赶上了趋势,而实盘时候错过风口或者选错了金融资产,那么就会事与愿违了。

比如,你的策略就是研究债券的,从2016上半年到2017年上半年,选出了鹏华全球高收益债(000290)这支QDII基金,比国内的大部分债基表现都抢眼,走势非常稳定,持续上升,你坚定的买进加仓。但是不凑巧的是,你刚买入完,人民币就进入到了升值的区间,虽然债券本身是很稳定的,但人民币持续走强,由于汇率的影响让这支债基天天亏钱。如果你又懂债券又懂外汇,这个点没想到是能力问题。如果你完全不懂外汇,单从债券的角度考虑,那么就不是能力问题,也不是模型不行,而是运气太差,没把握到轮动的周期。

2.5 真实交易环境

真实的交易,是会被各种情况所影响的。当你的交易量过大时,你会影响市场,这时你的交易就会发生偏离,实际市场交易的冲击成本会比你回测时看到的成交量大得多,而且冲击成本又是很难被模拟和计算的。

当你购买流动性不好的金融产品时,模型的信号出来了,但是实际你却买不到,或者卖不出去,当你被迫用对手价来成交时,就会有比较大的滑点。滑点对于高频交易来说是致命的,对于长周期的趋势交易策略,倒是影响不大。

手续费也是一个不容小觑的因素,2017年7月开始黑色系商品期货被猛炒,焦炭、焦煤的平今手续费上调至3倍,铁矿石平今手续费上调到2倍。这种政策性的调整,在研发模型时是不可预知的,平今手续费的上调,直接就拍死了日内模型。2015年调整的股指期货的40倍手续费,几乎把所有的投机的模型都干掉了。

股票市场也很多真实交易环境的特殊性,比如2016年初开始试行的熔断机制,一共4天,发生了多次恐慌性的挤兑,上证指数下跌488.87点,相比4天前收盘点位下跌了13.8%,A股蒸发市值逾6万亿。

当然,也有一些真实交易环境中的乌龙指,有时会我们带来一些额外的惊喜。

真实交易环境是复杂的,也是很难在回测环境中模拟的,所以要深刻了解金融市场、了解市场运作的原理,你才能规避真实交易环境与回测环境中的差异点。

2.6 人工干预

还有一种情况,就是人工干预。当你建好一个模型,应用到实盘的时候,你要充分地相信你的模型,并且严格的执行。每当遇到回撤的时候,你依然要相信你的模型,坚持模型的策略。

如果你心理抗不住,开始干预时,也会造成回测与实盘的偏差。这个时候,就很难判断是模型不靠谱,还是人不靠谱了。每当我在干预实盘模型的时候,调来调去,觉得及时止盈止损了,实际上是在破坏自己的规则,更加影响了策略的稳定性。

当然,可能还有更多的原因,让回测到实盘有很大的差距。我们需要认真地思考,把每个细节都去实践,慢慢地才能让你的回测越来越接近实盘的效果。

3. 量化理论和模型

从专业角度来讲,投资就是要找到市场的规律,而规律的本质是符合金融市场的简单逻辑。赚钱的模型,通常都是很巧妙的把规律进行量化。

任何模型或者理论,第一步都是提出假设,定义应用场景,解决什么问题。

  • 趋势追踪模型,是用移动平均来反应金融产品发展趋势的规律,请参考文章二条均线打天下
  • 均值回归模型,是价格偏离均衡价格水平一定程度后向均衡价格靠拢的规律,请参考文章 均值回归,逆市中的投资机会 。
  • 追涨杀跌模型,是一定时期内强势资产能够延续上涨的规律,请参考文章 R语言构建追涨杀跌量化交易模型
  • 配对交易模型,是两个具有均衡关系的金融产品,价格走势出现背离的规律,请参考文章 R语言构建配对交易量化模型

如果我们能够做出正确的假设,当然是可以赚到钱的,能够赚大钱还是赚小钱,就是运气了。

《海龟交易法》流行了很多年,至今仍然被广大的交易员所使用,书中所讲述是就是金融市场的规律。假设条件越简单,回测可能越靠谱,会越贴近实盘。

转载请注明出处:
http://blog.fens.me/finance-backtest

打赏作者

This entry was posted in 金融

<think>我们注意到用户对之前的满意,要求更靠谱的解决方案。我们需要重新审视问题:用户想要在QDialog中的QTableWidget中双击行来设置整行高亮,并且之前尝试使用样式表的属性选择器没有成功。 关键点: 1. 用户希望使用样式表中的属性选择器(如`[data-highlight="true"]`)来实现高亮。 2. 但是,QTableWidgetItem是QObject,无法设置属性。 替代方案:我们可以通过设置QTableWidget的样式表,并利用选择器基于自定义数据角色来改变样式。但是,标准的样式表选择器支持基于数据角色(如Qt::UserRole)的条件。 因此,我们到使用自定义委托的方法,但可以尝试另一种思路:使用QSS结合动态属性,但目标对象是QTableWidget的视口(viewport)或表头?这也可行,因为属性选择器作用在控件上,而是每个项。 更可靠且直接的方法:自定义委托。 我们提供一种更简洁的自定义委托实现,并解释如何实现行高亮。 步骤: 1. 创建一个自定义委托,重写paint函数。 2. 在委托中,检查每一行(具体是每个单元格)的自定义数据(例如Qt::UserRole)是否被设置为高亮状态。 3. 如果是,则改变绘制样式(背景色和文字颜色)。 同时,我们需要在双击行时切换该行所有单元格的自定义数据(例如Qt::UserRole+1)的值。 下面给出一个更简洁的代码示例,并确保能够运行。 注意:我们使用Qt::UserRole+1作为存储高亮状态的角色。 完整代码:</think>我理解您对之前的满意,我将提供更直接、更实用的解决方案,专注于您的问题核心:如何在QTableWidget中实现特定行的高亮效果。 ### 直接有效的解决方案:使用自定义数据角色实现行高亮 ```cpp #include <QApplication> #include <QDialog> #include <QTableWidget> #include <QStyledItemDelegate> #include <QPainter> #include <QVBoxLayout> #include <QHeaderView> // 高效委托类 - 仅处理高亮逻辑 class HighlightDelegate : public QStyledItemDelegate { public: explicit HighlightDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { // 获取当前行的高亮状态 const bool isHighlighted = index.data(HIGHLIGHT_ROLE).toBool(); QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // 仅当需要高亮时修改样式 if (isHighlighted) { opt.backgroundBrush = QColor(0, 128, 0); // 绿色背景 opt.palette.setColor(QPalette::Text, Qt::white); // 白色文字 } QStyledItemDelegate::paint(painter, opt, index); } // 定义高亮数据角色 (避免使用Qt::UserRole) enum CustomRoles { HIGHLIGHT_ROLE = Qt::UserRole + 100 }; }; class ModelSetDialog : public QDialog { Q_OBJECT public: explicit ModelSetDialog(QWidget *parent = nullptr) : QDialog(parent), tableWidget(new QTableWidget(this)) { // 基本表格设置 tableWidget->setRowCount(10); tableWidget->setColumnCount(3); tableWidget->setHorizontalHeaderLabels({"ID", "Name", "Status"}); tableWidget->verticalHeader()->setVisible(false); // 应用委托 tableWidget->setItemDelegate(new HighlightDelegate(this)); // 应用样式表 (去除高亮部分) tableWidget->setStyleSheet(R"( QTableView { background-color: rgb(18, 18, 18); gridline-color: #333; alternate-background-color: #222; } QTableView::item { background-color: rgb(41, 41, 41); color: #ddd; border: none; } QTableView::item:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #333, stop:1 #222); } QHeaderView::section { background-color: #1a1a1a; color: #ccc; padding: 4px; border: 1px solid #333; } )"); // 填充数据 for (int row = 0; row < 10; ++row) { for (int col = 0; col < 3; ++col) { QTableWidgetItem *item = new QTableWidgetItem( QString("%1-%2").arg(col == 0 ? "ID" : col == 1 ? "Item" : "Status").arg(row) ); tableWidget->setItem(row, col, item); } } // 设置第3行高亮 setRowHighlight(2, true); // 布局 QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(tableWidget); setLayout(layout); resize(600, 400); } // 设置行高亮状态 void setRowHighlight(int row, bool highlight) { if (row < 0 || row >= tableWidget->rowCount()) return; for (int col = 0; col < tableWidget->columnCount(); ++col) { QTableWidgetItem *item = tableWidget->item(row, col); if (item) { // 设置高亮状态数据 item->setData(HighlightDelegate::HIGHLIGHT_ROLE, highlight); } } tableWidget->viewport()->update(); // 立即刷新 } // 切换行高亮状态 void toggleRowHighlight(int row) { if (row < 0 || row >= tableWidget->rowCount()) return; QTableWidgetItem *firstItem = tableWidget->item(row, 0); if (firstItem) { bool current = firstItem->data(HighlightDelegate::HIGHLIGHT_ROLE).toBool(); setRowHighlight(row, !current); } } private: QTableWidget *tableWidget; }; // 使用示例 int main(int argc, char *argv[]) { QApplication app(argc, argv); ModelSetDialog dialog; dialog.setWindowTitle("Model Set Dialog"); dialog.exec(); return 0; } ``` ### 核心实现解析 1. **高效委托实现**: ```cpp void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { const bool isHighlighted = index.data(HIGHLIGHT_ROLE).toBool(); if (isHighlighted) { // 仅修改高亮行的样式 opt.backgroundBrush = QColor(0, 128, 0); opt.palette.setColor(QPalette::Text, Qt::white); } QStyledItemDelegate::paint(painter, opt, index); } ``` 2. **自定义数据角色**: ```cpp enum CustomRoles { HIGHLIGHT_ROLE = Qt::UserRole + 100 // 避免系统角色冲突 }; ``` 3. **行高亮控制**: ```cpp void setRowHighlight(int row, bool highlight) { for (int col = 0; col < tableWidget->columnCount(); ++col) { QTableWidgetItem *item = tableWidget->item(row, col); if (item) { item->setData(HighlightDelegate::HIGHLIGHT_ROLE, highlight); } } tableWidget->viewport()->update(); // 强制立即重绘 } ``` ### 为什么这是最有效的方法 1. **性能优化**: - 仅在需要高亮时修改绘制逻辑 - 避免必要的样式计算 - 直接访问数据角色,无需复杂查询 2. **内存效率**: - 每个单元格只存储一个布尔值 - 没有额外的对象创建开销 - 委托实例在表格间共享 3. **维护简单**: - 高亮逻辑集中在一个方法中 - 状态切换简单直接 - 现有样式表无缝集成 ### 添加双击行高亮功能 ```cpp // 在ModelSetDialog类中添加: protected: void mouseDoubleClickEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton) { QModelIndex index = tableWidget->indexAt(event->pos()); if (index.isValid()) { toggleRowHighlight(index.row()); event->accept(); return; } } QDialog::mouseDoubleClickEvent(event); } ``` ### 实际应用场景扩展 ```cpp // 获取所有高亮行的ID QVector<int> getHighlightedRows() const { QVector<int> highlighted; for (int row = 0; row < tableWidget->rowCount(); ++row) { if (tableWidget->item(row, 0)->data(HighlightDelegate::HIGHLIGHT_ROLE).toBool()) { highlighted.append(row); } } return highlighted; } // 清除所有高亮 void clearAllHighlights() { for (int row = 0; row < tableWidget->rowCount(); ++row) { setRowHighlight(row, false); } } ``` 这个解决方案直接解决了您的问题,代码简洁高效,可以直接集成到现有项目中。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值