def __init__(self, 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.moving_window_period = moving_window_period self.chart = Chart() self.ib_util = IBUtil() # Store parameters for this model self.strategy_params = StrategyParameters(evaluation_time_secs, resample_interval_secs) self.stocks_data = {} # Dictionary storing StockData objects. self.symbols = None # List of current symbols self.account_code = "" self.prices = None # Store last prices in a DataFrame self.trade_qty = 0 self.order_id = 0 self.lock = threading.Lock() # Use ibConnection() for TWS, or create connection for API Gateway self.conn = ibConnection() if is_use_gateway else \ Connection.create(host=host, port=port, clientId=client_id) self.__register_data_handlers(self.__on_tick_event, self.__event_handler)
def __init__(self, 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.moving_window_period = moving_window_period self.chart = Chart() self.ib_util = IBUtil() self.strategy_params = StrategyParameters(evaluation_time_secs, resample_interval_secs) self.stocks_data = {} self.symbols = None self.account_code = "" self.prices = None self.trade_qty = 0 self.order_id = 0 self.lock = threading.Lock() self.conn = ibConnection() if is_use_gateway else \ Connection.create(host=host, port=port, clientId=client_id) self.__register_data_handlers(self.__on_tick_event, self.__event_handler)
class HFTModel: def __init__(self, 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.moving_window_period = moving_window_period self.chart = Chart() self.ib_util = IBUtil() # Store parameters for this model self.strategy_params = StrategyParameters(evaluation_time_secs, resample_interval_secs) self.stocks_data = {} # Dictionary storing StockData objects. self.symbols = None # List of current symbols self.account_code = "" self.prices = None # Store last prices in a DataFrame self.trade_qty = 0 self.order_id = 0 self.lock = threading.Lock() # Use ibConnection() for TWS, or create connection for API Gateway self.conn = ibConnection() if is_use_gateway else \ Connection.create(host=host, port=port, clientId=client_id) self.__register_data_handlers(self.__on_tick_event, self.__event_handler) 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 """ volatility_ratio = self.strategy_params.get_volatility_ratio() is_up_trend, is_down_trend = volatility_ratio > 1, volatility_ratio < 1 is_overbought, is_oversold = self.__is_overbought_or_oversold() # Our final trade signals is_buy_signal, is_sell_signal = (is_up_trend and is_oversold), \ (is_down_trend and is_overbought) # Use account position details 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() 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) def __recalculate_strategy_parameters_at_interval(self): """ Consider re-evaluation of parameters on: - regime shifts - structural breaks """ if self.strategy_params.is_evaluation_time_elapsed(): self.__calculate_strategy_params() self.strategy_params.set_new_evaluation_time() print '[%s] === Beta re-evaluated ===' % dt.datetime.now() def __calculate_strategy_params(self): """ Here, we are calculating beta and volatility ratio for our signal indicators. Consider calculating other statistics here: - stddevs of errs - correlations - co-integration """ [symbol_a, symbol_b] = self.symbols filled_prices = self.prices.fillna(method='ffill') resampled = filled_prices.resample( self.strategy_params.resample_interval_secs, fill_method='ffill')\ .dropna() mean = resampled.mean() beta = mean[symbol_a] / mean[symbol_b] stddevs = resampled.pct_change().dropna().std() volatility_ratio = stddevs[symbol_a] / stddevs[symbol_b] self.strategy_params.add_indicators(beta, volatility_ratio) def __register_data_handlers(self, tick_event_handler, universal_event_handler): self.conn.registerAll(universal_event_handler) self.conn.unregister(universal_event_handler, ib_message_type.tickSize, ib_message_type.tickPrice, ib_message_type.tickString, ib_message_type.tickGeneric, ib_message_type.tickOptionComputation) self.conn.register(tick_event_handler, ib_message_type.tickPrice, ib_message_type.tickSize) def __init_stocks_data(self, symbols): self.symbols = symbols self.prices = pd.DataFrame(columns=symbols) # Init price storage for stock_symbol in symbols: contract = self.ib_util.create_stock_contract(stock_symbol) self.stocks_data[stock_symbol] = StockData(contract) def __request_streaming_data(self, ib_conn): # Stream market data for index, (key, stock_data) in enumerate( self.stocks_data.iteritems()): ib_conn.reqMktData(index, stock_data.contract, datatype.GENERIC_TICKS_NONE, datatype.SNAPSHOT_NONE) time.sleep(1) # Stream account updates ib_conn.reqAccountUpdates(True, self.account_code) def __request_historical_data(self, ib_conn): self.lock.acquire() try: for index, (key, stock_data) in enumerate( self.stocks_data.iteritems()): stock_data.is_storing_data = True ib_conn.reqHistoricalData( index, stock_data.contract, time.strftime(datatype.DATE_TIME_FORMAT), datatype.DURATION_1_HR, datatype.BAR_SIZE_5_SEC, datatype.WHAT_TO_SHOW_TRADES, datatype.RTH_ALL, datatype.DATEFORMAT_STRING) time.sleep(1) finally: self.lock.release() def __wait_for_download_completion(self): is_waiting = True while is_waiting: is_waiting = False self.lock.acquire() try: for symbol in self.stocks_data.keys(): if self.stocks_data[symbol].is_storing_data: is_waiting = True finally: self.lock.release() if is_waiting: time.sleep(1) def __place_spread_order(self, qty): [symbol_a, symbol_b] = self.symbols self.__send_order(symbol_a, qty) self.__send_order(symbol_b, -qty) def __send_order(self, symbol, qty): stock_data = self.stocks_data[symbol] order = self.ib_util.create_stock_order(abs(qty), qty > 0) self.conn.placeOrder(self.__generate_order_id(), stock_data.contract, order) stock_data.add_to_position(qty) def __generate_order_id(self): next_order_id = self.order_id self.order_id += 1 return next_order_id def __is_overbought_or_oversold(self): [symbol_a, symbol_b] = self.symbols leg_a_last_price = self.prices[symbol_a].values[-1] leg_b_last_price = self.prices[symbol_b].values[-1] expected_leg_a_price = \ leg_b_last_price * self.strategy_params.get_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 __on_portfolio_update(self, msg): for key, stock_data in self.stocks_data.iteritems(): if stock_data.contract.m_symbol == msg.contract.m_symbol: stock_data.update_position(msg.position, msg.marketPrice, msg.marketValue, msg.averageCost, msg.unrealizedPNL, msg.realizedPNL, msg.accountName) return def __calculate_pnls(self): upnl, rpnl = 0, 0 for key, stock_data in self.stocks_data.iteritems(): upnl += stock_data.unrealized_pnl rpnl += stock_data.realized_pnl return upnl, rpnl def __event_handler(self, msg): if msg.typeName == datatype.MSG_TYPE_HISTORICAL_DATA: self.__on_historical_data(msg) elif msg.typeName == datatype.MSG_TYPE_UPDATE_PORTFOLIO: self.__on_portfolio_update(msg) elif msg.typeName == datatype.MSG_TYPE_MANAGED_ACCOUNTS: self.account_code = msg.accountsList elif msg.typeName == datatype.MSG_TYPE_NEXT_ORDER_ID: self.order_id = msg.orderId else: print msg def __on_historical_data(self, msg): print msg ticker_index = msg.reqId if msg.WAP == -1: self.__on_historical_data_completed(ticker_index) else: self.__add_historical_data(ticker_index, msg) def __on_historical_data_completed(self, ticker_index): self.lock.acquire() try: symbol = self.symbols[ticker_index] self.stocks_data[symbol].is_storing_data = False finally: self.lock.release() def __add_historical_data(self, ticker_index, msg): timestamp = dt.datetime.strptime(msg.date, datatype.DATE_TIME_FORMAT) self.__add_market_data(ticker_index, timestamp, msg.close) def __on_tick_event(self, msg): ticker_id = msg.tickerId field_type = msg.field # Store information from last traded price if field_type == datatype.FIELD_LAST_PRICE: last_price = msg.price self.__add_market_data(ticker_id, dt.datetime.now(), last_price) self.__trim_data_series() # Post-bootstrap - make trading decisions if self.strategy_params.is_bootstrap_completed(): self.__recalculate_strategy_parameters_at_interval() self.__perform_trade_logic() self.__update_charts() def __add_market_data(self, ticker_index, timestamp, price): symbol = self.symbols[ticker_index] self.prices.loc[timestamp, symbol] = float(price) self.prices = self.prices.fillna(method='ffill') # Clear NaN values self.prices.sort_index(inplace=True) def __update_charts(self): if len(self.prices) > 0 and len(self.strategy_params.indicators) > 0: self.chart.display_chart(self.prices, self.strategy_params.indicators) def __trim_data_series(self): cutoff_timestamp = dt.datetime.now() - self.moving_window_period self.prices = self.prices[self.prices.index >= cutoff_timestamp] self.strategy_params.trim_indicators_series(cutoff_timestamp) @staticmethod def __print_elapsed_time(start_time): elapsed_time = time.time() - start_time print "Completed in %.3f seconds." % elapsed_time def __cancel_market_data_request(self): for i, symbol in enumerate(self.symbols): self.conn.cancelMktData(i) time.sleep(1) def start(self, symbols, trade_qty): print "HFT model started." self.trade_qty = trade_qty self.conn.connect() # Get IB connection object self.__init_stocks_data(symbols) self.__request_streaming_data(self.conn) print "Bootstrapping the model..." start_time = time.time() self.__request_historical_data(self.conn) self.__wait_for_download_completion() self.strategy_params.set_bootstrap_completed() self.__print_elapsed_time(start_time) print "Calculating strategy parameters..." start_time = time.time() self.__calculate_strategy_params() self.__print_elapsed_time(start_time) print "Trading started." try: self.__update_charts() while True: time.sleep(1) except Exception, e: print "Exception:", e print "Cancelling...", self.__cancel_market_data_request() print "Disconnecting..." self.conn.disconnect() time.sleep(1) print "Disconnected."
class HFTModel: def __init__(self, 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.moving_window_period = moving_window_period self.chart = Chart() self.ib_util = IBUtil() # Store parameters for this model self.strategy_params = StrategyParameters(evaluation_time_secs, resample_interval_secs) self.stocks_data = {} # Dictionary storing StockData objects. self.symbols = None # List of current symbols self.account_code = "" self.prices = None # Store last prices in a DataFrame self.trade_qty = 0 self.order_id = 0 self.lock = threading.Lock() # Use ibConnection() for TWS, or create connection for API Gateway self.conn = ibConnection() if is_use_gateway else \ Connection.create(host=host, port=port, clientId=client_id) self.__register_data_handlers(self.__on_tick_event, self.__event_handler) 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 """ volatility_ratio = self.strategy_params.get_volatility_ratio() is_up_trend, is_down_trend = volatility_ratio > 1, volatility_ratio < 1 is_overbought, is_oversold = self.__is_overbought_or_oversold() # Our final trade signals is_buy_signal, is_sell_signal = (is_up_trend and is_oversold), \ (is_down_trend and is_overbought) # Use account position details 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() 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) def __recalculate_strategy_parameters_at_interval(self): """ Consider re-evaluation of parameters on: - regime shifts - structural breaks """ if self.strategy_params.is_evaluation_time_elapsed(): self.__calculate_strategy_params() self.strategy_params.set_new_evaluation_time() print '[%s] === Beta re-evaluated ===' % dt.datetime.now() def __calculate_strategy_params(self): """ Here, we are calculating beta and volatility ratio for our signal indicators. Consider calculating other statistics here: - stddevs of errs - correlations - co-integration """ [symbol_a, symbol_b] = self.symbols filled_prices = self.prices.fillna(method='ffill') resampled = filled_prices.resample( self.strategy_params.resample_interval_secs, fill_method='ffill')\ .dropna() mean = resampled.mean() beta = mean[symbol_a] / mean[symbol_b] stddevs = resampled.pct_change().dropna().std() volatility_ratio = stddevs[symbol_a] / stddevs[symbol_b] self.strategy_params.add_indicators(beta, volatility_ratio) def __register_data_handlers(self, tick_event_handler, universal_event_handler): self.conn.registerAll(universal_event_handler) self.conn.unregister(universal_event_handler, ib_message_type.tickSize, ib_message_type.tickPrice, ib_message_type.tickString, ib_message_type.tickGeneric, ib_message_type.tickOptionComputation) self.conn.register(tick_event_handler, ib_message_type.tickPrice, ib_message_type.tickSize) def __init_stocks_data(self, symbols): self.symbols = symbols self.prices = pd.DataFrame(columns=symbols) # Init price storage for stock_symbol in symbols: contract = self.ib_util.create_stock_contract(stock_symbol) self.stocks_data[stock_symbol] = StockData(contract) def __request_streaming_data(self, ib_conn): # Stream market data for index, (key, stock_data) in enumerate( self.stocks_data.iteritems()): ib_conn.reqMktData(index, stock_data.contract, datatype.GENERIC_TICKS_NONE, datatype.SNAPSHOT_NONE) time.sleep(1) # Stream account updates ib_conn.reqAccountUpdates(True, self.account_code) def __request_historical_data(self, ib_conn): self.lock.acquire() try: for index, (key, stock_data) in enumerate( self.stocks_data.iteritems()): stock_data.is_storing_data = True ib_conn.reqHistoricalData( index, stock_data.contract, time.strftime(datatype.DATE_TIME_FORMAT), datatype.DURATION_1_HR, datatype.BAR_SIZE_5_SEC, datatype.WHAT_TO_SHOW_TRADES, datatype.RTH_ALL, datatype.DATEFORMAT_STRING) time.sleep(1) finally: self.lock.release() def __wait_for_download_completion(self): is_waiting = True while is_waiting: is_waiting = False self.lock.acquire() try: for symbol in self.stocks_data.keys(): if self.stocks_data[symbol].is_storing_data: is_waiting = True finally: self.lock.release() if is_waiting: time.sleep(1) def __place_spread_order(self, qty): [symbol_a, symbol_b, symbol_c] = self.symbols self.__send_order(symbol_a, qty) time.sleep(3) self.__send_order(symbol_b, qty) time.sleep(3) self.__send_order(symbol_c, -qty) time.sleep(3) def __send_order(self, symbol, qty): stock_data = self.stocks_data[symbol] order = self.ib_util.create_stock_order(abs(qty), qty > 0) self.conn.placeOrder(self.__generate_order_id(), stock_data.contract, order) stock_data.add_to_position(qty) def __generate_order_id(self): next_order_id = self.order_id self.order_id += 1 return next_order_id def __is_overbought_or_oversold(self): [symbol_a, symbol_b] = self.symbols leg_a_last_price = self.prices[symbol_a].values[-1] leg_b_last_price = self.prices[symbol_b].values[-1] expected_leg_a_price = \ leg_b_last_price * self.strategy_params.get_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 __on_portfolio_update(self, msg): for key, stock_data in self.stocks_data.iteritems(): if stock_data.contract.m_symbol == msg.contract.m_symbol: stock_data.update_position(msg.position, msg.marketPrice, msg.marketValue, msg.averageCost, msg.unrealizedPNL, msg.realizedPNL, msg.accountName) return def __calculate_pnls(self): upnl, rpnl = 0, 0 for key, stock_data in self.stocks_data.iteritems(): upnl += stock_data.unrealized_pnl rpnl += stock_data.realized_pnl return upnl, rpnl def __event_handler(self, msg): if msg.typeName == datatype.MSG_TYPE_HISTORICAL_DATA: self.__on_historical_data(msg) elif msg.typeName == datatype.MSG_TYPE_UPDATE_PORTFOLIO: self.__on_portfolio_update(msg) elif msg.typeName == datatype.MSG_TYPE_MANAGED_ACCOUNTS: self.account_code = msg.accountsList elif msg.typeName == datatype.MSG_TYPE_NEXT_ORDER_ID: self.order_id = msg.orderId else: print msg def __on_historical_data(self, msg): print msg ticker_index = msg.reqId if msg.WAP == -1: self.__on_historical_data_completed(ticker_index) else: self.__add_historical_data(ticker_index, msg) def __on_historical_data_completed(self, ticker_index): self.lock.acquire() try: symbol = self.symbols[ticker_index] self.stocks_data[symbol].is_storing_data = False finally: self.lock.release() def __add_historical_data(self, ticker_index, msg): timestamp = dt.datetime.strptime(msg.date, datatype.DATE_TIME_FORMAT) self.__add_market_data(ticker_index, timestamp, msg.close) def __on_tick_event(self, msg): ticker_id = msg.tickerId field_type = msg.field # Store information from last traded price if field_type == datatype.FIELD_LAST_PRICE: last_price = msg.price self.__add_market_data(ticker_id, dt.datetime.now(), last_price) self.__trim_data_series() # Post-bootstrap - make trading decisions if self.strategy_params.is_bootstrap_completed(): self.__recalculate_strategy_parameters_at_interval() self.__perform_trade_logic() self.__update_charts() def __add_market_data(self, ticker_index, timestamp, price): symbol = self.symbols[ticker_index] self.prices.loc[timestamp, symbol] = float(price) self.prices = self.prices.fillna(method='ffill') # Clear NaN values self.prices.sort_index(inplace=True) def __update_charts(self): if len(self.prices) > 0 and len(self.strategy_params.indicators) > 0: self.chart.display_chart(self.prices, self.strategy_params.indicators) def __trim_data_series(self): cutoff_timestamp = dt.datetime.now() - self.moving_window_period self.prices = self.prices[self.prices.index >= cutoff_timestamp] self.strategy_params.trim_indicators_series(cutoff_timestamp) @staticmethod def __print_elapsed_time(start_time): elapsed_time = time.time() - start_time print "Completed in %.3f seconds." % elapsed_time def __cancel_market_data_request(self): for i, symbol in enumerate(self.symbols): self.conn.cancelMktData(i) time.sleep(1) def start(self, symbols, trade_qty): print "HFT model started." self.trade_qty = trade_qty self.conn.connect() # Get IB connection object self.__init_stocks_data(symbols) self.__request_streaming_data(self.conn) print "Bootstrapping the model..." start_time = time.time() self.__request_historical_data(self.conn) self.__wait_for_download_completion() self.strategy_params.set_bootstrap_completed() self.__print_elapsed_time(start_time) print "Calculating strategy parameters..." start_time = time.time() self.__calculate_strategy_params() self.__print_elapsed_time(start_time) print "Trading started." try: self.__update_charts() while True: time.sleep(1) except Exception, e: print "Exception:", e print "Cancelling...", self.__cancel_market_data_request() print "Disconnecting..." self.conn.disconnect() time.sleep(1) print "Disconnected."
class ATS: def __init__(self, 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.moving_window_period = moving_window_period self.chart = Chart() self.ib_util = IBUtil() self.strategy_params = StrategyParameters(evaluation_time_secs, resample_interval_secs) self.stocks_data = {} self.symbols = None self.account_code = "" self.prices = None self.trade_qty = 0 self.order_id = 0 self.lock = threading.Lock() self.conn = ibConnection() if is_use_gateway else \ Connection.create(host=host, port=port, clientId=client_id) self.__register_data_handlers(self.__on_tick_event, self.__event_handler) def __perform_trade_logic(self): volatility_ratio = self.strategy_params.get_volatility_ratio() is_up_trend, is_down_trend = volatility_ratio > 1, volatility_ratio < 1 is_overbought, is_oversold = self.__is_overbought_or_oversold() is_buy_signal, is_sell_signal = (is_up_trend and is_oversold), \ (is_down_trend and is_overbought) 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() 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() 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) def __recalculate_strategy_parameters_at_interval(self): if self.strategy_params.is_evaluation_time_elapsed(): self.__calculate_strategy_params() self.strategy_params.set_new_evaluation_time() print '[%s] === Beta re-evaluated ===' % dt.datetime.now() def __calculate_strategy_params(self): [symbol_a, symbol_b] = self.symbols filled_prices = self.prices.fillna(method='ffill') resampled = filled_prices.resample( self.strategy_params.resample_interval_secs, fill_method='ffill')\ .dropna() mean = resampled.mean() beta = mean[symbol_a] / mean[symbol_b] stddevs = resampled.pct_change().dropna().std() volatility_ratio = stddevs[symbol_a] / stddevs[symbol_b] self.strategy_params.add_indicators(beta, volatility_ratio) def __register_data_handlers(self, tick_event_handler, universal_event_handler): self.conn.registerAll(universal_event_handler) self.conn.unregister(universal_event_handler, ib_message_type.tickSize, ib_message_type.tickPrice, ib_message_type.tickString, ib_message_type.tickGeneric, ib_message_type.tickOptionComputation) self.conn.register(tick_event_handler, ib_message_type.tickPrice, ib_message_type.tickSize) def __init_stocks_data(self, symbols): self.symbols = symbols self.prices = pd.DataFrame(columns=symbols) for stock_symbol in symbols: contract = self.ib_util.create_stock_contract(stock_symbol) self.stocks_data[stock_symbol] = StockData(contract) def __request_streaming_data(self, ib_conn): for index, (key, stock_data) in enumerate( self.stocks_data.iteritems()): ib_conn.reqMktData(index, stock_data.contract, datatype.GENERIC_TICKS_NONE, datatype.SNAPSHOT_NONE) time.sleep(1) ib_conn.reqAccountUpdates(True, self.account_code) def __request_historical_data(self, ib_conn): self.lock.acquire() try: for index, (key, stock_data) in enumerate( self.stocks_data.iteritems()): stock_data.is_storing_data = True ib_conn.reqHistoricalData( index, stock_data.contract, time.strftime(datatype.DATE_TIME_FORMAT), datatype.DURATION_1_HR, datatype.BAR_SIZE_5_SEC, datatype.WHAT_TO_SHOW_TRADES, datatype.RTH_ALL, datatype.DATEFORMAT_STRING) time.sleep(1) finally: self.lock.release() def __wait_for_download_completion(self): is_waiting = True while is_waiting: is_waiting = False self.lock.acquire() try: for symbol in self.stocks_data.keys(): if self.stocks_data[symbol].is_storing_data: is_waiting = True finally: self.lock.release() if is_waiting: time.sleep(1) def __place_spread_order(self, qty): [symbol_a, symbol_b] = self.symbols self.__send_order(symbol_a, qty) self.__send_order(symbol_b, -qty) def __send_order(self, symbol, qty): stock_data = self.stocks_data[symbol] order = self.ib_util.create_stock_order(abs(qty), qty > 0) self.conn.placeOrder(self.__generate_order_id(), stock_data.contract, order) stock_data.add_to_position(qty) def __generate_order_id(self): next_order_id = self.order_id self.order_id += 1 return next_order_id def __is_overbought_or_oversold(self): [symbol_a, symbol_b] = self.symbols leg_a_last_price = self.prices[symbol_a].values[-1] leg_b_last_price = self.prices[symbol_b].values[-1] expected_leg_a_price = \ leg_b_last_price * self.strategy_params.get_beta() is_overbought = \ leg_a_last_price < expected_leg_a_price is_oversold = \ leg_a_last_price > expected_leg_a_price return is_overbought, is_oversold def __on_portfolio_update(self, msg): for key, stock_data in self.stocks_data.iteritems(): if stock_data.contract.m_symbol == msg.contract.m_symbol: stock_data.update_position(msg.position, msg.marketPrice, msg.marketValue, msg.averageCost, msg.unrealizedPNL, msg.realizedPNL, msg.accountName) return def __calculate_pnls(self): upnl, rpnl = 0, 0 for key, stock_data in self.stocks_data.iteritems(): upnl += stock_data.unrealized_pnl rpnl += stock_data.realized_pnl return upnl, rpnl def __event_handler(self, msg): if msg.typeName == datatype.MSG_TYPE_HISTORICAL_DATA: self.__on_historical_data(msg) elif msg.typeName == datatype.MSG_TYPE_UPDATE_PORTFOLIO: self.__on_portfolio_update(msg) elif msg.typeName == datatype.MSG_TYPE_MANAGED_ACCOUNTS: self.account_code = msg.accountsList elif msg.typeName == datatype.MSG_TYPE_NEXT_ORDER_ID: self.order_id = msg.orderId else: print msg def __on_historical_data(self, msg): print msg ticker_index = msg.reqId if msg.WAP == -1: self.__on_historical_data_completed(ticker_index) else: self.__add_historical_data(ticker_index, msg) def __on_historical_data_completed(self, ticker_index): self.lock.acquire() try: symbol = self.symbols[ticker_index] self.stocks_data[symbol].is_storing_data = False finally: self.lock.release() def __add_historical_data(self, ticker_index, msg): timestamp = dt.datetime.strptime(msg.date, datatype.DATE_TIME_FORMAT) self.__add_market_data(ticker_index, timestamp, msg.close) def __on_tick_event(self, msg): ticker_id = msg.tickerId field_type = msg.field if field_type == datatype.FIELD_LAST_PRICE: last_price = msg.price self.__add_market_data(ticker_id, dt.datetime.now(), last_price) self.__trim_data_series() if self.strategy_params.is_bootstrap_completed(): self.__recalculate_strategy_parameters_at_interval() self.__perform_trade_logic() self.__update_charts() def __add_market_data(self, ticker_index, timestamp, price): symbol = self.symbols[ticker_index] self.prices.loc[timestamp, symbol] = float(price) self.prices = self.prices.fillna(method='ffill') self.prices.sort_index(inplace=True) def __update_charts(self): if len(self.prices) > 0 and len(self.strategy_params.indicators) > 0: self.chart.display_chart(self.prices, self.strategy_params.indicators) def __trim_data_series(self): cutoff_timestamp = dt.datetime.now() - self.moving_window_period self.prices = self.prices[self.prices.index >= cutoff_timestamp] self.strategy_params.trim_indicators_series(cutoff_timestamp) @staticmethod def __print_elapsed_time(start_time): elapsed_time = time.time() - start_time print "Completed in %.3f seconds." % elapsed_time def __cancel_market_data_request(self): for i, symbol in enumerate(self.symbols): self.conn.cancelMktData(i) time.sleep(1) def start(self, symbols, trade_qty): print "ATS model started." self.trade_qty = trade_qty self.conn.connect() self.__init_stocks_data(symbols) self.__request_streaming_data(self.conn) print "Bootstrapping the model..." start_time = time.time() self.__request_historical_data(self.conn) self.__wait_for_download_completion() self.strategy_params.set_bootstrap_completed() self.__print_elapsed_time(start_time) print "Calculating strategy parameters..." start_time = time.time() self.__calculate_strategy_params() self.__print_elapsed_time(start_time) print "Trading started." try: self.__update_charts() while True: time.sleep(1) except Exception, e: print "Exception:", e print "Cancelling...", self.__cancel_market_data_request() print "Disconnecting..." self.conn.disconnect() time.sleep(1) print "Disconnected."