老虎證券美股策略——將動量策略日頻調倉改成月頻

最近策略頻繁回撤,跑不贏標普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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章