def test_expiring_cache(self): expiry_1 = Timestamp('2014') before_1 = expiry_1 - Timedelta('1 minute') after_1 = expiry_1 + Timedelta('1 minute') expiry_2 = Timestamp('2015') after_2 = expiry_1 + Timedelta('1 minute') expiry_3 = Timestamp('2016') cache = ExpiringCache() cache.set('foo', 1, expiry_1) cache.set('bar', 2, expiry_2) self.assertEqual(cache.get('foo', before_1), 1) # Unwrap on expiry is allowed. self.assertEqual(cache.get('foo', expiry_1), 1) with self.assertRaises(KeyError) as e: self.assertEqual(cache.get('foo', after_1)) self.assertEqual(e.exception.args, ('foo', )) # Should raise same KeyError after deletion. with self.assertRaises(KeyError) as e: self.assertEqual(cache.get('foo', before_1)) self.assertEqual(e.exception.args, ('foo', )) # Second value should still exist. self.assertEqual(cache.get('bar', after_2), 2) # Should raise similar KeyError on non-existent key. with self.assertRaises(KeyError) as e: self.assertEqual(cache.get('baz', expiry_3)) self.assertEqual(e.exception.args, ('baz', ))
def test_expiring_cache(self): expiry_1 = Timestamp('2014') before_1 = expiry_1 - Timedelta('1 minute') after_1 = expiry_1 + Timedelta('1 minute') expiry_2 = Timestamp('2015') after_2 = expiry_1 + Timedelta('1 minute') expiry_3 = Timestamp('2016') cache = ExpiringCache() cache.set('foo', 1, expiry_1) cache.set('bar', 2, expiry_2) self.assertEqual(cache.get('foo', before_1), 1) # Unwrap on expiry is allowed. self.assertEqual(cache.get('foo', expiry_1), 1) with self.assertRaises(KeyError) as e: self.assertEqual(cache.get('foo', after_1)) self.assertEqual(e.exception.args, ('foo',)) # Should raise same KeyError after deletion. with self.assertRaises(KeyError) as e: self.assertEqual(cache.get('foo', before_1)) self.assertEqual(e.exception.args, ('foo',)) # Second value should still exist. self.assertEqual(cache.get('bar', after_2), 2) # Should raise similar KeyError on non-existent key. with self.assertRaises(KeyError) as e: self.assertEqual(cache.get('baz', expiry_3)) self.assertEqual(e.exception.args, ('baz',))
def __init__(self, trading_calendar, reader, equity_adjustment_reader, asset_finder, roll_finders=None, sid_cache_size=1000, prefetch_length=0): self.trading_calendar = trading_calendar self._asset_finder = asset_finder self._reader = reader self._adjustment_readers = {} if equity_adjustment_reader is not None: self._adjustment_readers[Equity] = \ HistoryCompatibleUSEquityAdjustmentReader( equity_adjustment_reader) if roll_finders: self._adjustment_readers[ContinuousFuture] =\ ContinuousFutureAdjustmentReader(trading_calendar, asset_finder, reader, roll_finders, self._frequency) self._window_blocks = { field: ExpiringCache(LRU(sid_cache_size)) for field in self.FIELDS } self._prefetch_length = prefetch_length
def __init__(self): super(MarketImpactBase, self).__init__() self._window_data_cache = ExpiringCache()
class MarketImpactBase(SlippageModel): """ Base class for slippage models which compute a simulated price impact according to a history lookback. """ NO_DATA_VOLATILITY_SLIPPAGE_IMPACT = 10.0 / 10000 def __init__(self): super(MarketImpactBase, self).__init__() self._window_data_cache = ExpiringCache() @abstractmethod def get_txn_volume(self, data, order): """ Return the number of shares we would like to order in this minute. Parameters ---------- data : BarData order : Order Return ------ int : the number of shares """ raise NotImplementedError('get_txn_volume') @abstractmethod def get_simulated_impact(self, order, current_price, current_volume, txn_volume, mean_volume, volatility): """ Calculate simulated price impact. Parameters ---------- order : The order being processed. current_price : Current price of the asset being ordered. current_volume : Volume of the asset being ordered for the current bar. txn_volume : Number of shares/contracts being ordered. mean_volume : Trailing ADV of the asset. volatility : Annualized daily volatility of volume. Return ------ int : impact on the current price. """ raise NotImplementedError('get_simulated_impact') def process_order(self, data, order): if order.open_amount == 0: return None, None minute_data = data.current(order.asset, ['volume', 'high', 'low']) mean_volume, volatility = self._get_window_data(data, order.asset, 20) # Price to use is the average of the minute bar's open and close. price = np.mean([minute_data['high'], minute_data['low']]) volume = minute_data['volume'] if not volume: return None, None txn_volume = int( min(self.get_txn_volume(data, order), abs(order.open_amount))) # If the computed transaction volume is zero or a decimal value, 'int' # will round it down to zero. In that case just bail. if txn_volume == 0: return None, None if mean_volume == 0 or np.isnan(volatility): # If this is the first day the contract exists or there is no # volume history, default to a conservative estimate of impact. simulated_impact = price * self.NO_DATA_VOLATILITY_SLIPPAGE_IMPACT else: simulated_impact = self.get_simulated_impact( order=order, current_price=price, current_volume=volume, txn_volume=txn_volume, mean_volume=mean_volume, volatility=volatility, ) impacted_price = \ price + math.copysign(simulated_impact, order.direction) if fill_price_worse_than_limit_price(impacted_price, order): return None, None return impacted_price, math.copysign(txn_volume, order.direction) def _get_window_data(self, data, asset, window_length): """ Internal utility method to return the trailing mean volume over the past 'window_length' days, and volatility of close prices for a specific asset. Parameters ---------- data : The BarData from which to fetch the daily windows. asset : The Asset whose data we are fetching. window_length : Number of days of history used to calculate the mean volume and close price volatility. Returns ------- (mean volume, volatility) """ try: values = self._window_data_cache.get(asset, data.current_session) except KeyError: try: # Add a day because we want 'window_length' complete days, # excluding the current day. volume_history = data.history( asset, 'volume', window_length + 1, '1d', ) close_history = data.history( asset, 'close', window_length + 1, '1d', ) except HistoryWindowStartsBeforeData: # If there is not enough data to do a full history call, return # values as if there was no data. return 0, np.NaN # Exclude the first value of the percent change array because it is # always just NaN. close_volatility = close_history[:-1].pct_change()[1:].std( skipna=False, ) values = { 'volume': volume_history[:-1].mean(), 'close': close_volatility * SQRT_252, } self._window_data_cache.set(asset, values, data.current_session) return values['volume'], values['close']
class MarketImpactBase(SlippageModel): """ Base class for slippage models which compute a simulated price impact according to a history lookback. """ NO_DATA_VOLATILITY_SLIPPAGE_IMPACT = 10.0 / 10000 def __init__(self): super(MarketImpactBase, self).__init__() self._window_data_cache = ExpiringCache() @abstractmethod def get_txn_volume(self, data, order): """ Return the number of shares we would like to order in this minute. Parameters ---------- data : BarData order : Order Return ------ int : the number of shares """ raise NotImplementedError('get_txn_volume') @abstractmethod def get_simulated_impact(self, order, current_price, current_volume, txn_volume, mean_volume, volatility): """ Calculate simulated price impact. Parameters ---------- order : The order being processed. current_price : Current price of the asset being ordered. current_volume : Volume of the asset being ordered for the current bar. txn_volume : Number of shares/contracts being ordered. mean_volume : Trailing ADV of the asset. volatility : Annualized daily volatility of volume. Return ------ int : impact on the current price. """ raise NotImplementedError('get_simulated_impact') def process_order(self, data, order): if order.open_amount == 0: return None, None minute_data = data.current(order.asset, ['volume', 'high', 'low']) mean_volume, volatility = self._get_window_data(data, order.asset, 20) # Price to use is the average of the minute bar's open and close. price = np.mean([minute_data['high'], minute_data['low']]) volume = minute_data['volume'] if not volume: return None, None txn_volume = int( min(self.get_txn_volume(data, order), abs(order.open_amount)) ) # If the computed transaction volume is zero or a decimal value, 'int' # will round it down to zero. In that case just bail. if txn_volume == 0: return None, None if mean_volume == 0 or np.isnan(volatility): # If this is the first day the contract exists or there is no # volume history, default to a conservative estimate of impact. simulated_impact = price * self.NO_DATA_VOLATILITY_SLIPPAGE_IMPACT else: simulated_impact = self.get_simulated_impact( order=order, current_price=price, current_volume=volume, txn_volume=txn_volume, mean_volume=mean_volume, volatility=volatility, ) impacted_price = \ price + math.copysign(simulated_impact, order.direction) if fill_price_worse_than_limit_price(impacted_price, order): return None, None return impacted_price, math.copysign(txn_volume, order.direction) def _get_window_data(self, data, asset, window_length): """ Internal utility method to return the trailing mean volume over the past 'window_length' days, and volatility of close prices for a specific asset. Parameters ---------- data : The BarData from which to fetch the daily windows. asset : The Asset whose data we are fetching. window_length : Number of days of history used to calculate the mean volume and close price volatility. Returns ------- (mean volume, volatility) """ try: values = self._window_data_cache.get(asset, data.current_session) except KeyError: try: # Add a day because we want 'window_length' complete days, # excluding the current day. volume_history = data.history( asset, 'volume', window_length + 1, '1d', ) close_history = data.history( asset, 'close', window_length + 1, '1d', ) except HistoryWindowStartsBeforeData: # If there is not enough data to do a full history call, return # values as if there was no data. return 0, np.NaN # Exclude the first value of the percent change array because it is # always just NaN. close_volatility = close_history[:-1].pct_change()[1:].std( skipna=False, ) values = { 'volume': volume_history[:-1].mean(), 'close': close_volatility * SQRT_252, } self._window_data_cache.set(asset, values, data.current_session) return values['volume'], values['close']