IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 在Elasticsearch中回测隨機(Stochastic)指標交叉交易策略 -> 正文阅读

[大数据]在Elasticsearch中回测隨機(Stochastic)指標交叉交易策略

????????之前的文章“在Elasticsearch 中回测 RSI 交叉策略”,介紹在Elasticsearch中如何回測 相对强弱指数(RSI)指标交叉交易策略。在本文中,我们将实施随机(Stochastic)指标并衡量其性能,最后与相对强弱指数指标进行比较。 儘管这指標是由 George C. Lane 在 1950 年代後期開發,看似非常古老,但仍然很受歡迎。与 RSI 指标一样,随机指标也是展示在 0 和 100 之间价格的波动动量。所以,它们称为振荡器(Oscillator)。这两个指标都可用于识别超买区域和超卖区域。

????????随机指标将价格变化转化为一种数据,定义为两个距离的比值为 %K (包括最近收盘价C和最近最高价MMax之间的距离,以及最近最高价和最近最低价MMin之间的距离)。随机指标分为两种类型,快速和慢速。假设%Kfast为快速的例子,方程可以改写如下,其中 MMaxn,1 和 MMinn,1 是移动最大值和移动最小值。 对应滑动窗口为n的Elasticsearch移动函数,需要右移1个数据以包含当前数据。滑动窗口 n常用值为14 。

????????与 MACD 类似,随机指标也定义了一条名为 %D 的信号线,它是滑动窗口为3 的%K 简单移动平均值(SMA)。假设%Dfast为快速的例子,方程可以改写如下。

快速比慢速对价格变化更敏感,会产生更多的买入或卖出信号。慢速随机指标%Fslow %Dslow可以定义如下:

简单的随机交叉交易策略可以定义为当 %K 线在超买区 (> 80) 越过并且低于%D 线时发出卖出信号,而当 %K 线在超卖区(< 20)中越过并且高于%D 线时发出买入信号。 对于其他值,请耐心等待买入或卖出信号。

?????????使用图表来观察值的变化要容易得多。在本文中,我们将回测应用于Tushare大数据开放社区提供的股票型公募基金,并专注于将 Elasticsearch 作为分析工具。 下面的例子随机选择了"工银研究精选股票" (代码为000803.OF) ,并另外抽取10只股票型基金运行,结果将在最后的段落汇总和展示。数据选自2021年01月01日到2021年05月31日之间的时间范围。在下图中,随机指标 (FK/SK) 和信号线 (FD/SD) 与每日收盘价一起绘制。 在每日价格曲线中,卖出信号的价格标为红色,而买入信号的价格标为蓝色。 如下图所示,慢随机指标产生的信号数量略少于快速随机指标。

????????????在这里,我们展示了一个简单的随机交叉交易策略,并使用 Elasticsearch 来展示实现细节。

  • 假设由于成本的限制,只能每一次购买和持有 1 股,并在持有的股份被售出之前不能发生任何交易。
  • 当 FK 在超卖区域 (<20) 超过 FD 时买入 1 股。
  • 当 FK 在超买区域 (> 80) 低于 FD 时卖出 1 股。
  • 在测试期结束时,持有的股票以当前价格兑现。
  • 慢速随机交叉交易策略以SK代替FK和以 SD代替FD

????????根据以上的交易策略,快速随机指标有 5 个蓝点和 11 个红点,但只允许进行 3 次买入和 3 次卖出交易。慢速随机指标有 3 个蓝点和 10 个红点,但只允许进行 2 次买入和 2 次卖出交易。让我们描述一下使用 Elasticsearch 的实现。假设有一个填充有数据的 Elasticsearch 索引,其使用的数据映射与上一篇论文中描述的相同。以下步骤演示了 REST API 请求正文的代码。

通过搜索操作收集所有相关文档

使用带有必要条件(must)子句的布林查询(bool query)来收集基金代码为000803.OF,和公告截止日期从2021年01月01日到2021年05月31日的文档。 由于需要计算移动平均值,因此增加了一个月的数据(从2020年12月01日到2020年12月31日)。

{
    "query": {
        "bool": {
		     "must": [
		         {"range": {"end_date": {"gte": "20201201", "lte": "20210531"}}},
		         {"term": {"ts_code": "000803.OF"}}
		     ]
		}
	},

提取每日的复权单位净值

使用名为Backtest_Stochastic日期直方图(date_histogram)存储桶聚合,并配合参数field(字段)为end_date和interval(间隔)为 1d(1天),提取每日的复权单位净值(adj_nav)。由于子聚合使用管道(pipeline)聚合而无法直接采用文档字段,所以额外使用平均值(avg)聚合获取每日的复权单位净值,聚合名称为Daily。由于买入价格和利润百分比使用单位价格(unit_nav)计算,故此同样建立聚合名称Daily_unit_nav.

    "aggs": {
        "Backtest_Stochastic": {
            "date_histogram": {
                "field": "end_date",
                "interval": "1d",
                "format": "yyyyMMdd"
            },
             "aggs": {
                "Daily": {
                    "avg": {"field": "adj_nav"}
                },
                "Daily_unit_nav": {
                    "avg": {"field": "unit_nav”}
                },

提取存储桶的日期

由于增加了一个月的数据,而后续操作需要过滤掉额外的文档,因此以存储桶的日期作为筛选限制条件。我们可以使用名为DateStr的最小值聚合间接取得日期,Elasticsearch的日期用新纪元时间(Epoch Time) 表示,并且以毫秒为单位,时区为UTC。

                "DateStr": {
                    "min": {"field": "end_date"}
                },

选择多于1个文档的bucket

由于没有内插数据,非交易日为了过滤掉空桶),使用名为 SDaily 的“bucket_selector”聚合来选择文档计数大于 0 的桶。

                "SDaily": {
                    "bucket_selector": {
                        "buckets_path": {"count":"_count"},
                        "script": "params.count > 0"
                    }
                },

计算收盘价的简单移动最大值和最小值

使用两个"moving_fn"聚合,命名为 MMax 和 MMin,参数 window 为 14,参数"buckets_path"为 Daily。 参数"shift"设置为 1 以包括最近的数据。 MMax 和MMin 使用函数MovingFunctions.max() 和MovingFunctions.min() 计算。

                "MMax": {
                    "moving_fn": {
                        "script": "MovingFunctions.max(values)", "window": 14, "buckets_path": "Daily", "shift":1
                    }
                },
                "MMin": {
                    "moving_fn": {
                        "script": "MovingFunctions.min(values)", "window": 14, "buckets_path": "Daily", "shift":1
                }
            },

计算快速随机和慢速随机的指標 %K 和信号线 %D

使用三个"bucket_script"聚合,FK 用于快速 %K,FDSK 用于快速 %D 和慢速 %K,而SD 则用于慢速 %D。 使用参数" buckets_path"指定来自 Daily、MMin 和 MMax 的聚合结果。 然后,根据脚本中的公式计算快速 %K。 FDSK 是滑动窗口为3 的FK 简单移动平均值 ,SD 是滑动窗口为3 的FDSK 简单移动平均值。简单移动平均值使用函数MovingFunctions.unweightedAvg()来计算。 参数"shift"设置为 1 以包括最近的数据。

                "FK": {
                    "bucket_script": {
                        "buckets_path": {"Daily": "Daily", "MMin": "MMin", "MMax": "MMax"}, "script": "100 * (params.Daily - params.MMin)/(params.MMax - params.MMin)"
                    }
                },           
                "FDSK": {
                    "moving_fn": {
                        "script": "MovingFunctions.unweightedAvg(values)", "window": 3, "buckets_path": "FK", "shift": 1
                    }
                },
                "SD": {
                    "moving_fn": {
                        "script": "MovingFunctions.unweightedAvg(values)", "window": 3, "buckets_path": "FDSK", "shift": 1
                    }
                },

识别 %K和 %D的交叉类型

a) 使用名为 FKFD_Diff 的"bucket_script"聚合,并配合参数"buckets_path"来指定 FK 和 FDSK 的值,以确定距离是正数还是负数。 如果 FK 高于 FDSK,则设置为 1。如果 FK 低于 FDSK,则设置为 -1。 如果它们相等,则设置为 0。 SKSD_Diff 聚合可以相同的方式定义。

                "FKFD_Diff": {
                    "bucket_script": {
                        "buckets_path": {"FK": "FK", "FDSK": "FDSK"}, "script": "(params.FK - params.FDSK) > 0 ? 1 : ((params.FK - params.FDSK) == 0 ? 0 : -1)"
                    }
                },
                "SKSD_Diff": {
                    "bucket_script": {
                        "buckets_path": {"FDSK": "FDSK", "SD": "SD"}, "script": "(params.FDSK - params.SD) > 0 ? 1 : ((params.FDSK - params.SD) == 0 ? 0 : -1)"
                    }
                },

b) 使用名为 F_Diff 的导数聚合,并配合参数"buckets_path"指定 FKFD_Diff 的值,以计算与前一个时间戳的值的差异。%K 和 %D 的交叉可能涉及一或两个交易日,所以F_Diff的值可以为-1、-2、1或2。在超卖区域需要关注-1 和 -2 的值,在超买区域需要关注1 和 2 的值。 S_Diff 聚合可以相同的方式定义为慢速随机指标。

                "F_Diff": {
                    "derivative": {"buckets_path": "FKFD_Diff"}
                }, 
                "S_Diff": {
                    "derivative": {"buckets_path": "SKSD_Diff"}
                },

c) 由于%K 和 %D 的交叉可能涉及一或两个交易日。 因此需要前一个交易日的数据。 使用名为 PRE_FK 的"moving_fn"聚合,并配合参数 window 为 1,参数"buckets_path"为 FK的简单移动总和值。简单移动总和值使用函数MovingFunctions.sum()来计算。PRE_FDSK 和 PRE_SD 聚合可以用相同的方式定义。

                "PRE_FK": {
                    "moving_fn": {"script": "MovingFunctions.sum(values)", "window": 1, "buckets_path": "FK"}
                },
                "PRE_FDSK": {
                    "moving_fn": {"script": "MovingFunctions.sum(values)", "window": 1, "buckets_path": "FDSK"}
                },
                "PRE_SD": {
                    "moving_fn": {"script": "MovingFunctions.sum(values)", "window": 1, "buckets_path": "SD"}
},

d) 当%K和%D产生交叉时,原则上应该发生在超买或超卖区域。然而,在 Elasticsearch 服务器中计算实际交叉点甚不方便,因此为确保交叉在邻近正确的区域发生,两个交易日中必须有一日在超买或超卖区域。要确定交叉是否有效,聚合 F_Type 检查以下几个条件。 如果是卖出信号,则将 F_Type 设置为 1。如果是买入信号,则将 F_Type 设置为 -1。 否则,将 F_type 设置为 0。 S_Type 聚合可以用相同的方式定义。

  • 在超买区域内

????????params.PRE_FK > 80 && params.PRE_FDSK > 80

  • 在超卖区域内

????????params.PRE_FK < 20 && params.PRE_FDSK < 20

  • 在超卖区域需要关注的交叉

????????params.F_Diff == -1 || params.F_Diff == -2

  • 在超买区域需要关注的交叉

????????params.F_Diff == 1 || params.F_Diff == 2

  • FK 在 FDSK 下方交叉

????????params..FK <= params..FDSK

  • FK 在 FDSK 上方交叉

????????params..FK >= params..FDSK

                "F_Type": {
                    "bucket_script": {
                        "buckets_path": {"F_Diff": "F_Diff", "FK": "FK", "FDSK": "FDSK", "SD": "SD", "PRE_FK": "PRE_FK", "PRE_FDSK": "PRE_FDSK", "PRE_SD": "PRE_SD"},
                        "script": "((params.F_Diff == -1 || params.F_Diff == -2) && (params.PRE_FK > 80 || params.FK > 80) && (params.PRE_FDSK > 80 || params.FDSK > 80) && params.FK <= params.FDSK) ? 1 : (((params.F_Diff == 1 || params.F_Diff == 2) && (params.PRE_FK < 20 || params.FK < 20) && (params.PRE_FDSK < 20 || params.FDSK < 20) && params.FK >= params.FDSK) ? -1 : 0)"
                    }
                 },
                 "S_Type": {
                     "bucket_script": {
                         "buckets_path": {"S_Diff": "S_Diff", "FDSK": "FDSK", "SD": "SD", "PRE_FDSK": "PRE_FDSK", "PRE_SD": "PRE_SD"}, 
                         "script": "((params.S_Diff == -1 || params.S_Diff == -2) && (params.PRE_FDSK > 80 || params.FDSK > 80) && (params.PRE_SD > 80 || params.SD > 80) && params.FDSK <= params.SD) ? 1 : (((params.S_Diff == 1 || params.S_Diff == 2) && (params.PRE_FDSK < 20 || params.FDSK < 20) && (params.PRE_SD < 20 || params.SD < 20)&& params.FDSK >= params.SD) ? -1 : 0)"
                     }
                 },

使用名为 S_Date 的"bucket_selector"聚合,并配合参数"buckets_path"为"DateStr”,“script"语句中指定存储桶的选择条件。 选择标准是日期在 2021 年 1月 1 日及之后的存储桶(以毫秒为单位指定纪元时间 1609459200000)。由于涉及当前收盘价,买入或卖出交易将顺延至下一个交易日。因此,所有的结果都会在python程序中上报和处理,根据策略买入或卖出交易。

                "S_Date": {
                    "bucket_selector": {
                        "buckets_path": {"DateStr": "DateStr"}, "script": "params.DateStr >= 1609459200000L"
                    }
                }
            }
        }
    },
    "from": 0, "size": 0
}

收集结果后,可以绘制如前图所示。


????????执行结果会发出买入或卖出信号; 然而,这些信号仅满足前述交易策略的第二种和第三种情况。 对于第一种和第四种情况,需要使用 Python 编程语言来编写程序。主程序包括四个部分。

  • 读取两个命令行参数。 一个用于选定的代码,另一个用于包含使用 JSON 格式在 Elasticsearch REST API 请求正文中编写的交易策略的文件名称。
  • 从 Elasticsearch 服务器获取数据。
  • 解析响应数据并优化买入和卖出信号。
  • 报告回测统计数据(为简单起见,利润并未扣除交易费用)。

主函数如下所示:

def main(argv):
    inputfile, symbol, type = get_opt(argv) 
    resp = get_data(inputfile, symbol)
    transactions = parse_data(resp, type)
    report(transactions, type)

????????在本文中,仅展示了买卖信号细化的代码段。 读者可以进一步参考Gitee上的开源项目(Backtest_Stochastic)。 为确保每次只买入及只持有一股,并且在卖出持有的股票之前不发生交易,我们使用布尔变量"hold"来确保交易满足以下条件。

  • 当hold为 False 时,买入信号(值等于 -1)被接受
  • 当hold为True时,卖出信号(值等于1)被接受

????????函数parse_data()如下所示。 最后,交易数组transaction只包含有效信号。然而,这些信号将在report中按照交易策略处理买入或卖出。利润是使用复权单位净值差额计算。而买入价格和利润百分比则使用单位价格(unit_nav)计算。

# parse the response data and refine the buy/sell signal
def parse_data(resp, type):
    result = json.loads(resp)
    aggregations = result['aggregations']
    if aggregations and 'Backtest_Stochastic' in aggregations:
        Backtest_Stochastics = aggregations['Backtest_Stochastic']

    transactions = []
    hold = False
    if Backtest_Stochastics and 'buckets' in Backtest_Stochastics:
        for bucket in Backtest_Stochastics['buckets']:
            transaction = {}
            transaction['date'] = bucket['key_as_string']
            transaction['Daily'] = bucket['Daily']['value']
            # honor buy signal if there is no share hold
            if bucket[type]['value'] == -1:
                transaction['original'] = 'buy'
                if not hold:
                    transaction['buy_or_sell'] = 'buy'
                else:
                    transaction['buy_or_sell'] = 'hold'
                hold = True
            # honor sell signal if there is a share hold
            elif bucket[type]['value'] == 1:
                transaction['original'] = 'sell'
                if hold:
                    transaction['buy_or_sell'] = 'sell'
                else:
                    transaction['buy_or_sell'] = 'hold'
                hold = False
            # for other situations, just hold the action
            else:
                transaction['original'] = 'hold'
                transaction['buy_or_sell'] = 'hold'
            transactions.append(transaction)

    return transactions

????????python程序提供交易策略的统计信息,包括整个买卖交易的"赢"和"输"。以下是对000803.OF运行快速随机交易策略后的结果。

购买次数:              2
卖出次数:              2
得胜次数:              1
亏损次数:              1
总利润:            -0.13
平均购买价格:        3.24
利润百分率:        -3.89%

????????从2021年01月01日到2021年05月31日,11只股票型基金运行简单型随机交易策略的结果汇总和展示如下表。结果表明,这个交易策略不一定可以获利。大多数交易者的建议不要根据单一指标进行交易。

????????下表为本次实验中,使用慢速随机交叉交易策略的结果,显示亏损于多于盈利。

????????下表为本次实验中,使用快速随机指标、慢速随机指标和相对强弱指数指标之间的收益。 在表格中显示相对强弱指数指标比其他两个指标具有更高的收益。然而,有一只基金在 RSI 中没有提供信号,但快速和慢速随机指标均获利。

????????下表是比较买入、卖出、赢和输的总次数。 相对强弱指数指标交叉交易策略有更好的表现。


备注:

  1. 感谢Tushare大数据开放社区提供相关数据及Gitee开源社区提供存储开源项目。
  2. 本文基于公开发布技术和研究观点,并不构成任何投资建议,读者在使用时须自行承担责任。
  3. 文中可能还存在疏漏和错误之处,恳请广大读者批评和指正。
  4. 作者的中文著作Elasticsearch 数据分析与实战应用(ISBN 978-7-113-27886-1号)将于2021 年 8月出版。
  5. 作者的英文著作Advanced Elasticsearch 7.0(ISBN 978-1-789-95775-4号)被bookauthority评为 2021 年最值得阅读的 4 本 Elasticsearch 新书之一。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-08-01 14:34:51  更:2021-08-01 14:36:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 11:05:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码