欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

公平博弈必输策略及Python改进:基金补仓

程序员文章站 2022-04-09 16:20:02
公平博弈必输策略及改进前言一、必输的原因二、使用步骤1.引入库2.读入数据总结前言考虑一个场景:某股票当前股价100元,假设未来60个交易日,这支股票要么+10%涨停,要么-10%跌停,最终涨停30次,跌停30次,那么该股票最终价格将是多少?计算:x = 1.1^30 * 0.9^30 * 100 = 0.99^30 * 100 = 73.97元类似情况,假设一名投机人,每次拿出当前本金的10%进行抛硬币测试(公平博弈,输赢均50%),一共测试60次,最后输赢各30次,那么他的本金将是多少?计算:...

公平博弈必输策略及Python改进:基金补仓

前言

考虑一个场景:一名投机人每次拿出当前本金的10%进行抛硬币测试(公平博弈,输赢均50%),一共测试60次,最终输赢各30次,那么他的本金将是多少?

计算:x = 1.1^30 * 0.9^30 * 100% = 0.99^30 * 100% =73.97%

这就带来了一个问题:一个公平博弈,如果策略不妥,那么长期下来有可能是必输。

为什么公平博弈,长期下来,最后“必输”?

举个抛硬币两次的例子:一共4个结果,赢赢、赢输、输赢、输输各占25%,那么本金分别是:121%、99%、99%、81%,其平均值(期望)仍然是100%。因此,虽然是公平博弈,但是长期下来,少数人大赚,多数人亏损,彩票化了。

注:该问题,与“一支股票经历10%涨停30次,10%跌停30次,最终价格如何”,实质是一样的。

一、算术原因

从算术角度来说,胜率必须略高于52.5%,长期才可以不输。以1000次为例:
1. 1 525 ∗ 0. 9 475 = 1. 1 50 ∗ 0.9 9 475 = 0.9916 ≈ 1 1.1^{525} * 0.9^{475} = 1.1^{50} * 0.99^{475} = 0.9916 \approx 1 1.15250.9475=1.1500.99475=0.99161
但是,根据大数理论,硬币实验次数越多,概率越趋近50%。当实验次数足够多时,胜率略高于52.5%的可能性变得微乎其微,对多数人而言,就必输了。

二、逻辑原因

从逻辑角度来说,落后之时减少投入,而领先之时增大投入,是不妥的。以起始10000为例:
1)先输后赢,则10000输1000,变成9000,再赢900,变为9900
2)先赢后输,则10000赢1000,变成11000,再输1100,变为9900

只有反过来,落后时增大投入,而领先时减少投入,才是合理的。比如:
1)先输后赢,10000变9000,增加投入1100,赢后变10100
2)先赢后输,10000变11000,减少投入900,输后变10100

然而,这种策略也存在两个问题:一是有点类似倍投法,假如一开始连输几次,有输光的风险;二是领先之时,资金大了,而每次投入要变少,资金利用率降低了。

三、Python改进

事实上,“每次拿出当前本金10%”的方案,不如“固定金额”方案。套用在购买基金的场景,前者类似“固定份额”定投方案,后者是“固定金额”定投方案。

现在试一试改进方案:
1)以某天净值x0为基准
2)当净值突破至下一个基准:上涨为x0的2倍,下跌为x0的一半,切换基准
3)当上一次操作之后,净值变化超过3%,则再次操作:如下跌,则补仓(倍数见下),如上涨且持有,则减仓(倍数见下,不足则清完为止)
4)当净值在[1, 2)倍x0时,倍数为1倍;净值在[0.9, 1)倍x0时,倍数为2倍;净值在[0.8, 0.9)倍x0时,倍数为3倍;…;净值在[0.5, 0.6)倍x0时,倍数为6倍。但是,如果当前持仓过多(大于该倍数的2倍),那么补仓倍数仅为1倍(减仓倍数不影响)

以下是该方案的Python源码,在Jupyter notebook里面分三段:

import requests
import time
import execjs

fileTest = './data/accTest.csv'
jjTest = '001630'

def getUrl(fscode):
    head = 'http://fund.eastmoney.com/pingzhongdata/'
    tail = '.js?v='+ time.strftime("%Y%m%d%H%M%S",time.localtime())
    return head+fscode+tail

# 根据基金代码获取净值
def getWorth(fscode):
    content = requests.get(getUrl(fscode))
    jsContent = execjs.compile(content.text)
    name = jsContent.eval('fS_name')
    code = jsContent.eval('fS_code')
    #单位净值走势
    netWorthTrend = jsContent.eval('Data_netWorthTrend')
    #累计净值走势
    ACWorthTrend = jsContent.eval('Data_ACWorthTrend')
    netWorth = []
    ACWorth = []
    for dayWorth in netWorthTrend:
        netWorth.append(dayWorth['y'])
    for dayACWorth in ACWorthTrend:
        ACWorth.append(dayACWorth[1])
    return netWorth, ACWorth

ACWorthTestFile = open(fileTest, 'w')
_, ACWorth = getWorth(jjTest)
if len(ACWorth) > 0:
    ACWorthTestFile.write(",".join(list(map(str, ACWorth))))
    ACWorthTestFile.write("\n")
    print('{} data downloaded'.format(jjTest))
ACWorthTestFile.close()

第一段如上,在天天基金网爬基金。001630是天弘中证计算机主题ETF联接C,有两个特点:
1)在支付宝基金里面,买入0费率,持有7天卖出0费率
2)该基金2015年7月成立的,至今(2021年2月)5年多,成立以来累计涨跌幅约-10%(目前基金净值约0.9元),10多亿大小

import csv

with open(fileTest) as f:
    row = csv.reader(f, delimiter=',')
    for r in row:
        #去掉记录为None的数据(当天数据缺失)
        r = [float(x) for x in r if x != 'None']

show_days = 1500 #仅用最近的show_days天数(1年约250交易日)

if len(r) > show_days:
    r = r[len(r)-show_days:]

x0 = r[0] #当前基准
x1 = x0 #已存净值
x2 = x0 #上次操作时净值
a = 10000 #标准份额(1倍)
y = 0 #当前持有倍数
z = 0 #累计盈利(含浮盈浮亏)
s = [0] #历史盈利率(不乘以标准份额,即z/a)
t = [0] #历史倍数乘以0.1(0.1y,乘以0.1是为了图形显示)

for i in range(1,len(r)):
    x3 = r[i]

    #刷新浮盈浮亏
    if y > 0:
        z += a * y * (x3 - x1)

    s.append(z/a)

    #净值变化超过基准
    if (x3 >= x0 * 2) or (x3 < x0 / 2):
        #下跌,则买入1倍份额
        if x3 < x2:
            y += 1
        #反之,如有则卖出1倍份额
        elif y > 0:
            y -= 1

        #刷新
        x0 = x3
        x1 = x3
        x2 = x3

    #上一次操作至今,净值变化超过3%
    elif (x3 > x2 * 1.03) or (x3 < x2 * 0.97):
        if x3 >= x0:
            # [x0, 2x0)采用1倍
            i = 1
        else:
            i = 2 + int(10 * (1 - x3 / x0))
            #但如果当前持仓过多,则补仓仅采用1倍(减仓不受影响)
            if (y > 2 * i) and (x3 < x2):
                i = 1

        #下跌,则买入i倍份额
        if x3 < x2:
            y += i
        #反之,如持有则卖出i倍份额
        else:
            if y > i:
                y -= i
            else:
                y = 0

        #刷新
        x1 = x3
        x2 = x3

    else:
        x1 = x3

    t.append(y * 0.1)

print('累计盈亏为:{:.0f}'.format(z))

第二段就是之前说的“改进方案”的实现。注意:当持仓过多而补仓时,以及基准切换时,倍数都用1。
该基金成立至今5年半,近1400交易日(show_days=1500亦即全部显示)。

import numpy as np
from matplotlib import pyplot as plt

plt.rcParams['font.sans-serif']='SimHei'
plt.rcParams['axes.unicode_minus']=False

r_plot = np.array(r).reshape(-1, 1)
s_plot = np.array(s).reshape(-1, 1)
t_plot = np.array(t).reshape(-1, 1)

# 图表显示
fig=plt.figure(figsize=(15,6))
plt.plot(r_plot, color='blue', label='基金净值')
plt.plot(s_plot, color='red', label='盈亏情况')
plt.plot(t_plot, color='yellow', label='持仓情况')
plt.legend(loc='upper left')
plt.show()

第三段代码就是画图了,贴图如下:
公平博弈必输策略及Python改进:基金补仓

蓝色线就是001630的净值。看似很平,1元上下,其实最低值是0.485元,最高值是1.0826元。如果只会捂着,不会波段操作,简直太惨了。
黄色线是持仓倍数情况,红色线是盈亏情况。在最惨的时候(净值0.5元左右),倍数近20倍(黄纵坐标要乘以10),浮盈浮亏约-1.5(假定1万份为1倍,即浮亏1.5万);目前净值0.9元左右,浮盈浮亏为5(同理,浮盈5万)。

如果把show_days改设为250,亦即查看近1年的情况,则图片如下所示:
公平博弈必输策略及Python改进:基金补仓
近一年最高持仓倍数为6,当前浮盈7千多(1万份1倍)。

注意:因为001630持有7天卖出0费率,且基金是按照先进先出原则卖出的,因此本文忽略了持仓不足7天而卖出的惩罚费率情况。如实战,则自行注意之。

总结

本文针对特色基金001630(基本面尚可,人气较旺,表现较差),提出了一种“温和”补仓的策略------落后时略增加倍数。当然,其它基金也是可以的,试过10多个基金(都是7天0费率的)。

关于基金补仓减仓,凭感觉,两种策略都是可行的:一种即是本文的“上次操作后变化超3%”,另一种是“连涨连跌几天”。或者两者结合:5天当中4-5天涨,且涨幅超3%;5天当中4-5天跌,且跌幅超3%。采用5天是比较好的(5个交易日必满足持有7天)。这些策略经反复测试,大同小异,不另贴了。

有一种直觉,现在“韭菜”不喜欢买股票,而喜欢买基金了。尤其是女白领。于是,有些光棍跑到基金板块留言“交友启事”,蛮有趣。

本文地址:https://blog.csdn.net/fanmin2000/article/details/113392512

相关标签: Python 爬虫