From de5125a42e92b2172a97759d651ba6781dc5b789 Mon Sep 17 00:00:00 2001 From: James Ma Date: Tue, 18 Jun 2019 23:15:40 +0800 Subject: [PATCH] add req methods --- main.py | 68 +++++++------ models/hft_model.py | 233 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 232 insertions(+), 69 deletions(-) diff --git a/main.py b/main.py index 0a4de6f..c9d6bc0 100644 --- a/main.py +++ b/main.py @@ -1,42 +1,50 @@ -from ib_insync import util +from ib_insync import Forex from models.hft_model import HftModel if __name__ == '__main__': - model = HftModel(host='localhost', - # port = 4002, - port = 7497, - client_id = 1, - is_use_gateway = False, - evaluation_time_secs = 15, - resample_interval_secs = '30s') - model.run() + model = HftModel( + host='localhost', + # port = 4002, + port=7497, + client_id=1, + is_use_gateway=False, + evaluation_time_secs=15, + resample_interval_secs='30s' + ) - # from ib_insync import * + to_trade = [ + ('EURUSD', Forex('EURUSD')), + ('USDJPY', Forex('USDJPY')) + ] - # util.startLoop() + model.run(to_trade=to_trade) - # ib = IB() - # ib.connect('127.0.0.1', 7497, clientId=15) +# from ib_insync import * + +# util.startLoop() + +# ib = IB() +# ib.connect('127.0.0.1', 7497, clientId=15) - # contracts = [Forex(pair) for pair in ('EURUSD', 'USDJPY', 'GBPUSD', 'USDCHF', 'USDCAD', 'AUDUSD')] - # ib.qualifyContracts(*contracts) - # - # eurusd = contracts[0] - # - # for contract in contracts: - # ib.reqMktData(contract, '', False, False) - # - # ticker = ib.ticker(eurusd) - # ib.sleep(2) +# contracts = [Forex(pair) for pair in ('EURUSD', 'USDJPY', 'GBPUSD', 'USDCHF', 'USDCAD', 'AUDUSD')] +# ib.qualifyContracts(*contracts) +# +# eurusd = contracts[0] +# +# for contract in contracts: +# ib.reqMktData(contract, '', False, False) +# +# ticker = ib.ticker(eurusd) +# ib.sleep(2) - # eurusd = Forex('EURUSD') - # ticker = ib.reqTickByTickData(eurusd, 'BidAsk') - # while True: - # print(ticker) - # ib.sleep(2) - # - # print(ticker) +# eurusd = Forex('EURUSD') +# ticker = ib.reqTickByTickData(eurusd, 'BidAsk') +# while True: +# print(ticker) +# ib.sleep(2) +# +# print(ticker) # from ib_insync import * # diff --git a/models/hft_model.py b/models/hft_model.py index 8280d5d..186f2f0 100644 --- a/models/hft_model.py +++ b/models/hft_model.py @@ -5,55 +5,211 @@ from ib_insync import IB, Stock, Forex from ib_insync.util import df +from dateutil import tz class HftModel(object): + def __init__( self, - host='localhost', port=4001, - client_id=101, is_use_gateway=False, evaluation_time_secs=20, + host='localhost', port=4001, client_id=101, + is_use_gateway=False, evaluation_time_secs=20, resample_interval_secs='30s', moving_window_period=dt.timedelta(hours=1) ): + self.host = host + self.port = port + self.client_id = client_id + self.ib = IB() - def run(self): - self.ib.connect('127.0.0.1', 7497, clientId=101) + self.utc_timezone = tz.tzutc() + self.local_timezone = tz.tzlocal() + + self.symbol_map = {} + self.historical_data = {} # of mid prices + self.df_hist = None + + self.symbols = [] - eurusd = Forex('EURUSD') - usdjpy = Forex('USDJPY') - # ticker = ib.reqTickByTickData(eurusd, 'BidAsk') - # ticker = ib.reqTickByTickData(usdjpy, 'BidAsk') - # ib.pendingTickersEvent += self.on_ticker - dt = '' + self.volatility_ratio = 1 + self.beta = 0 + self.moving_window_period = dt.timedelta(hours=1) + self.is_buy_signal, self.is_sell_signal = False, False - self.hist = self.get_historical_data(eurusd) - # print(hist) + def run(self, to_trade=[]): + self.ib.connect('127.0.0.1', 7497, clientId=101) + + self.symbol_map = {str(contract): ident for (ident, contract) in to_trade} + contracts = [contract for (_, contract) in to_trade] + symbols = list(self.symbol_map.values()) + self.symbols = symbols - self.request_market_data(eurusd) + self.df_hist = pd.DataFrame(columns=symbols) - # ib.pendingTickersEvent += self.on_ticker + self.request_historical_data(contracts) + self.request_market_data(contracts) while self.ib.waitOnUpdate(): self.ib.sleep(1) + self.calculate_strategy_params() + + def request_market_data(self, contracts): + for contract in contracts: + self.ib.reqMktData(contract) - def request_market_data(self, contract): - self.ib.reqMktData(contract) self.ib.pendingTickersEvent += self.on_tick def on_tick(self, tickers): for ticker in tickers: - print(ticker.time, ticker.close) - dt_obj = pd.to_datetime(ticker.time) - self.hist.loc[dt_obj] = ticker.close - # self.hist.index = self.hist.index.tz_convert('Asia/Singapore') - print(self.hist.tail()) + self.get_incoming_tick_data(ticker) + + self.perform_trade_logic() + + def perform_trade_logic(self): + """ + This part is the 'secret-sauce' where actual trades takes place. + My take is that great experience, good portfolio construction, + and together with robust backtesting will make your strategy viable. + GOOD PORTFOLIO CONTRUCTION CAN SAVE YOU FROM BAD RESEARCH, + BUT BAD PORTFOLIO CONSTRUCTION CANNOT SAVE YOU FROM GREAT RESEARCH + + This trade logic uses volatility ratio and beta as our indicators. + - volatility ratio > 1 :: uptrend, volatility ratio < 1 :: downtrend + - beta is calculated as: mean(price A) / mean(price B) + We use the assumption that prive levels will mean-revert. + Expected price A = beta x price B + + Consider other methods of identifying our trade logic: + - current trend + - current regime + - detect structural breaks + """ + self.calculate_signals() + self.calculate_positions() + self.check_and_enter_orders() + + def check_and_enter_orders(self): + # if is_position_closed and is_sell_signal: + # print + # "==================================" + # print + # "OPEN SHORT POSIITON: SELL A BUY B" + # print + # "==================================" + # self.__place_spread_order(-self.trade_qty) + # + # elif is_position_closed and is_buy_signal: + # print + # "==================================" + # print + # "OPEN LONG POSIITON: BUY A SELL B" + # print + # "==================================" + # self.__place_spread_order(self.trade_qty) + # + # elif is_short and is_buy_signal: + # print + # "==================================" + # print + # "CLOSE SHORT POSITION: BUY A SELL B" + # print + # "==================================" + # self.__place_spread_order(self.trade_qty) + # + # elif is_long and is_sell_signal: + # print + # "==================================" + # print + # "CLOSE LONG POSITION: SELL A BUY B" + # print + # "==================================" + # self.__place_spread_order(-self.trade_qty) + pass + + def calculate_positions(self): + # Use account position details + pass + + # symbol_a = self.symbols[0] + # position = self.stocks_data[symbol_a].position + # is_position_closed, is_short, is_long = \ + # (position == 0), (position < 0), (position > 0) + # + # upnl, rpnl = self.__calculate_pnls() + # + # # Display to terminal dynamically + # signal_text = \ + # "BUY" if is_buy_signal else "SELL" if is_sell_signal else "NONE" + # console_output = '\r[%s] signal=%s, position=%s UPnL=%s RPnL=%s\r' % \ + # (dt.datetime.now(), signal_text, position, upnl, rpnl) + # sys.stdout.write(console_output) + # sys.stdout.flush() + + def calculate_strategy_params(self): + """ + Here, we are calculating beta and volatility ratio + for our signal indicators. - r = self.hist.resample('MS') - print(r) + Consider calculating other statistics here: + - stddevs of errs + - correlations + - co-integration + """ + [symbol_a, symbol_b] = self.symbols + resampled = self.df_hist.resample('30s').ffill().dropna() + mean = resampled.mean() + self.beta = mean[symbol_a] / mean[symbol_b] + + stddevs = resampled.pct_change().dropna().std() + self.volatility_ratio = stddevs[symbol_a] / stddevs[symbol_b] + + print('beta:', self.beta, 'vr:', self.volatility_ratio) + + def calculate_signals(self): + self.trim_historical_data() + + is_up_trend, is_down_trend = self.volatility_ratio > 1, self.volatility_ratio < 1 + is_overbought, is_oversold = self.is_overbought_or_oversold() + + # Our final trade signals + self.is_buy_signal = is_up_trend and is_oversold + self.is_sell_signal = is_down_trend and is_overbought + + def trim_historical_data(self): + cutoff_time = dt.datetime.now(tz=self.local_timezone) - self.moving_window_period + self.df_hist = self.df_hist[self.df_hist.index >= cutoff_time] + + def is_overbought_or_oversold(self): + [symbol_a, symbol_b] = self.symbols + leg_a_last_price = self.df_hist[symbol_a].dropna().values[-1] + leg_b_last_price = self.df_hist[symbol_b].dropna().values[-1] + + expected_leg_a_price = leg_b_last_price * self.beta + + is_overbought = leg_a_last_price < expected_leg_a_price # Cheaper than expected + is_oversold = leg_a_last_price > expected_leg_a_price # Higher than expected + + return is_overbought, is_oversold + + def get_incoming_tick_data(self, ticker): + symbol = self.get_symbol(ticker.contract) + + dt_obj = self.convert_utc_datetime(ticker.time) + bid = ticker.bid + ask = ticker.ask + mid = (bid + ask) / 2 + + self.df_hist.loc[dt_obj, symbol] = mid + + def request_historical_data(self, contracts): + for contract in contracts: + self.set_historical_data(contract) + + def set_historical_data(self, contract): + symbol = self.get_symbol(contract) - def get_historical_data(self, contract): bars = self.ib.reqHistoricalData( contract, endDateTime=time.strftime('%Y%m%d %H:%M:%S'), @@ -63,19 +219,18 @@ def get_historical_data(self, contract): useRTH=True, formatDate=1 ) - # df_obj = df_close(bars) - df_obj = pd.DataFrame(columns=['datetime', 'last']) for bar in bars: - # dt_obj = dt.datetime.strptime(str(bar.date), '%Y-%m-%d %H:%M:%S') - dt_obj = pd.to_datetime(bar.date, format='%Y-%m-%d %H:%M:%s') - df_obj['datetime'] = dt_obj - df_obj['last'] = bar.close - # - # df_close = pd.DataFrame(columns=['close']) - # df_close.loc[df.index, 'close'] = df.close - df_obj = df_obj.set_index('datetime') - print(df_obj.head()) - return df_obj - - def on_ticker(self, ticker): - print('tiker:', ticker) + dt_obj = self.convert_local_datetime(bar.date) + self.df_hist.loc[dt_obj, symbol] = bar.close + + def get_symbol(self, contract): + return self.symbol_map.get(str(contract)) + + def convert_utc_datetime(self, datetime): + utc = datetime.replace(tzinfo=self.utc_timezone) + local_time = utc.astimezone(self.local_timezone) + return pd.to_datetime(local_time) + + def convert_local_datetime(self, datetime): + local_time = datetime.replace(tzinfo=self.local_timezone) + return pd.to_datetime(local_time)