老虎证券美股策略——将动量策略日频调仓改成月频

最近策略频繁回撤,跑不赢标普500指数,所以对策略简单修改,以待后效。
新加入的代码

def get_if_trade_day():
    infile = open('countday.dat','r')
    incontent = infile.read()
    infile.close()
    num=int(incontent)
    if num !=0 :
        num = (num+1)%4
        outfile = open('countday.dat','w')
        outfile.write(str(num))
        outfile.close()
        return False
    else :
        num = (num+1)%4
        outfile = open('countday.dat','w')
        outfile.write(str(num))
        outfile.close()
        return True

在策略文件目录下建立countday.dat文件写入0,在crontab配置里改成每周一晚上9点定时运行,get_if_trade_day()函数会每四次返回一次True,大概四周调仓一次

import os
import logging
import traceback
import pandas as pd
import numpy as np
import talib as ta
import re
import math
import threading
import time
import sys
import signal
import copy
from scipy import stats  # using this for the reg slope
from datetime import datetime
#账户设置包导入
from tigeropen.examples.client_config import get_client_config
#交易包导入
from tigeropen.trade.domain.order import ORDER_STATUS
from tigeropen.trade.request.model import AccountsParams
from tigeropen.common.response import TigerResponse
from tigeropen.tiger_open_client import TigerOpenClient
from tigeropen.trade.trade_client import TradeClient
from tigeropen.quote.request import OpenApiRequest
from tigeropen.common.util.contract_utils import option_contract, future_contract
#行情包导入
from tigeropen.examples.sp500 import save_sp500_tickers as getsp500code
from tigeropen.common.consts import Market, QuoteRight, BarPeriod,Language
from tigeropen.quote.quote_client import QuoteClient

momentum_window = 60  # first momentum window.
momentum_window2 = 90  # second momentum window
minimum_momentum = 60
exclude_days = 5  # excludes most recent days from momentum calculation
#判断可以买入的股票代码
buy_set=set()
final_buy_list=[]
#当前持仓股票代码和数量的字典
cur_quant={}
goal=3
num_of_stocks=3
inv_vola_table=[]
#日志设置
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(levelname)s %(message)s',
                    filemode='a', )
logger = logging.getLogger('TigerOpenApi')
#客户端设置
client_config = get_client_config()
#定义交易客户端
openapi_client = TradeClient(client_config, logger=logger)
#定义行情客户端
quote_client = QuoteClient(client_config, logger=logger)
#获取标普500的20日bars
def get_price(bars_num):
    stocks = read_sp_500code()
    df=pd.DataFrame(columns=['symbol','time','open','high','low','close','volume'])
    #每次取数据要取的条目数量
    items=math.floor(1000/bars_num)
    stock_num=len(stocks)
    print(stock_num)
    roundtrip=math.floor(stock_num/items)
    print(roundtrip)

    for i in range(roundtrip):
        bars = quote_client.get_bars(stocks[i*items:i*items+items],period=BarPeriod.DAY,begin_time=-1, end_time=-1,right=QuoteRight.BR,limit=bars_num)
        df=df.append(bars,ignore_index=True)
        print(bars)
    bars = quote_client.get_bars(stocks[roundtrip*items:stock_num],\
                     period=BarPeriod.DAY,begin_time=-1,end_time=-1,right=QuoteRight.BR,limit=bars_num)
    df=df.append(bars,ignore_index=True)
    df.drop(['time','open','high','low','volume'],axis=1,inplace=True)
    print(df)
  
    return_df=pd.DataFrame(columns=stocks)
    for i in range(len(stocks)):
        close = df[i*bars_num:i*bars_num+bars_num]['close'].values
        print(close)
        print(type(close))
        return_df[stocks[i]]=close
    print(return_df)
    return return_df

def inv_vola_calc(ts):
    """
    Input: Price time series.
    Output: Inverse exponential moving average standard deviation. 
    Purpose: Provides inverse vola for use in vola parity position sizing.
    """
    returns = np.log(ts).diff()
    stddev = returns.ewm(halflife=20, ignore_na=True, min_periods=0,
                         adjust=True).std(bias=False).dropna()
    return 1 / stddev.iloc[-1]

def slope(ts):
    """
    Input: Price time series.
    Output: Annualized exponential regression slope, multipl
    """
    x = np.arange(len(ts))
    log_ts = np.log(ts)
    #print('This is ts',ts)
    #print('This is log_ts:\n',log_ts)
    #print(' ')
    slope, intercept, r_value, p_value, std_err = stats.linregress(x, log_ts)
    #print(slope)
    annualized_slope = (np.power(np.exp(slope), 250) - 1) * 100
    #print('This is annualized_slope',annualized_slope)
    return annualized_slope * (r_value ** 2)  

def generate_trade_info():
    stocks = read_sp_500code()
    hist_window = max(momentum_window,
                      momentum_window2) + exclude_days
    hist=get_price(hist_window)
    print(hist)

    data_end = -1 * exclude_days # exclude most recent data
    momentum1_start = -1 * (momentum_window + exclude_days)
    momentum_hist1 = hist[momentum1_start:data_end]

    momentum2_start = -1 * (momentum_window2 + exclude_days)
    momentum_hist2 = hist[momentum2_start:data_end]

    momentum_list = momentum_hist1.apply(slope)  # Mom Window 1
    momentum_list2 = momentum_hist2.apply(slope)  # Mom Window 2

    # Combine the lists and make average
    momentum_concat = pd.concat((momentum_list, momentum_list2))
    mom_by_row = momentum_concat.groupby(momentum_concat.index)
    mom_means = mom_by_row.mean()

    # Sort the momentum list, and we've got ourselves a ranking table.
    ranking_table = mom_means.sort_values(ascending=False)

    buy_list = ranking_table[:num_of_stocks]
    global final_buy_list
    final_buy_list = buy_list[buy_list > minimum_momentum] # those who passed minimum slope requirement

    # Calculate inverse volatility, for position size.
    global inv_vola_table
    inv_vola_table = hist[buy_list.index].apply(inv_vola_calc)
    
    global buy_set
    buy_set = set(final_buy_list)
    
def trade(account):
    
    #generate_trade_info()
    #account = client_config.paper_account
    curset=get_curset(account)
    #设计pos_local_curset来应对主观交易的问题
    pos_local_curset=get_local_curset(account)
    temp_pos_local_curset = copy.deepcopy(pos_local_curset)

    # Sell positions no longer wanted.
    for security in temp_pos_local_curset:
        if (security not in set(final_buy_list.index)):
            print('preparing substitution')
            print(security)
            contract = openapi_client.get_contracts(security)[0]
            print(cur_quant)
            quant=cur_quant[security]
            print(quant)
            pos_local_curset.remove(security)
            order = openapi_client.create_order(account, contract, 'SELL', 'MKT' , cur_quant[security])
            openapi_client.place_order(order)
    # sum inv.vola for all selected stocks.  

    sum_inv_vola = np.sum(inv_vola_table)            
    vola_target_weights = inv_vola_table / sum_inv_vola
    cur_asset=openapi_client.get_assets(account=account)
    potfolio=cur_asset[0]
    print(potfolio)
    print(potfolio.summary)
    #cash=potfolio.summary.available_funds
    cash=potfolio.summary.net_liquidation*0.5

    print(final_buy_list)
    print(type(final_buy_list))
    for security in set(final_buy_list.index):
        # allow rebalancing of existing, and new buys if can_buy, i.e. passed trend filter.
        weight = vola_target_weights[security]
        contract = openapi_client.get_contracts(security)[0]
            #print(contract)
            #print(type(tobuy_list[i]))
            #print(tobuy_list[i])
        brief=quote_client.get_briefs(symbols=[security],right=QuoteRight.BR)
        latest_price=brief[0].latest_price
        quant=math.floor(cash*weight/latest_price)
        opt='BUY'
        if (security in curset): 
            net=math.floor(cash*weight/latest_price)-cur_quant[security]
            if net > 0:
                quant=net
                if abs(net)/cur_quant[security] < 0.1 :
                    continue
            elif net < 0:
                opt='SELL'
                quant=-net
                if abs(net)/cur_quant[security] < 0.1 :
                    continue
            else: 
                continue
        else:
            pos_local_curset.add(security)
        
        if math.floor(cash*weight/latest_price) == 0 :
            pos_local_curset.remove(security)
        if quant == 0:
            continue

        order = openapi_client.create_order(account, contract, opt, 'MKT' , quant)
        print('buy order sent')
        openapi_client.place_order(order)

    write_pos_local_curset(list(pos_local_curset),account)

       
#position对象:[contract: TVIX/STK/USD, quantity: 5, average_cost: 36.0764, market_price: 0.0]
def get_curset(account):
    pos=openapi_client.get_positions(account=account)
    print(len(pos))
    curset=set()
    global cur_quant
    cur_quant={}
    for i in range(len(pos)):
        print('当前持仓\n',pos[i])
        cont=pos[i].contract
        code = re.search(r'[A-Z]*(?=/STK)',str(cont)).group(0)
        curset.add(code)
        cur_quant[code]=pos[i].quantity
    return curset

def get_local_curset(account):
    if client_config.account == account :
        infile = open('account_local_curset.dat','r')
    elif client_config.paper_account == account :
        infile = open('paper_local_curset.dat','r')
    incontent = infile.read()
    infile.close()
    if incontent.strip() == str(''):
        return set()
    print(incontent)
    incontent=incontent.lstrip('[')
    print(incontent)
    incontent=incontent.rstrip(']\n')
    print(incontent)
    stocks = incontent.split(', ')
    print(stocks)
    print(len(stocks))
    print(eval(stocks[0]))
    new= []
    for i in range(len(stocks)):
        new.append(eval(stocks[i]))
    return set(new)

def write_pos_local_curset(pos_local_curset,account):
    if client_config.account == account :
        outfile = open('account_local_curset.dat','w')
    elif client_config.paper_account == account :
        outfile = open('paper_local_curset.dat','w')
    outfile.write(str(pos_local_curset))
    outfile.close()
#卖出当前所有持仓
def all_off(account):
    pos=openapi_client.get_positions(account=account)
    for i in range(len(pos)):
        contract=pos[i].contract
        quantity=pos[i].quantity
        if quantity>0:
            order = openapi_client.create_order(account, contract, 'SELL', 'MKT' , quantity)
            openapi_client.place_order(order)
        elif quantity<0:
            order = openapi_client.create_order(account, contract, 'BUY', 'MKT' , -quantity)
            openapi_client.place_order(order)
    if client_config.account == account :
        outfile = open('account_local_curset.dat','w')
    elif client_config.paper_account == account :
        outfile = open('paper_local_curset.dat','w')
    outfile.write('')
    outfile.close()

def read_sp_500code():
    infile = open('sp500code.dat','r')
    incontent = infile.read()
    incontent=incontent.lstrip('[')
    incontent=incontent.rstrip(']')
    stocks = incontent.split(', ')
    new= []
    for i in range(len(stocks)):
        new.append(eval(stocks[i]))
    return new

def before_market_open():
    generate_trade_info()
def get_if_trade_day():
    infile = open('countday.dat','r')
    incontent = infile.read()
    infile.close()
    num=int(incontent)
    if num !=0 :
        num = (num+1)%4
        outfile = open('countday.dat','w')
        outfile.write(str(num))
        outfile.close()
        return False
    else :
        num = (num+1)%4
        outfile = open('countday.dat','w')
        outfile.write(str(num))
        outfile.close()
        return True

#Not Yet Open, Pre Market Trading, Market Trading
if __name__ == '__main__':
    if get_if_trade_day() == False:
        print('Dont trade')
        sys.exit(0)
    print('trade')
    if 'account' == sys.argv[2] :
        account = client_config.account
    elif 'paper_account' == sys.argv[2] :
        account = client_config.paper_account
    if sys.argv[1] == 'trade':    
        p = threading.Thread(target=before_market_open)
        p.setDaemon(True)
        p.start()
        time.sleep(10)
        p.join()
        while True: 
            market_status_list = quote_client.get_market_status(Market.US)
            print(market_status_list)
            if market_status_list[0].status == 'Trading':
                trade(account)
                break
            time.sleep(60)
    elif sys.argv[1] == 'all_off':
    #get_price(95)
    #trade()
    #get_curset(client_config.paper_account)
        all_off(account)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章