def _compute_market_impact(self, date: datetime, tickers: Sequence[Ticker], fill_volumes: Sequence[int]) \ -> Sequence[float]: """ Market Impact is positive for buys and negative for sells. MI = +/- price_impact * volatility * sqrt (fill volume / average daily volume) """ start_date = date - RelativeDelta(days=60) end_date = date - RelativeDelta(days=1) # Download close price and volume values data_array = self._data_provider.get_price( tickers, [PriceField.Close, PriceField.Volume], start_date, end_date, Frequency.DAILY) close_prices = cast_data_array_to_proper_type( data_array.loc[:, tickers, PriceField.Close]) volumes = cast_data_array_to_proper_type( data_array.loc[:, tickers, PriceField.Volume]) close_prices_volatility = close_prices.apply( self._compute_volatility).values average_volumes = volumes.apply(self._compute_average_volume).values abs_fill_volumes = np.abs(fill_volumes) volatility_volume_ratio = np.divide(abs_fill_volumes, average_volumes) sqrt_volatility_volume_ratio = np.sqrt( volatility_volume_ratio) * np.sign(fill_volumes) return self.price_impact * close_prices_volatility * sqrt_volatility_volume_ratio
def _get_open_prices(self, prices_data_array: QFDataArray) -> PricesDataFrame: """ Returns PricesDataFrame consisting of only Open prices. """ open_prices_df = cast_data_array_to_proper_type( prices_data_array.loc[:, :, PriceField.Open], use_prices_types=True).dropna(how="all") return open_prices_df
def generate_trades_for_ticker(self, prices_array: QFDataArray, exposures_tms: pd.Series, ticker: Ticker) \ -> List[Trade]: open_prices_tms = cast_data_array_to_proper_type( prices_array.loc[:, ticker, PriceField.Open], use_prices_types=True) # historical data cropped to the time frame of the backtest (from start date till end date) historical_data = pd.concat((exposures_tms, open_prices_tms), axis=1).loc[self._start_date:] prev_exposure = 0.0 trades_list = [] trade_start_date = None trade_exposure = None trade_start_price = None for curr_date, row in historical_data.iterrows(): curr_exposure, curr_price = row.values # skipping the nan exposures (the first one is sure to be nan) if np.isnan(curr_exposure) or np.isnan(curr_price): continue out_of_the_market = trade_exposure is None if out_of_the_market: assert prev_exposure == 0.0 if curr_exposure != 0.0: trade_start_date = curr_date trade_exposure = curr_exposure trade_start_price = curr_price else: assert prev_exposure != 0.0 exposure_change = int(curr_exposure - prev_exposure) should_close_position = exposure_change != 0.0 if should_close_position: trades_list.append( Trade(start_time=trade_start_date, end_time=curr_date, contract=self._tickers_to_contracts[ticker], pnl=(curr_price / trade_start_price - 1) * trade_exposure, commission=0.0, direction=int(trade_exposure))) trade_start_date = None trade_exposure = None trade_start_price = None going_into_opposite_direction = curr_exposure != 0.0 if going_into_opposite_direction: trade_start_date = curr_date trade_exposure = curr_exposure trade_start_price = curr_price prev_exposure = curr_exposure return trades_list
def get_current_bar(self, tickers: Union[Ticker, Sequence[Ticker]], frequency: Frequency = None) \ -> Union[QFSeries, QFDataFrame]: """ Gets the current bar(s) for given Ticker(s). If the bar is not available yet, None is returned. If the request for single Ticker was made, then the result is a QFSeries indexed with PriceFields (OHLCV). If the request for multiple Tickers was made, then the result has Tickers as an index and PriceFields as columns. In case of N minutes frequency, the current bar can be returned in the time between (inclusive) N minutes after MarketOpenEvent and the MarketCloseEvent). E.g. for 1 minute frequency, at 13:00 (if the market opens before 13:00), the 12:59 - 13:00 bar will be returned. In case of 15 minutes frequency, when the market opened less then 15 minutes ago, Nones will be returned. If current time ("now") contains non-zero seconds or microseconds, Nones will be returned. Parameters ----------- tickers: Ticker, Sequence[Ticker] tickers of the securities which prices should be downloaded frequency: Frequency frequency of the data Returns ------- QFSeries, QFDataFrame current bar """ if not tickers: return QFSeries() frequency = frequency or self.fixed_data_provider_frequency or Frequency.MIN_1 tickers, was_single_ticker_provided = convert_to_list(tickers, Ticker) current_datetime = self.timer.now() start_date = current_datetime - frequency.time_delta() prices_data_array = self.get_price(tickers=tickers, fields=PriceField.ohlcv(), start_date=start_date, end_date=current_datetime, frequency=frequency) try: last_available_bars = cast_data_array_to_proper_type( prices_data_array.loc[start_date]) except KeyError: return QFDataFrame(index=tickers, columns=PriceField.ohlcv()) if was_single_ticker_provided: last_available_bars = last_available_bars.iloc[0, :] return last_available_bars
def _get_current_bars(self, tickers: Sequence[Ticker]) -> QFDataFrame: """ Gets the current bars for given Tickers. If the bars are not available yet, NaNs are returned. The result is a QFDataFrame with Tickers as an index and PriceFields as columns. In case of daily trading, the current bar is returned only at the Market Close Event time, as the get_price function will not return data for the current date until the market closes. In case of intraday trading (for N minutes frequency) the current bar can be returned in the time between (inclusive) N minutes after MarketOpenEvent and the MarketCloseEvent. Important: If current time ("now") contains non-zero seconds or microseconds, NaNs will be returned. """ if not tickers: return QFDataFrame() assert self._frequency >= Frequency.DAILY, "Lower than daily frequency is not supported by the simulated " \ "executor" current_datetime = self._timer.now() market_close_time = current_datetime + MarketCloseEvent.trigger_time( ) == current_datetime if self._frequency == Frequency.DAILY: # In case of daily trading, the current bar can be returned only at the Market Close if not market_close_time: return QFDataFrame(index=tickers, columns=PriceField.ohlcv()) else: current_datetime = date_to_datetime(current_datetime.date()) start_date = current_datetime - self._frequency.time_delta() current_bar_start = current_datetime else: # In case of intraday trading the current full bar is always indexed by the left side of the time range start_date = current_datetime - self._frequency.time_delta() current_bar_start = start_date prices_data_array = self._data_handler.get_price( tickers=tickers, fields=PriceField.ohlcv(), start_date=start_date, end_date=current_datetime, frequency=self._frequency) try: current_bars = cast_data_array_to_proper_type( prices_data_array.loc[current_bar_start]) except KeyError: current_bars = QFDataFrame(index=tickers, columns=PriceField.ohlcv()) return current_bars
def _initialize_futures_chain(self, fields: Union[PriceField, Sequence[PriceField]], start_date: datetime, end_date: datetime, frequency: Frequency): """ Provides data related to the future chain, within a given time range, and updates the Futures Chain with the newly acquired data. It gets the values of price fields and expiration dates for each specific future contract. It is the first function, that needs to be called, before generating the chain, because it is responsible for downloading all the necessary data, used later on by other functions. Parameters ---------- fields price fields, which should be retrieved start_date end_date frequency represent the time range and frequency of the downloaded prices data for the future contracts """ # Check if single field was provided _, got_single_field = convert_to_list(fields, PriceField) # Get the expiration dates related to the future contracts belonging to the futures chain future_tickers_exp_dates_series = self._future_ticker.get_expiration_dates() # Consider only these future contracts, which may be used to build the futures chain - do not include contracts, # which expired before the start_date future_tickers_exp_dates_series = future_tickers_exp_dates_series[ future_tickers_exp_dates_series.index >= start_date ] # Exclude contracts which will not be used while building the current futures chain. All of the newer contracts, # which will be used for later futures chains building will be downloaded later anyway, as # _initialize_futures_chain() is called after each expiration of a contract. current_contract_index = pd.Index(future_tickers_exp_dates_series).get_loc( self._future_ticker.get_current_specific_ticker() ) last_ticker_position = min(future_tickers_exp_dates_series.size, current_contract_index + 1) future_tickers_exp_dates_series = future_tickers_exp_dates_series.iloc[0:last_ticker_position] # Download the historical prices future_tickers_list = list(future_tickers_exp_dates_series.values) futures_data = self._data_provider.get_price(future_tickers_list, fields, start_date, end_date, frequency) # Store the start_date used for the purpose of FuturesChain initialization self._first_cached_date = start_date for exp_date, future_ticker in future_tickers_exp_dates_series.items(): # Create a data frame and cast it into PricesDataFrame or PricesSeries if got_single_field: data = futures_data.loc[:, future_ticker] else: data = futures_data.loc[:, future_ticker, :] data = cast_data_array_to_proper_type(data, use_prices_types=True) # Check if data is empty (some contract may have no price within the given time range) - if so do not # add it to the FuturesChain if not data.empty: # Create the future object and add it to the Futures Chain data structure future = FutureContract(ticker=future_ticker, exp_date=exp_date, data=data ) self.loc[exp_date] = future self.sort_index(inplace=True)
def get_current_bar(self, tickers: Union[Ticker, Sequence[Ticker]], frequency: Frequency = None) \ -> Union[QFSeries, QFDataFrame]: """ Gets the current bar(s) for given Ticker(s). If the bar is not available yet (e.g. before the market close), None is returned. If the request for single Ticker was made, then the result is a QFSeries indexed with PriceFields (OHLCV). If the request for multiple Tickers was made, then the result has Tickers as an index and PriceFields as columns. Normally on working days the method will return non-empty bars in the time between (inclusive) MarketCloseEvent and midnight (exclusive). On non-working days or on working days in the time between midnight (inclusive) and MarketClose (exclusive), e.g. 12:00, the returned bars will contain Nones as values. Parameters ----------- tickers: Ticker, Sequence[Ticker] tickers of the securities which prices should be downloaded frequency: Frequency frequency of the data Returns ------- QFSeries, QFDataFrame current bar """ if not tickers: return QFSeries() frequency = frequency or self.fixed_data_provider_frequency or Frequency.DAILY tickers, was_single_ticker_provided = convert_to_list(tickers, Ticker) current_datetime = self.timer.now() if self.time_helper.datetime_of_latest_market_event( MarketCloseEvent) < current_datetime: last_available_bars = QFDataFrame(index=tickers, columns=PriceField.ohlcv()) else: current_date = self._zero_out_time_component(current_datetime) start_date = current_date - RelativeDelta(days=7) prices_data_array = self.get_price( tickers=tickers, fields=PriceField.ohlcv(), start_date=start_date, end_date=current_date, frequency=frequency) # type: QFDataArray try: last_available_bars = cast_data_array_to_proper_type( prices_data_array.loc[current_date]) except KeyError: return QFDataFrame(index=tickers, columns=PriceField.ohlcv()) if was_single_ticker_provided: last_available_bars = last_available_bars.iloc[0, :] return last_available_bars