(原创)基金定投的一种实现

绝大多数的理财投资app都会提供一个定投的功能,定投就是定期定额投资指定标的。如此推荐当然是因为他的优点很多,同时受理方也相对会获得更多的存量资金,算是双赢,只是周期较长,对于不懂理财的人来说可能就跟普通人去跑一万米一样长。
以基金为例,一般基金的交易平台除了申购/赎回的接口,也会提供定投的接口,只是调用第三方接口的话,一方面提高了耦合性,另一方面为了优雅地向用户展示定投相关信息,就需要付出额外的工作,因此我们需要DIY,然后按时调用买入的接口完成投资即可。

一、定投的数据模型

  • 首先,从定投的定义上来考虑:定期定额买入指定的投资标的
    1)我们需要知道是谁(user_id)
    2)定期,又分为按日、按周、按月(cycle_unit,jyrq)
    3)定额(apply_num)
    4 ) 投资标的,要考虑扩展性,可买的不仅仅是单个基金(invest_flag,invest_code)
    5)一般理财app都支持绑定多张卡,需要用户指定从哪张卡里扣款(trade_acoo)

  • 其次,要从定投执行过程方面来设计
    6)考虑到用户会出现资金紧张,可以允许顺延,但有最大天数限制,超出则判定失败,如果连续多次失败则认定用户已放弃定投,自动停止(delay_day,delay_count,fail_count)
    7)用户可能想知道下一次扣款会在哪天,同时考虑到顺延等状况,需要更新当前的扣款日期以便执行(next_kkdate,cur_kkdate)
    8 )累积的投资金额和成功次数(total_sum,count)
    9 )标记定投的状态,激活的or被终止了(is_active,soft_del)

  • 最后,还要考虑定投结果的展示
    9)定投结果要关联定投、标记结果状态--成功/失败(aip_id, state)
    10)如果成功还需要关联交易订单、交易金额、交易日期(order_id,apply_sum,trade_date)

综上,在models.py中定义如下

class Aip(Document):
    '''
    自动投资计划:Automatic investment plan
    '''
    meta = {'db_alias': 'test', 'indexes': [略]}
    user_id = IntField(required=True)
    invest_flag = StringField(required=True)
    invest_code = StringField(required=True)
    apply_sum = StringField(required=True)
    trade_acco = StringField(required=True)
    cycle_unit = StringField(required=True)
    jyrq = StringField(required=True)  
    delay_day = IntField(default=2)
    next_kkdate = DateTimeField()    # 考虑顺延和工作日
    cur_kkdate = DateTimeField()      # 不考虑顺延和工作日,用于连续循环更新
    total_sum = IntField(default=0)
    count = IntField(default=0)
    delay_count = IntField(default=0)
    fail_count = IntField(default=0)
    is_active = BooleanField(default=True)
    soft_del = BooleanField(default=False)
    created = DateTimeField(default=datetime.datetime.now)

    # 扣款日期描述
    @property
    def kkdate_desc(self):
        if self.cycle_unit == '0':
            desc = '每月' + str(int(self.jyrq)) + '日'
        elif self.cycle_unit == '1':
            desc = '每周' + WEEKDAY_DICT[int(self.jyrq)]
        elif self.cycle_unit == '2':
            desc = '每天'
        else:
            desc = ''
        return desc

class Aiphis(Document):
    meta = {'db_alias': 'test', 'indexes': [略)]}

    aip_id = StringField(required=True)
    state = StringField(required=True)
    order_id = StringField()           # 关联交易订单
    apply_sum = StringField()
    trade_date = DateTimeField()
    created = DateTimeField(default=datetime.datetime.now)

二、用户的交互

与用户的交互当然是前端操作,但后端需要考虑到操作需求,以便提供足够丰富的接口,最基本的不外乎对于Aip的增删改查和对Aiphis的查,Aiphis的增是在执行中调用。
1)create_aip # 创建
2)query_aip_detail # 查询单个aip详情,调用get_aiphis_list获取对应的定投历史
3)query_aip_list # 查询用户名下所有的定投计划
4)update_aip # 更新、包括修改日期、金额、标的,暂停、激活、终止、重启
5)create_aiphis # 定投执行时,不论成败均会创建一条记录
6)get_aiphis_list # 获取定投历史记录

三、定投的执行

每日执行定投的任务脚本
1、只有扣款日期next_kkdate == today才会执行
2、定投成功,创建成功的Aiphis --- 5
3、余额不足则顺延,

  • 1)日定投不顺眼直接判定失败,创建失败的Aiphis --- 5
  • 2)顺延次数+1,达到次数限制则判定失败,aip的顺延次数清零,创建失败的Aiphis
  • 3)其余定投方式要将next_kkdate顺延至下一个交易日,并创建Aiphis

4、定投失败,同时要创建失败的Aiphis --- 5
5、创建Aiphis

  • 1)失败,要更新失败次数 --- 6
  • 2)成功,关联当前的order_id,更新aip的total_sum和count,并清空aip的顺延和失败次数
  • 3)不论成功失败,都需要更新下一次扣款日期 --- 7

6、更新失失败次数,+1,达到上限则终止定投计划
8、更新下一次扣款日期(定投日期)

  • 1)首先根据cur_kkdate计算下一个周期的日期next_day(不一定是交易日)
  • 2)其次根据next_day寻找最近的一个交易日next_tradeday,包括next_day自身
  • 3)如果是按天定投,则cur_kkdate = next_kkdate = next_tradeday;否则,cur_kkdate = next_day,next_kkdate = next_tradeday。

9、考虑到会出现第三方买入接口超时导致结果不确定的情况,应当当时的order_id,创建Aiphis并标记为“未知”,然后第二天进行同步检查 --- 0
0、 每天执行前,需要同步检查前一日标记为“未知”的定投记录,然后根据成功、失败、未知分别处理。

最后贴上update_next_kkdate的代码,并推荐一个处理时间间隔的第三方库dateutil.relativedelta,让你在处理时间的时候摆脱边界问题的困扰。

# 按照Pep8的要求分块引入内建、第三方、自己实现的库
import datetime

from dateutil.relativedelta import relativedelta

from util.date import TradeDay        # 自定义抓取的交易日期记录


def update_next_kkdate(aip):
    aip = aip if hasattr(aip, 'id') else Aip.get(id=aip)
    td = aip.cur_kkdate
    if aip.cycle_unit == '0':
        nd = td + relativedelta(months=1)      # 月
    elif aip.cycle_unit == '1':
        nd = td + relativedelta(weeks=1)        # 周
    elif aip.cycle_unit == '2':
        nd = td + relativedelta(days=1)          # 日
    else:
        nd = td
    # 最近的一个交易日
    tradeday = TradeDay.objects(
        trade_day__gte=int(nd.strftime("%Y%m%d"))).first()
    aip.next_kkdate = datetime.datetime.strptime(
        str(tradeday.trade_day), "%Y%m%d")

    if aip.cycle_unit == '2':
        aip.cur_kkdate = aip.next_kkdate
    else:
        aip.cur_kkdate = nd
    aip.save()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容