平民版均线量化交易模型

平民版均线量化交易模型前言2021年转瞬即逝,回顾一下在蚂蚁上定投的基金,在金融危机风雨欲来的2022年,分享一个懒人版的理财策略,愿大家新年里能财源广进,元旦快乐。基金定投我的策略非常简单,每月无脑小额定投,…

大家好,又见面了,我是你们的朋友全栈君。

9ed652ecafddb79b47a3e5f10a1196ae.gif

前言

2021年转瞬即逝,回顾一下在蚂蚁上定投的基金,在金融危机风雨欲来的2022年,分享一个懒人版的理财策略,愿大家新年里能财源广进,元旦快乐。

基金定投

我的策略非常简单,每月无脑小额定投,等”全民炒股”这种话题上新闻,择时清仓。一般2-3年有一个操作点,每次操作窗口期几个月,错过就从头再来。卖出点不影响买入操作,循环往复。

623c28fd578babfa318c60fdff4bf867.png

为什么选择基金

  • 每月一次或几次交易,不影响本职工作,繁复的工作交给机器来做。

  • 基金可以规避单一股票的对应行业或公司的黑天鹅事件。比如乐视的”下周回国”,或是“双减”对整个教育行业的冲击。

  • 相对股票来说,基金更“永续”,更少的停牌,ST或是增发干扰。

  • 在瞬息万变的股票市场,政策的影响度,舆情的提取手段尚不成熟,基金对于普通人来说可以大幅度简化投资模型。

为什么选择指数基金

  • 公募基金的基金经理有”业绩原罪”,即不惜一切代价追求高利润率,因为这样才能让排名靠前,才能让投资者”看到”。这种激励机制下,交易员更偏向冒险的投资风格,赌赢手续费赚的盆满钵满,赌输不过是换个基金代号从头再来。

  • 基金经理策略会与我们的自主策略混淆,无法归因,无法回测和改进策略,指数基金则减少了“个人”风险的集中度。

为什么选择定投策略

  • 定投不择时,相对模型简化了一个变量。

  • 小额定期交易可以更好的小步试错,累积时序数据。

  • A股的“「熊长牛短」“特征,意味着间隔平均的投资点大概率会落于熊市的低位,降低成本。

关于高频交易

  • 对于普通投资者来说,高频交易策略的手续费偏高,非金融专业的知识储备不足。

  • A股的T+1也相对限制了个股上交易操作(若资金量大可以做T改善这个问题,但也面临资金冲击现象)。

  • 基金作为一揽子股票的ETF,则不太适合频繁交易,有能力的可以选择高增长行业的龙头股来做高频。

弊端

  • 投资周期非常长,一般按年计;

  • 收益率一般(年化8%左右),远比不上专业投资人,更不能和币圈相提并论;

  • 资金流动性不好,长时间的熊市属于常态,卖出的窗口期很短;

  • 投资风格非常保守,更偏向于理财,而非投资。

优点

  • 完全属于被动投资,平时几乎不需要操心;

  • 没有追涨杀跌,没有人性考验,可以天天睡得安稳;

  • 熊市”播种”救企业于危难,牛市”分红”共享盛世繁华,政治正确,妥妥的正能量。

量化交易平台

普通的定投或是简单的智能定投策略,在支付宝上已经能很方便的操作了。作为码农,我们肯定希望更自主可控些,这里有更多的量化交易平台可供选择:

  • 开源 vnpy https://github.com/vnpy/vnpy

  • 聚宽 https://www.joinquant.com/

  • 米筐 https://www.ricequant.com/

  • 优矿 https://uqer.datayes.com/

大多以 Python 为开发语言,对于习惯拿机器学习做数据分析的朋友非常友好,这里以聚宽为例。官网注册个账号,新建 Notebook 就能在线运行了,离线版本需要额外申请免费6个月试用。详细可以参考一下 API 文档。

https://www.joinquant.com/help/api/help#api:API文档

初始化

选择沪深300为基线,每月定投1万元,每次至少定投10个月才赎回,目标收益率为 20%。

# 导入函数库
from jqdata import *

# 初始化函数,设定基准等等
def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 输出内容到日志 log.info()
    log.info('初始函数开始运行且全局只运行一次')
    # 过滤掉order系列API产生的比error级别低的log
    # log.set_level('order', 'error')

    ### 股票相关设定 ###
    # 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # 要操作的基金:(g.为全局变量)
    g.security = '510300.XSHG'
    # 预期目标
    g.goal = 1.2
    # 均值窗口期
    g.during = 500
    # 定投基本值
    g.base_order_value = 10000
    # 卖出锁定周期
    g.sell_period = 10
    g.sell_timestamp = context.current_dt
    
    run_monthly(market_open, monthday=1, reference_security='000300.XSHG')

均线策略

在每月无脑定投中,稍微加点策略:

高位少买

c838e438b35b50a3a70a907f48f0a2c2.png
b990b896b2c4bf9cb8b6be875babb402.png

# 高位买入    
def high_buy(security, base_order_value, cash, ratio):
    # 默认扣款率
    pay_ratio = 1
    
    if ratio < 0.15:
        pay_ratio = 0.9
    elif ratio < 0.50:
        pay_ratio = 0.8
    elif ratio < 1:
        pay_ratio = 0.7
    else:
        pay_ratio = 0.6
    
    # 需买入的金额
    order_value_cash = base_order_value*pay_ratio
    log.info("%s: %f, cash: %d, ratio: %f"%(security, pay_ratio, cash, ratio))
    
    if cash < order_value_cash:
        log.info("现金 %d 不足, 买入失败 %s: %d " % (cash, security, order_value_cash))
        return False
    
    log.info("高位买入 %s: %d" % (security, order_value_cash))
    order_value(security, order_value_cash)
    
    return True

低位多买

b1a5cdb0e2142269b96d029728ac04da.png
2bec703ffc61d9e0838ef540965f0342.png

# 低位买入    
def low_buy(security, base_order_value, cash, ratio):
    # 默认扣款率
    pay_ratio = 1
    
    # 近10天的最高价和最低价
    value_range = get_bars(security, count=10, unit='1d', fields=['high','low'])
    max_value = max(value_range['high'])
    min_value = min(value_range['low'])
    
    if (max_value - min_value)/max_value > 0.05:
        # 大幅度震荡,持续下跌通道中
        if ratio < 0.05:
            pay_ratio = 0.6
        elif ratio < 0.10:
            pay_ratio = 0.7
        elif ratio < 0.20:
            pay_ratio = 0.8
        elif ratio < 0.30:
            pay_ratio = 0.9
        elif ratio < 0.40:
            pay_ratio = 1
        else:
            pay_ratio = 1.1
    else:
        # 小幅度震荡,形成底部加大购买量
        if ratio < 0.5:
            pay_ratio = 1.6
        elif ratio < 0.10:
            pay_ratio = 1.7
        elif ratio < 0.20:
            pay_ratio = 1.8
        elif ratio < 0.30:
            pay_ratio = 1.9
        elif ratio < 0.40:
            pay_ratio = 2
        else:
            pay_ratio = 2.1       
    
    # 需买入的金额
    order_value_cash = base_order_value*pay_ratio
    
    if cash < order_value_cash:
        log.info("现金 %d 不足, 买入失败 %s: %d " % (cash, security, order_value_cash))
        return False
    
    log.info("低位买入 %s: %d" % (security, order_value_cash))
    order_value(security, order_value_cash)
    
    return True

预期止盈

这里为了回测方便,没法”听新闻”判断了,先以满足预期目标收益来止盈。

# 卖出    
def sell(security, current_price, MA, sell_timestamp):
    log.info("sell: %f, MA: %f" %(current_price, MA))
    
    if current_price/MA < g.goal:
        return False
        
    if (sell_timestamp - g.sell_timestamp).days/30 < g.sell_period:
        return False
    
    # 卖出所有股票,使这只股票的最终持有量为0
    log.info("%s, %s, %d" % (sell_timestamp, g.sell_timestamp, (sell_timestamp - g.sell_timestamp).days))
    log.info("达到预期目标, 卖出 %s" % (security))
    order_target(security, 0)
    # 更新卖出时间戳
    g.sell_timestamp = sell_timestamp
    return True

运行策略

万事俱备,我们将上述函数都组合到一起,跑一下看看效果。

## 开盘时运行函数
def market_open(context):
    log.info('函数运行时间(market_open):'+str(context.current_dt.time()))
    security = g.security
    
    # 平均值窗口期
    during = g.during
    
    # 获取股票的收盘价
    close_data = get_bars(security, count=during, unit='1d', fields=['close'])
    # 取得过去500天的平均价格
    MA = close_data['close'].mean()
    # 取得上一时间点价格
    current_price = close_data['close'][-1]
    # 取得当前的现金
    cash = context.portfolio.available_cash
    # 定投金额
    base_order_value = g.base_order_value
    
    if (current_price > MA):
        # 高位卖出
        sell(security, current_price, MA, context.current_dt)
        
        # 高位少买 
        high_buy(security, base_order_value, cash, (current_price-MA)/MA)
    else:
        # 低位多买
        low_buy(security, base_order_value, cash, (MA-current_price)/MA)

    #得到本次所有成交记录
    trades = get_trades()
    for _trade in trades.values():
        log.info('成交记录:'+str(_trade))
    log.info('本轮结束')
    log.info('##############################################################')

364c7def4ef34dae0b3a98b636fee7c7.png

选择2015年到2021年来做回测,貌似看起来还不错,买入点很多,除了零星几次资金量用完,几乎每月都有交易;卖出点才4次,这一方面受限于“卖出锁定周期”,另一方面也体现A股长期熊市的现象。

8aa5294e33c69a8b1c280ac299f43431.png

年化收益 9.85%,这里小额定投的建仓周期比较长,所以相对资金利用率不够高,这里的仓位可以简单理解为最大投资规模。实际操作中,大家可以把固定收入作为资金投入,大额资金可以用作其他投资,扩大收益率。

再多选几个长周期,比如2017年至今,或是2019年至今,比不上牛市收益,但可以逃过股市熔断大跳水,大多时候收益率能保持稳定正值。

37b73d781b501c36949ae2186e0f20c9.png

持仓分析

实盘交易

  • 直接对接证券机构

  • 模拟交易,手动操作

    f44415996488e70dfabc038732d19405.png

    模拟交易

由于我们买入卖出操作不多,可以将制定好的策略,用模拟交易的方式将买入卖出信号通过微信消息来发送,再根据实际情况进行基金操作。

# 给微信发送消息(添加模拟交易,并绑定微信生效)
send_message("买入 %s: %d" % (security, order_value_cash))

总结

看到这里,相信搞深度学习的朋友肯定想拿手上一堆时序模型跃跃欲试了。先小泼盆冷水,就自我体验和身边做量化交易朋友的教训来看,单一模型在A股上基本没戏。

a2ce0f1c6319a21602a35b39128580d0.png

原因主要有以下几点:

  • 时序模型基于历史趋势可再现,历史不会简单重复,只会换种形势卷土重来。

  • 众多的量化机构让原有的“低风险策略点”稍纵即逝,寄生策略泛滥。

  • A股是强政策市,金融因素占比偏小。

  • 散户比例很高,市场情绪化影响偏大。

另外,以上这些仅作为技术分享和个人经验之谈,不作为投资建议。更多的交易策略,等金融专业人士乱入指点。还有NLP舆情分析关键字拉涨停的更多骚操作,评论区打开脑洞吧。

论一个韭菜的自我修养,侠之大者,为国接盘。

哎,2021年说好的每周一更呢,感觉欠了很多的债。有鸿蒙系列的,树莓派系列的,OCR系列的,知识图谱系列的,手稿太多,没有心境慢慢整理,希望来年能补全。

c236136f3b35f2c1976e1e0f8f077963.png

d76f842a9da4aa9e9d1ff1ec02d82809.gif

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/153174.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • hashmap动态扩容死循环_HashMap扩容

    hashmap动态扩容死循环_HashMap扩容HashMap扩容死循环问题源码分析问题(jdk1.7)一、首先hashmap单线程正常扩容遍历每个数组,依次遍历每个数组的链表,根据头插法由原来的1,2,3变为了3,2,1二、hashmap多线程扩容死循环问题两个线程e1,e2此时线程一先执行,但线程二的指向发生改变,改为线程变换后的具体存储;初始的e2指向0号位的1,但经过线程一的变换指向了2号位的1了,next也发生改变线程二开始在线程一的基础存储,当next2指向空时。e.next=newTable[i],也就

  • 浏览器内核(navigator.appName显示的不是内核信息!!)。

    浏览器内核(navigator.appName显示的不是内核信息!!)。前言:今天用navigator.appName命令时,发现Chrome和FireFox都是”Netscape”。于是有疑问,怎么回事网景公司的浏览起名字呢!(IE是”MicrosoftInternetExplorer”)(Presto是”Opera”)上网调查了一下,最开始以为和浏览器内核有关,但其实关系不大。Trident:IE以Triden…

  • 图像处理——Canny算子 图像边缘检测:Canny算子、Prewitt算子和sobel算子

    图像处理——Canny算子 图像边缘检测:Canny算子、Prewitt算子和sobel算子https://blog.csdn.net/fengye2two/article/details/79190759https://www.jianshu.com/p/bed4ffe996a1

  • java struts2 漏洞_struts2漏洞原理及解决办法

    java struts2 漏洞_struts2漏洞原理及解决办法1、原理Struts2的核心是使用的webwork框架,处理action时通过调用底层的getter/setter方法来处理http的参数,它将每个http参数声明为一个ONGL(这里是ONGL的介绍)语句。当我们提交一个http参数:?user.address.city=Bishkek&user[‘favoriteDrink’]=kumysONGL将它转换为:action.getUser…

  • Git创建远程分支并提交代码到远程分支

    Git创建远程分支并提交代码到远程分支1、可以通过gitbranch-r命令查看远端库的分支情况如图所示,远程仓库只有一个master分支2、从已有的分支创建新的分支(如从master分支),创建一个dev分支但此时并没有在远程仓库上创建分支如图所示还是只有一个master分支3、建立本地到远端仓库的链接–这样代码才能提交上去使用命令行gitpush–set-…

  • 零基础学Java(9)在mac上运行命令行提示”找不到或无法加载主类”

    零基础学Java(9)在mac上运行命令行提示”找不到或无法加载主类”天坑遇到的问题:使用命令行执行命令:javaEightSample,会报以下错误错误:找不到或无法加载主类EightSample运行环境mac系统IntelliJIDEA编译器Ja

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号