Пример #1
0
    def _generate_market_order(self, contract: Contract, signal: Signal):
        assert is_finite_number(
            self._initial_risk), "Initial risk has to be a finite number"
        assert is_finite_number(signal.fraction_at_risk
                                ), "fraction_at_risk has to be a finite number"

        target_percentage = self._initial_risk / signal.fraction_at_risk
        self.logger.info("Target Percentage: {}".format(target_percentage))

        target_percentage = self._cap_max_target_percentage(target_percentage)

        target_percentage *= signal.suggested_exposure.value  # preserve the direction (-1, 0 , 1)
        self.logger.info("Target Percentage considering direction: {}".format(
            target_percentage))

        assert is_finite_number(
            target_percentage), "target_percentage has to be a finite number"

        market_order_list = self._order_factory.target_percent_orders(
            {contract: target_percentage}, MarketOrder(), TimeInForce.OPG)
        if len(market_order_list) == 0:
            return None

        assert len(
            market_order_list) == 1, "Only one order should be generated"
        return market_order_list[0]
Пример #2
0
    def _get_orders_with_fill_prices_without_slippage(self, market_orders_list,
                                                      tickers, market_open,
                                                      market_close):
        unique_tickers = list(set(tickers))
        current_prices_series = self._get_current_prices(unique_tickers)

        expired_orders = []  # type: List[int]
        to_be_executed_orders = []
        no_slippage_prices = []

        # Check at first if at this moment of time, expiry checks should be made or not (optimization reasons)
        if market_open or market_close:
            # In case of market open or market close, some of the orders may expire
            for order, ticker in zip(market_orders_list, tickers):
                security_price = current_prices_series[ticker]

                if is_finite_number(security_price):
                    to_be_executed_orders.append(order)
                    no_slippage_prices.append(security_price)
                elif self._order_expires(order, market_open, market_close):
                    expired_orders.append(order.id)
        else:
            for order, ticker in zip(market_orders_list, tickers):
                security_price = current_prices_series[ticker]

                if is_finite_number(security_price):
                    to_be_executed_orders.append(order)
                    no_slippage_prices.append(security_price)

        return no_slippage_prices, to_be_executed_orders, expired_orders
Пример #3
0
 def update_price(self, bid_price: float, ask_price: float):
     """
     Sets the current price of the security in a way that takes into account the bid-ask spread
     This is used for market valuation of the open position.
     This method should be called every time we have have a new price
     """
     self._check_if_open()
     if self._quantity > 0 and is_finite_number(
             bid_price):  # we are long -> use the lower (bid) price
         self._current_price = bid_price
     elif self._quantity < 0 and is_finite_number(
             ask_price):  # we are short -> use the higher (ask) price
         self._current_price = ask_price
Пример #4
0
    def _get_orders_with_fill_prices_without_slippage(self, market_orders_list,
                                                      tickers):
        """
        Unexecuted orders dict will always be empty.
        Executes only on the market open and drops the orders if unexecuted
        """
        unique_tickers = list(set(tickers))
        current_prices_series = self._data_handler.get_current_price(
            unique_tickers)

        unexecuted_orders_dict = {}  # type: Dict[int, Order]
        to_be_executed_orders = []
        no_slippage_prices = []

        for order, ticker in zip(market_orders_list, tickers):
            security_price = current_prices_series[ticker]

            if is_finite_number(security_price):
                to_be_executed_orders.append(order)
                no_slippage_prices.append(security_price)
            else:
                if order.time_in_force == TimeInForce.GTC:
                    # preserve only GTC orders. DAY orders will be dropped at this point
                    unexecuted_orders_dict[order.id] = order

        return no_slippage_prices, to_be_executed_orders, unexecuted_orders_dict
Пример #5
0
def volume_weighted_average_price(prices_tms: PricesSeries,
                                  volumes_tms: QFSeries,
                                  interval: Timedelta) -> PricesSeries:
    """
    Aggregates prices in the prices_tms by calculating the average weighted price for each period. The average weighted
    prices are weighted by the volumes traded in each period.

    Parameters
    ----------
    prices_tms: PricesSeries
        timeseries of prices which should be aggregated
    volumes_tms: QFSeries
        timeseries of volumes traded; must correspond to the prices_tms
    interval: Timedelta
        the length of each period from which prices should be aggregated

    Returns
    -------
    PricesSeries
        timeseries of aggregated prices; first datetimes are:
        first_price_datetime + interval, first_price_datetime + 2*interval, ..., first_price_datetime + i*interval,
        where first_price_datetime is the datetime of the first price in the original prices_tms
        The last datetime is always <= last datetime in the prices_tms
    """
    assert prices_tms.index.equals(volumes_tms.index)

    last_date = prices_tms.index[-1]
    beginning_of_window = prices_tms.index[0]
    end_of_window = beginning_of_window + interval

    weighted_avg_price_tms = PricesSeries(name=prices_tms.name)

    while end_of_window < last_date:
        prices_in_window = prices_tms.loc[
            beginning_of_window:end_of_window].drop([end_of_window]).values
        volumes_in_window = volumes_tms.loc[
            beginning_of_window:end_of_window].drop([end_of_window]).values

        # if there are no prices in the window then skip try with the next window
        if prices_in_window.size == 0:
            continue

        # if there are no volumes to use -> assume that volume in each step is the same
        if count_nonzero(volumes_in_window) == 0:
            # if all the volumes are set to 0 than assume that volume for each asset is the same
            weighted_avg_price = mean(prices_in_window)
        else:
            # calculate volume-weighted average price
            weighted_price_sum = prices_in_window.dot(volumes_in_window)
            volume_sum = sum(volumes_in_window)
            weighted_avg_price = weighted_price_sum / volume_sum

        # if the weighted average price is equal exactly 0, it means that there were missing data
        if is_finite_number(weighted_avg_price) and weighted_avg_price != 0:
            weighted_avg_price_tms[end_of_window] = weighted_avg_price

        beginning_of_window = end_of_window
        end_of_window = end_of_window + interval

    return weighted_avg_price_tms
Пример #6
0
    def execute_orders(self, market_open=False, market_close=False):
        """
        Converts Orders into Transactions. Preserves the dictionary of unexecuted Orders (order_id -> Order)
        """
        open_orders_list = self.get_open_orders()
        if not open_orders_list:
            return

        tickers = [order.ticker for order in open_orders_list]
        no_slippage_fill_prices_list, to_be_executed_orders, expired_orders_list = \
            self._get_orders_with_fill_prices_without_slippage(open_orders_list, tickers, market_open, market_close)

        if len(to_be_executed_orders) > 0:
            current_time = self._timer.now()
            fill_prices, fill_volumes = self._slippage_model.process_orders(
                current_time, to_be_executed_orders,
                no_slippage_fill_prices_list)

            for order, fill_price, fill_volume in zip(to_be_executed_orders,
                                                      fill_prices,
                                                      fill_volumes):
                if fill_volume != 0 and is_finite_number(fill_price):
                    self._execute_order(order, fill_price, fill_volume)
                    # Delete the executed orders from awaiting orders dictionary
                    del self._awaiting_orders[order.id]

            # If any orders have been executed - update the portfolio
            self._portfolio.update()

        # Delete all expired orders
        for expired_order_id in expired_orders_list:
            del self._awaiting_orders[expired_order_id]
Пример #7
0
    def _generate_stop_order(self, signal, ticker_to_market_order: Dict[Ticker, Order]) -> Optional[Order]:
        """
        As each of the stop orders relies on the precomputed stop_price, which considers a.o. last available price of
        the security, orders are being created separately for each of the signals.
        """
        # stop_quantity = existing position size + recent market orders quantity
        stop_quantity = self._get_existing_position_quantity(signal.ticker)

        try:
            market_order = ticker_to_market_order[signal.ticker]
            stop_quantity += market_order.quantity
        except KeyError:
            # Generated Market Order was equal to None
            pass

        if stop_quantity != 0:
            stop_price = self._calculate_stop_price(signal)
            if not is_finite_number(stop_price):
                self.logger.info("Stop price should be a finite number")
                return None

            stop_price = self._cap_stop_price(stop_price, signal)

            # put minus before the quantity as stop order has to go in the opposite direction
            stop_orders = self._order_factory.orders({signal.ticker: -stop_quantity}, StopOrder(stop_price),
                                                     TimeInForce.GTC)

            assert len(stop_orders) == 1, "Only one order should be generated"
            return stop_orders[0]
        else:
            # quantity is 0 - no need to place a stop order
            return None
Пример #8
0
    def _adjust_quantity(self, order: Order, stop_order: Optional[Order], volume_df: QFDataFrame) -> \
            Tuple[Order, Order]:
        """Returns order with adjusted quantity if applicable."""
        ticker = self._contract_ticker_mapper.contract_to_ticker(order.contract)

        def average_past_volume(ticker: Ticker) -> Optional[float]:
            volume_series = volume_df[ticker]
            volume_series = volume_series.dropna()
            volume_series = volume_series[volume_series >= 0]
            return volume_series.mean()

        past_volume = average_past_volume(ticker)

        if is_finite_number(past_volume):
            volume_limit: int = math.floor(past_volume * self._volume_percentage_limit)

            # Check if the order quantity exceeds the limit
            if abs(order.quantity) > volume_limit:
                final_quantity = volume_limit * np.sign(order.quantity)
                adjustment_difference = final_quantity - order.quantity

                self.logger.info("{} VolumeOrdersFilter: Quantity change {} "
                                 "\n\tfinal quantity: {}".format(self._data_handler.timer.now(), order,
                                                                 final_quantity))
                order.quantity = final_quantity

                if stop_order:
                    # Adjust the corresponding stop order
                    stop_order_final_quantity = stop_order.quantity - adjustment_difference
                    self.logger.info("{} VolumeOrdersFilter: Quantity change {} "
                                     "\n\tfinal quantity: {}".format(self._data_handler.timer.now(), stop_order,
                                                                     final_quantity))
                    stop_order.quantity = stop_order_final_quantity

        return order, stop_order
Пример #9
0
    def _compute_target_percentage(self, signal):
        assert is_finite_number(signal.fraction_at_risk
                                ), "fraction_at_risk has to be a finite number"
        target_percentage = self._initial_risk / signal.fraction_at_risk
        self.logger.info("Target Percentage for {}: {}".format(
            signal.ticker, target_percentage))

        target_percentage = self._cap_max_target_percentage(target_percentage)

        target_percentage *= signal.suggested_exposure.value  # preserve the direction (-1, 0 , 1)
        self.logger.info(
            "Target Percentage considering direction for {}: {}".format(
                signal.ticker, target_percentage))

        assert is_finite_number(
            target_percentage), "target_percentage has to be a finite number"
        return target_percentage
Пример #10
0
 def _atr_fraction_at_risk(self, ticker: Ticker, time_period):
     try:
         current_time = self.data_handler.timer.now()
         data_frame = self.get_data(ticker.name, current_time)
         atr_value = self.compute_atr(data_frame)
         return atr_value * self.risk_estimation_factor if is_finite_number(
             atr_value) else None
     except NotEnoughDataException:
         return None
Пример #11
0
 def _to_supported_type(self, value):
     if isinstance(value, (numpy.int64, numpy.int32)):
         return int(value)
     elif is_finite_number(value) or isinstance(value, datetime):
         return value
     elif isinstance(value, Ticker):
         return value.as_string()
     else:
         return str(value)
    def _compute_target_value(self,
                              signal: Signal,
                              frequency=Frequency.DAILY) -> float:
        """
        Caps the target value, so that according to historical volume data, the position will not exceed
        max_volume_percentage * mean volume within last 100 days.
        """
        ticker: Ticker = signal.ticker

        portfolio_value = self._broker.get_portfolio_value()
        target_percentage = self._compute_target_percentage(signal)
        target_value = portfolio_value * target_percentage

        end_date = self._data_handler.timer.now()
        start_date = end_date - RelativeDelta(days=100)

        if isinstance(ticker, FutureTicker):
            # Check if a futures chain instance already exists for this ticker and create it if not
            # The default adjustment method will be taken (FuturesAdjustmentMethod.NTH_NEAREST) as the volume should
            # not be adjusted
            if ticker not in self._cached_futures_chains_dict.keys():
                self._cached_futures_chains_dict[ticker] = FuturesChain(
                    ticker, self._data_handler)

            volume_series: PricesSeries = self._cached_futures_chains_dict[
                ticker].get_price(PriceField.Volume, start_date, end_date,
                                  frequency)
        else:
            volume_series: PricesSeries = self._data_handler.get_price(
                ticker, PriceField.Volume, start_date, end_date, frequency)

        mean_volume = volume_series.mean()

        specific_ticker = ticker.get_current_specific_ticker() if isinstance(
            ticker, FutureTicker) else ticker
        current_price = self._data_handler.get_last_available_price(
            specific_ticker, frequency)
        contract_size = ticker.point_value if isinstance(ticker,
                                                         FutureTicker) else 1
        divisor = current_price * contract_size
        quantity = target_value // divisor

        if abs(quantity) > mean_volume * self._max_volume_percentage:
            target_quantity = np.floor(mean_volume *
                                       self._max_volume_percentage)
            target_value = target_quantity * divisor * np.sign(quantity)
            self.logger.info(
                "InitialRiskWithVolumePositionSizer: {} - capping {}.\n"
                "Initial quantity: {}\n"
                "Reduced quantity: {}".format(self._data_handler.timer.now(),
                                              ticker.ticker, quantity,
                                              target_quantity))

        assert is_finite_number(
            target_value), "target_value has to be a finite number"
        return target_value
Пример #13
0
    def _calculate_stop_price(self, signal: Signal):
        current_price = signal.last_available_price
        assert is_finite_number(current_price), f"Signal generated for the {signal.symbol} does not contain " \
                                                f"last_available_price. In order to use the Position Sizer with " \
                                                f"stop_losses it is necessary for the signals to contain the last " \
                                                f"available price."

        price_multiplier = 1 - signal.fraction_at_risk * signal.suggested_exposure.value
        stop_price = price_multiplier * current_price
        stop_price = self._round_stop_price(stop_price)
        return stop_price
Пример #14
0
    def _compute_target_percentage(self, signal):
        if not is_finite_number(
                signal.fraction_at_risk) or signal.fraction_at_risk == 0.0:
            self.logger.warn(
                "Invalid Fraction at Risk = {} for {}. Setting target percentage = 0.0"
                .format(signal.fraction_at_risk, signal.ticker))
            return 0.0
        target_percentage = self._initial_risk / signal.fraction_at_risk
        self.logger.info("Target Percentage for {}: {}".format(
            signal.ticker, target_percentage))

        target_percentage = self._cap_max_target_percentage(target_percentage)

        target_percentage *= signal.suggested_exposure.value  # preserve the direction (-1, 0 , 1)
        self.logger.info(
            "Target Percentage considering direction for {}: {}".format(
                signal.ticker, target_percentage))

        assert is_finite_number(
            target_percentage), "target_percentage has to be a finite number"
        return target_percentage
Пример #15
0
    def _cap_stop_price(self, stop_price: float, signal: Signal):
        """
        Prevent the stop price from moving down in case of a long position or up in case of a short position.

        Adjust the stop price only in case if there is an existing open position for specifically this contract. E.g.
        in case if there exists an open position for the December Cotton future contract, which expired today, if
        we are creating an order for the January Cotton contract we should not adjust the StopOrders stop price.
        """
        # If there exist an open position for the contract - check the previous Stop Order
        specific_ticker = signal.ticker.get_current_specific_ticker() \
            if isinstance(signal.ticker, FutureTicker) else signal.ticker

        contract = self._contract_ticker_mapper.ticker_to_contract(
            specific_ticker)
        position_quantity = self._get_existing_position_quantity(contract)

        if position_quantity != 0:
            # Get the last signal that was generated for the contract
            signals_series = self._signals_register.get_signals_for_ticker(
                ticker=specific_ticker, alpha_model=signal.alpha_model)
            signals_series = signals_series[
                signals_series.index < self._data_handler.timer.now()]

            if not signals_series.empty:
                # Get the last signal, which was generated for the same exposure as the current exposure
                exposure_series = signals_series.apply(
                    lambda s: s.suggested_exposure)
                current_exposure = Exposure.LONG if position_quantity > 0 else Exposure.SHORT
                last_date_with_current_exposure = exposure_series.where(exposure_series == current_exposure)\
                    .last_valid_index()
                if last_date_with_current_exposure is None:
                    self.logger.error(
                        "There is an open position for the contract {} but no signal was found"
                    )
                    return stop_price

                last_signal_for_the_contract = signals_series.loc[
                    last_date_with_current_exposure]  # type: Signal
                previous_stop_price = self._calculate_stop_price(
                    last_signal_for_the_contract)

                if last_signal_for_the_contract.suggested_exposure == Exposure.OUT or \
                        not is_finite_number(previous_stop_price):
                    return stop_price

                if last_signal_for_the_contract.suggested_exposure == Exposure.LONG:
                    stop_price = max([stop_price, previous_stop_price])
                elif last_signal_for_the_contract.suggested_exposure == Exposure.SHORT:
                    stop_price = min([stop_price, previous_stop_price])

        return stop_price
Пример #16
0
    def _generate_exposure_values(self, config: FastAlphaModelTesterConfig,
                                  data_handler: FastDataHandler,
                                  tickers: Sequence[Ticker]):
        """
        For the given Alpha model and its parameters, generates the dataframe containing all exposure values, that
        will be returned by the model through signals.
        """

        model = config.generate_model(data_handler)

        current_exposures_values = QFSeries(
            index=pd.Index(tickers, name=TICKERS))
        current_exposures_values[:] = 0.0

        backtest_dates = pd.date_range(self._start_date,
                                       self._end_date,
                                       freq="B")

        exposure_values_df = QFDataFrame(index=backtest_dates,
                                         columns=pd.Index(tickers,
                                                          name=TICKERS))

        for ticker in tickers:
            if isinstance(ticker, FutureTicker):
                # Even if the tickers were already initialize, during pickling process, the data handler and timer
                # information is lost
                ticker.initialize_data_provider(self._timer, data_handler)

        for i, curr_datetime in enumerate(backtest_dates):
            new_exposures = QFSeries(index=tickers)
            self._timer.set_current_time(curr_datetime)

            for j, ticker, curr_exp_value in zip(count(), tickers,
                                                 current_exposures_values):
                curr_exp = Exposure(curr_exp_value) if is_finite_number(
                    curr_exp_value) else None
                try:
                    new_exp = model.calculate_exposure(ticker, curr_exp)
                except NoValidTickerException:
                    new_exp = None
                new_exposures.iloc[
                    j] = new_exp.value if new_exp is not None else None

            # assuming that we always follow the new_exposures from strategy, disregarding confidence levels
            # and expected moves, looking only at the suggested exposure
            current_exposures_values = new_exposures
            exposure_values_df.iloc[i, :] = current_exposures_values.iloc[:]

        exposure_values_df = exposure_values_df.dropna(axis=1, how="all")
        return exposure_values_df
Пример #17
0
 def test_is_finite_number(self):
     self.assertFalse(is_finite_number(None))
     self.assertFalse(
         is_finite_number(
             "There is a bomb attack on US president being planned"))
     self.assertFalse(is_finite_number(np.inf))
     self.assertFalse(is_finite_number(np.nan))
     self.assertFalse(is_finite_number(self))
     self.assertTrue(is_finite_number(2.5))
Пример #18
0
def capture_ratio(strategy: QFSeries, benchmark: QFSeries, upside_capture: bool,
                  frequency: Frequency = Frequency.MONTHLY) -> float:
    """
    Upside / Downside Capture Ratio
    This ratio is a statistical measure of a Fund Manager's overall performance in down or up markets.
    For example Downside Capture Ratio is used to evaluate how well or poorly the Manager performed
    relative to a specific index during periods when that index has dropped (had negative returns).
    The ratio is calculated by dividing the Strategy returns by the returns of the index during the down-market
    Downside Capture Ratio = sum(Strategy  Returns) / sum(Index Returns)

    A Fund Manager who has a downside capture ratio less than 1 has outperformed the index during the down-market
    by falling less than the index. For instance, a ratio of 0.75 indicates that the portfolio declined only 75%
    as much as the index during the period under consideration.

    Parameters
    ----------
    strategy:
        series of the strategy that will be evaluated
    benchmark:
        series of the benchmark for the strategy
    upside_capture: bool
        True - for upside capture ratio
        False - for downside capture ratio
    frequency: Frequency
        Frequency on which the the ratio is evaluated.
        For example Frequency.MONTHLY will result in evaluating the ration based on Monthly returns.

    Returns
    -------
    float
    """

    aggregated_strategy = get_aggregate_returns(strategy, frequency)
    aggregated_benchmark = get_aggregate_returns(benchmark, frequency)

    if upside_capture:
        selected_dates = aggregated_benchmark.loc[aggregated_benchmark > 0].index
    else:
        selected_dates = aggregated_benchmark.loc[aggregated_benchmark < 0].index

    selected_strategy_returns = aggregated_strategy.loc[selected_dates]
    selected_benchmark_returns = aggregated_benchmark.loc[selected_dates]
    benchmark_sum = selected_benchmark_returns.sum()

    if benchmark_sum != 0.0 and is_finite_number(benchmark_sum):
        return selected_strategy_returns.sum() / selected_benchmark_returns.sum()
    return float('nan')
Пример #19
0
    def __init__(self,
                 broker: Broker,
                 data_provider: DataProvider,
                 order_factory: OrderFactory,
                 signals_register: SignalsRegister,
                 initial_risk: float,
                 max_target_percentage: float = None,
                 tolerance_percentage: float = 0.0):
        super().__init__(broker, data_provider, order_factory,
                         signals_register)

        assert is_finite_number(
            initial_risk), "Initial risk has to be a finite number"
        assert initial_risk >= 0, "Initial risk has to be positive"

        self._initial_risk = initial_risk
        self.max_target_percentage = max_target_percentage
        self.tolerance_percentage = tolerance_percentage
Пример #20
0
    def __init__(self,
                 broker: Broker,
                 data_handler: DataHandler,
                 order_factory: OrderFactory,
                 contract_ticker_mapper: ContractTickerMapper,
                 signals_register: SignalsRegister,
                 initial_risk: float,
                 max_target_percentage: float = None,
                 tolerance_percentage: float = 0.0):
        super().__init__(broker, data_handler, order_factory,
                         contract_ticker_mapper, signals_register)

        assert is_finite_number(
            initial_risk), "Initial risk has to be a finite number"
        assert initial_risk >= 0, "Initial risk has to be positive"

        self._initial_risk = initial_risk
        self.max_target_percentage = max_target_percentage
        self.tolerance_percentage = tolerance_percentage

        self._signals_register.set_initial_risk(initial_risk)
        self.logger.info("Initial Risk: {}".format(initial_risk))
Пример #21
0
    def _generate_stop_order(self, contract, signal, market_order: Order):
        # stop_quantity = existing position size + recent market orders quantity
        stop_quantity = self._get_existing_position_quantity(contract)

        if market_order is not None:
            stop_quantity += market_order.quantity

        if stop_quantity != 0:
            stop_price = self._calculate_stop_price(signal)
            assert is_finite_number(
                stop_price), "Stop price should be a finite number"

            # put minus before the quantity as stop order has to go in the opposite direction
            stop_orders = self._order_factory.orders(
                {contract: -stop_quantity}, StopOrder(stop_price),
                TimeInForce.GTC)

            assert len(stop_orders) == 1, "Only one order should be generated"
            return stop_orders[0]
        else:
            # quantity is 0 - no need to place a stop order
            return None
Пример #22
0
 def append_to_statistics(measure_description: str, function: Callable, trades_containers,
                          percentage_style: bool = False):
     style_format = "{:.2%}" if percentage_style else "{:.2f}"
     returned_values = (function(tc) for tc in trades_containers)
     returned_values = (value if is_finite_number(value) else 0.0 for value in returned_values)
     statistics.append((measure_description, *(style_format.format(val) for val in returned_values)))