好久没写这种一二三四五的长文了,从上家公司出来的时候,我已经做好了要离开舒适圈的打算。那之后,写过私活,也参与了几个开源项目,其他时间除了必要的生活琐事,都花在了看书上。

买了四本书,分别是:《HTTP 权威指南》,《深入浅出 Nodejs》,《Docker 容器与容器云》和《算法基础》。现阶段我可以更肆无忌惮地学习自己喜欢的东西,而不被打扰,同时自由支配的时间更多了。晚上,关上电脑冲个凉后,泡上一壶绿茶或者熟普,美美地翻几页书,累了就睡觉,便觉得是个自由的人。

从宁波离开后,我带着手头的一两个项目来到广州,住进一个不太贵的小公寓,外面的街道充满了烟火气,超市里的喇叭总是叫卖着特价商品,沿街的商店什么都有,电线杆子上贴着小广告和「不要打架,打输住院,打赢坐牢」,常用情侣们从各自的小窝出来,手挽着手去买菜,或是点上两杯奶茶。

值得一提的是,这边的奶茶也不贵,花上五块钱可以喝一杯不正宗的白咖啡,加两块钱可以喝超大杯的珍波椰奶茶。虽然被告知治安不太好,但对于一个独来独往的成年男人而言,谁又会去下手呢(杰哥?)。

本以为疫情的影响渐渐消退,但最近广东又新增了几起病例,还是得提防一下,因此这几天都有好好戴口罩。

广东的雨让我联想起广东雨神的《广东十年爱情故事》,这首歌是被我寄存了回忆的,虽然现在听来土的一比。在部队的日子里,炊事班经常拿一个大音响(拉着走的那种)放这首歌,到副歌部分基本大家都能唱出来,很有感觉。

昨晚梦到了部队的日子,那是在 18 年的 8 月,临近退伍了,几个老兵在晚点名后,偷跑到饭堂后面的水槽拆水龙头洗澡,光着膀子,抬头看湖南的星空一片灿烂,满怀期待地憧憬着退伍后地生活。醒来时,才发现已经是下午五点了,窗外飘着冷雨,关窗时一阵哆嗦,赶紧把短袖换成长袖,再打开一天的生活。

我把今年定义为沉淀的一年,无论是心性还是技能都一样。这一年里,我要放慢生活节奏,想通一些事情,为将来的打算做足准备,积蓄力量。

思路

通过聚宽平台提供的数据接口,跟踪北向资金出入数据,追涨来实现套利。代码获取了每日的北向流入额,然后用 150 日的流入额做出了一个布林带,当流入额大于等于布林带上轨的时候,策略会跟踪北向持仓中股票自身占比最大的 15 只股票,然后把他们买入,并且卖出自己持仓中不在这 15 只股票。当流入额小于等于布林带下轨时,清仓卖出。

代码

from jqdata import *
import random
import pandas as pd
import numpy as np
import datetime as dt

# 初始化函数,设定基准等等
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.max_stock_count = 15
    g.buy_pool = []
    g.sale_pool = []
    g.top_money_in = []
    g.window = 90
    g.stdev_n = 1
    g.mf, g.upper, g.lower = None, None, None
    run_daily(before_market_open, time='07:00')
    run_daily(reblance, '9:30')

def before_market_open(context):
    g.buy_pool = []
    g.sale_pool = []
    pre_date = (context.current_dt - datetime.timedelta(1)
                ).strftime('%Y-%m-%d')
    g.mf, g.upper, g.lower = get_boll(pre_date)
    log.info('北上资金均值:%.2f  北上资金上界:%.2f 北上资金下界:%.2f' % (g.mf, g.upper, g.lower))

def get_boll(end_date):
    """
    获取北向资金布林带
    """
    # 数据表:北上资金某日成交额
    table = finance.STK_ML_QUOTA
    # 查询:日期,每日额度,每日余额
    q = query(
        table.day, table.quota_daily, table.quota_daily_balance
    ).filter(
        table.link_id.in_(['310001', '310002']), table.day <= end_date
    ).order_by(table.day)
    money_df = finance.run_query(q)
    # 每日额度 - 每日余额
    money_df['net_amount'] = money_df['quota_daily'] - \
        money_df['quota_daily_balance']
    # 分组求和
    money_df = money_df.groupby('day')[['net_amount']].sum().iloc[-g.window:]
    mid = money_df['net_amount'].mean()
    stdev = money_df['net_amount'].std()
    upper = mid + 1*g.stdev_n * stdev
    lower = mid - 0.17*g.stdev_n * stdev
    mf = money_df['net_amount'].iloc[-1]
    return mf, upper, lower

def calc_change(context):
    # 数据表:北上资金持有股票数据
    table = finance.STK_HK_HOLD_INFO
    # 查询:日期,名称,代码,持股比例
    q = query(table.day, table.name, table.code, table.share_ratio).filter(
        table.link_id.in_(['310001', '310002']), table.day.in_([context.previous_date]))
    # 获取查询数据结果
    df = finance.run_query(q)
    # 将结果数据集根据持股比例进行降序排序,选出前x只股,x即为 g.max_stock_count 的值,返回结果
    return df.sort_values(by='share_ratio', ascending=False)[:g.max_stock_count]['code'].values

### 每日比较算法 ###
def reblance(context):
    if g.mf >= g.upper:
        s_change_rank = calc_change(context)
        final = list(s_change_rank)
        current_hold_funds_set = set(context.portfolio.positions.keys())
        if set(final) != current_hold_funds_set:
            need_buy = set(final).difference(current_hold_funds_set)
            need_sell = current_hold_funds_set.difference(final)
            cash_per_fund = context.portfolio.total_value/g.max_stock_count*0.99
            for fund in need_sell:
                # order_target(fund, 0)
                add_to_sale_pool(fund)
            for fund in need_buy:
                # order_value(fund, cash_per_fund)
                add_to_buy_pool(fund, cash_per_fund)
        

    elif g.mf <= g.lower:
        current_hold_funds_set = set(context.portfolio.positions.keys())
        if len(current_hold_funds_set) != 0:
            for fund in current_hold_funds_set:
                #log.info('买入 %s' % fund.name)
                # order_target(fund, 0)
                add_to_sale_pool(fund)
           
### 当天卖出队列 ###
def add_to_sale_pool(security_code):
    if len(g.sale_pool) <= g.max_stock_count:
        g.sale_pool.append(security_code)
    
### 当天买入队列 ###
def add_to_buy_pool(security_code, cash):
    if len(g.sale_pool) <= g.max_stock_count:
        g.buy_pool.append({'code': security_code, 'cash': cash})
        log.info(g.buy_pool)

### 买卖策略 ###
'''
测试时暂时空仓
'''
def handle_data(context):
    if len(g.buy_pool) > 0:
        length = len(g.buy_pool)
        idx = 0
        while length > 0:
            buy_item = g.buy_pool[idx]
            order_value(buy_item['code'], buy_item['cash'])
            del g.buy_pool[idx]
            length = length - 1

    elif len(g.sale_pool) > 0:
        length = len(g.sale_pool)
        idx = 0
        while length > 0:
            sale_code = g.sale_pool[idx]
            order_target(sale_code, 0)
            del g.sale_pool[idx]
            length = length - 1