一個適用于新手的量化交易模板

ricequant 的用戶 ming gao 在社區里為我們貢獻了一個很有趣的模板~
小編決定把回測結果放在前面

以下是正文:
關鍵詞:策略模板、策略、策略交易、新人、模板、模塊……
引言:
在rice里混了大半年了,學習了不少大牛的有用知識,也編寫了一大堆的有的沒的策略,但是每次都面臨大量的重復勞動,費時費力,于是這里就總結了一個適合新人的交易策略的模板分享給大家。
原理:
看了大家的策略,和查閱了一些資料,也總結了和歸納了一些,大概分為,選股、進場時機、持倉平衡、現金管理、出場時機、風險管理,一些工具組件~
不廢話了,直接上demo代碼簡單寫~
模板代碼:
說明:分鐘回測,組合初始100萬現金,交易手續默認的,無賣空,benchmark為默認,進場策略輸出了股票列表,出場策略也是返回要賣出的股票列表……
1、傳奇的小市值策略(市值最小的100只股票做為每天的備選列表),這個因子表現的最好,為避免模板的demo曲線表現太差,所以用了這個吸引眼球的選股因子,高手勿噴,勿笑;
2、進場以大家熟悉的5日均線上傳10日均線,并保持20日線為進場條件;(感覺自己寫復雜了,反正是模板)
3、出場為低于20日線的99%為強制出場(20日線,在近兩年基本作為了行業標配了,反正有點用)
4、持倉策略:在市值不便的情況下平均持倉,每日臨近收盤進行再平衡,理想情況保持每只持倉占比相等(
5、現金管理:本來想再收盤前現金買進“銀華日利”,但由于默認市價交易滑點太大,就省略了(要限價交易才可能有利潤,但是這里也發現尾盤表現非常不好,就注釋掉了)
6、風險管理:略了,流傳一個大盤跌過3%的強制止損風險策略,小市值也可以增加二八輪動的擇時,沒有加上,有興趣可以自己弄著玩,加這個模塊里;
7、交易方式:為了避免過大的成家量超過25%的error,這里都下的限價單,但是后續模塊化吧,另控制了單只持倉不超過10%~(模塊寫起來比較復雜,還要新建dict進行撤單再下單等計算,后續成熟了,再拿出來分享)
8、工具:
trans、歷史數據強制轉化成真正的DataFrame(效率問題,做了.T的來回變換),問licco說,其實2016年5月2X后就不需要了
n日內隨機交易的收益率概率(例子中未用到)
多個list里取交集,懶得每次都寫了,干脆寫了個小函數
標的上市自然日的函數,避免次新股對收益干擾太大,要真像炒次新股,要好好研究一下,個人做過嘗試發現,高風險高利潤,大盤擇時比較關鍵,干脆這里做了過濾比如一定要上市超過60個自然日
是否漲跌停區間,一定要可交易,人家都封版了,交易量有,關咱策略毛事,所以也進行了過濾
因為分鐘回測,所以選擇了14:50作為買入時間點,而出場選擇每15分鐘采樣計算一次(性能壓力和必要性的問題),14:59(后來發現深圳市場應該14點56,要不57開始深圳會集合競價了,但是例子里沒有調整,所以還是error一大堆,見笑了)
接下來就是代碼啦,可讀性還是很強的,大家可以去ricequant上克隆一下運行起來試試。

import pandas as pd
import numpy as np
import time
import math
import itertools

# 數據準備

def init_variables (context):
    context.init = 0 
    context.days = 0
    context.barcount = 0
    context.choosenum = 300
    context.obv = 50
    context.tj1 = 5 # 5日均線
    context.tj2 = 10 # 10日均線
    context.tj3 = 20 # 20日均線
    context.his = pd.DataFrame()
    return


'''第1部、選擇標的'''

def choose_target(context):
    # 最小市值的100只標的
    df = get_fundamentals(
        query(fundamentals.eod_derivative_indicator.market_cap)
        .order_by(fundamentals.eod_derivative_indicator.market_cap.asc())
        .limit(context.choosenum)
    )
    context.stocks = [stock for stock in df][:100]
    return context.stocks

'''第2部、入場策略'''
#2.1 大盤環境問題
    #可增加外部數據

#2.2 個股選擇問題,最后還要過濾非跌停、上市天數、非停牌的標的(st未過濾)
def for_buy(context, bar_dict, his):
    #2.2.1 備選中標的站上5日線
    def tj1(context, bar_dict, his):
        ma_n = pd.rolling_mean(his, context.tj1)
        temp = his - ma_n
        temp_s = list(temp[temp>0].iloc[-1,:].dropna().index)
        return temp_s
    #2.2.2 備選中標的站上10日線
    def tj2(context, bar_dict, his):
        ma_n = pd.rolling_mean(his, context.tj2)
        temp = his - ma_n
        temp_s = list(temp[temp>0].iloc[-1,:].dropna().index)
        return temp_s
    
    #2.2.2 所謂金叉,今天短均線大于長均線,上一個bar反之
    def tj3(context, bar_dict, his):
        mas = pd.rolling_mean(his, context.tj1)
        mal = pd.rolling_mean(his, context.tj2)
        temp = mas - mal
        temp_jc = list(temp[temp>0].iloc[-1,:].dropna().index)
        temp_r = list(temp[temp>0].iloc[-2,:].dropna().index)
        temp = []
        for stock in temp_jc:
            if stock not in temp_r:
                temp.append(stock)
        return temp
    
    #整合各個子條件的交集
    
    l1 = tj1(context, bar_dict, his)
    l2 = tj2(context, bar_dict, his)
    l3 = tj3(context, bar_dict, his)
    l_tar = jj_list([l1,l2,l3])
    to_buy = []
    #過濾上市時間、是否漲停、是否停牌等條件
    if l_tar:
        for stock in l_tar:
            con1 = ipo_days(stock,context.now)>60
            con2 = zdt_trade(stock,context,bar_dict)
            con3 = bar_dict[stock].is_trading
            if con1 & con2 & con3:
                to_buy.append(stock)
    return to_buy


'''第3部、持倉組合的微調策略'''
# 平均市值做微調
def for_balance(context, bar_dict):
    #mvalues = context.portfolio.market_value
    #avalues = context.portfolio.portfolio_value
    #per = mvalues / avalues
    hlist = []
    for stock in context.portfolio.positions:
        hlist.append([stock,bar_dict[stock].last * context.portfolio.positions[stock].quantity])
    
    if hlist:
        hlist = sorted(hlist,key=lambda x:x[1], reverse=True)
        temp = 0
        for li in hlist:
            temp += li[1]
        for li in hlist:
            if bar_dict[li[0]].is_trading:
                order_target_value(li[0], temp/len(hlist))
    return

'''第4部、出場策略'''
# 小于20日均線,并且可交易,沒跌停
def for_sell(context, bar_dict):
    to_sell = []
    for stock in context.portfolio.positions:
        con1 = bar_dict[stock].last < 0.99 * bar_dict[stock].mavg(20, frequency='day')
        con2 = bar_dict[stock].is_trading
        con3 = zdt_trade(stock,context,bar_dict)
        if con1 & con2 & con3:
            to_sell.append(stock)
    return to_sell

'''第5部、閑置資金效率最大化'''
def for_cash(context, bar_dict):
    cash = context.portfolio.cash
    #order_target_value('511880.XSHG',cash) 注釋掉因為滑點太大,可以買一個貨基,或者逆回購
    return 

'''第6部、風險控制'''
def alert_rish(context,bar_dict):
    #這里如果給出策略,要強制執行,注意在handle優先級高于所有
    pass

'''第7部、備用組件'''

#7.1 將his的非標DF進行轉換,licco說現在不用轉換了,我還是保留了:)
def trans(df):
    temp = pd.DataFrame()
    for col in df.index:
        temp[col] = df.T[col]
    return temp.T

#7.2 計算n日概率隨機交易的概率收益率
def rts_sj(df,n,m): 
    dfp_pct = df.pct_change()
    def the_list(df,n,m):
        temp = []
        for i in range(n,n+m):
            temp.append(df.iloc[-i,:] + 1)
        return temp
    def from_list(self,num):
        result = []
        for i in range(1,num+1):
            result.extend(list(itertools.combinations(self,i)))
        return result
    def rts_n(tu):
        sum0 = []
        for i in tu:
            temp = 1
            for z in i:
                temp = temp * z
            temp = temp**(1/len(i))
            sum0.append(temp)
        sum1 = 0
        for i in sum0:
            sum1 = sum1 + i - 1
        return sum1/len(sum0)
    return rts_n(from_list(the_list(dfp_pct,n,m),m)) 

#7.3 多list獲得并集
def jj_list(tar_list):
    temp = tar_list[0]
    for i in tar_list:
        temp = list(set(temp).intersection(set(i)))
    return temp

#7.4 標的上市時間距離參照時間的自然日數量
def ipo_days(stock, today):
    ssrq = instruments(stock).listed_date.replace(tzinfo=None)
    today = today.replace(tzinfo=None)
    return (today - ssrq).days

#7.5 判斷當前標在可交易區間內(非漲跌停)
def zdt_trade(stock, context, bar_dict):
    yesterday = history(2,'1d', 'close')[stock].values[-1]
    zt = round(1.10 * yesterday,2)
    dt = round(0.99 * yesterday,2)
    return dt < bar_dict[stock].last < zt
    



'''--------------華麗的分割線----------------'''

def init(context):
    init_variables(context)
    choose_target(context)


# before_trading此函數會在每天交易開始前被調用,當天只會被調用一次
def before_trading(context, bar_dict):
    choose_target(context)
    update_universe(context.stocks)
    context.his = trans(history(context.obv,'1d','close'))
    context.barcount = 0
    context.init = 1
    pass


# 你選擇的證券的數據更新將會觸發此段邏輯,例如日或分鐘歷史數據切片或者是實時數據切片更新
def handle_bar(context, bar_dict):
    context.barcount += 1
    
    alert_rish(context,bar_dict)
    
    #模擬交易第一次開始,如果是交易時間可能運行不了before_trading,所以這里做了個參數來控制這種出錯的特例
    if context.init == 0:
        update_universe(context.stocks)
        context.his = trans(history(context.obv, '1d', 'close'))
        context.barcount = 0
        context.init = 1
    else:
        pass
    
    if context.barcount % 15 == 0:
        to_sell = for_sell(context, bar_dict)
        if to_sell:
            for oid in get_open_orders().keys():
                cancel_order(oid)
            for stock in to_sell:
                order_target_value(stock, 0, style=LimitOrder(bar_dict[stock].last*0.995))
    
    if context.barcount == 230:
        his = trans(history(2,'1m','close'))
        his = context.his.append(his.iloc[-1,:],ignore_index=True)
        to_buy = for_buy(context, bar_dict, his)
        if to_buy:
            print (to_buy)
        hnum = len(list(set(to_buy).union(set(context.portfolio.positions.keys()))))
        for stock in to_buy:
            if hnum <10:
                print ('buy', stock, bar_dict[stock].high * 1.005)
                order_target_percent(stock, 0.99/10, style=LimitOrder(bar_dict[stock].high * 1.005))
            else:
                order_target_percent(stock, 0.99um, style=LimitOrder(bar_dict[stock].high * 1.005))
    
    if context.barcount == 236: 
        his = trans(history(2,'1m','close'))
        his = context.his.append(his.iloc[-1,:],ignore_index=True)
        for_balance(context, bar_dict)
        for_cash(context, bar_dict)  
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容