Esempio n. 1
0
    def simulate(self, event, current_orders):

        dt = event.dt
        simulated_impact = 0.0
        max_volume = self.volume_limit * event.volume
        total_volume = 0

        txns = []
        for order in current_orders:

            open_amount = order.amount - order.filled

            if zp_math.tolerant_equals(open_amount, 0):
                continue

            order.check_triggers(event)
            if not order.triggered:
                continue

            # price impact accounts for the total volume of transactions
            # created against the current minute bar
            remaining_volume = max_volume - total_volume
            if (
                remaining_volume <= 0
                or
                zp_math.tolerant_equals(remaining_volume, 0)
            ):
                # we can't fill any more transactions
                return txns

            # the current order amount will be the min of the
            # volume available in the bar or the open amount.
            cur_amount = min(remaining_volume, abs(open_amount))
            cur_amount = cur_amount * order.direction
            # tally the current amount into our total amount ordered.
            # total amount will be used to calculate price impact
            total_volume = total_volume + order.direction * cur_amount

            volume_share = min(order.direction * (total_volume) / event.volume,
                               self.volume_limit)

            simulated_impact = (volume_share) ** 2 \
                * self.price_impact * order.direction * event.price

            if order.direction * cur_amount > 0:
                txn = create_transaction(
                    event.sid,
                    cur_amount,
                    # In the future, we may want to change the next line
                    # for limit pricing
                    event.price + simulated_impact,
                    dt.replace(tzinfo=pytz.utc),
                    order.id
                )

                txns.append(txn)

        return txns
Esempio n. 2
0
    def simulate(self, event, current_orders):

        dt = event.dt
        simulated_impact = 0.0
        max_volume = self.volume_limit * event.volume
        total_volume = 0

        txns = []
        for order in current_orders:

            open_amount = order.amount - order.filled

            if zp_math.tolerant_equals(open_amount, 0):
                continue

            order.check_triggers(event)
            if not order.triggered:
                continue

            # price impact accounts for the total volume of transactions
            # created against the current minute bar
            remaining_volume = max_volume - total_volume
            if (remaining_volume <= 0
                    or zp_math.tolerant_equals(remaining_volume, 0)):
                # we can't fill any more transactions
                return txns

            # the current order amount will be the min of the
            # volume available in the bar or the open amount.
            cur_amount = min(remaining_volume, abs(open_amount))
            cur_amount = cur_amount * order.direction
            # tally the current amount into our total amount ordered.
            # total amount will be used to calculate price impact
            total_volume = total_volume + order.direction * cur_amount

            volume_share = min(order.direction * (total_volume) / event.volume,
                               self.volume_limit)

            simulated_impact = (volume_share) ** 2 \
                * self.price_impact * order.direction * event.price

            if order.direction * cur_amount > 0:
                txn = create_transaction(
                    event.sid,
                    cur_amount,
                    # In the future, we may want to change the next line
                    # for limit pricing
                    event.price + simulated_impact,
                    dt.replace(tzinfo=pytz.utc),
                    order.id)

                txns.append(txn)

        return txns
Esempio n. 3
0
def asymmetric_round_price(price, prefer_round_down, tick_size, diff=0.95):
    """
    Asymmetric rounding function for adjusting prices to the specified number
    of places in a way that "improves" the price. For limit prices, this means
    preferring to round down on buys and preferring to round up on sells.
    For stop prices, it means the reverse.

    If prefer_round_down == True:
        When .05 below to .95 above a specified decimal place, use it.
    If prefer_round_down == False:
        When .95 below to .05 above a specified decimal place, use it.

    In math-speak:
    If prefer_round_down: [<X-1>.0095, X.0195) -> round to X.01.
    If not prefer_round_down: (<X-1>.0005, X.0105] -> round to X.01.
    """
    precision = zp_math.number_of_decimal_places(tick_size)
    multiplier = int(tick_size * (10**precision))
    diff -= 0.5  # shift the difference down
    diff *= (10**-precision)  # adjust diff to precision of tick size
    diff *= multiplier  # adjust diff to value of tick_size

    # Subtracting an epsilon from diff to enforce the open-ness of the upper
    # bound on buys and the lower bound on sells.  Using the actual system
    # epsilon doesn't quite get there, so use a slightly less epsilon-ey value.
    epsilon = float_info.epsilon * 10
    diff = diff - epsilon

    # relies on rounding half away from zero, unlike numpy's bankers' rounding
    rounded = tick_size * consistent_round(
        (price - (diff if prefer_round_down else -diff)) / tick_size)
    if zp_math.tolerant_equals(rounded, 0.0):
        return 0.0
    return rounded
Esempio n. 4
0
def sharpe_ratio(algorithm_volatility, annualized_return, treasury_return):
    """
    http://en.wikipedia.org/wiki/Sharpe_ratio

    Args:
        algorithm_volatility (float): Algorithm volatility.
        algorithm_return (float): Algorithm return percentage.
        treasury_return (float): Treasury return percentage.

    Returns:
        float. The Sharpe ratio.
    """
    if zp_math.tolerant_equals(algorithm_volatility, 0):
        return np.nan

    return (
        (annualized_return - treasury_return)
        # The square of the annualization factor is in the volatility,
        # because the volatility is also annualized,
        # i.e. the sqrt(annual factor) is in the volatility's numerator.
        # So to have the the correct annualization factor for the
        # Sharpe value's numerator, which should be the sqrt(annual factor).
        # The square of the sqrt of the annual factor, i.e. the annual factor
        # itself, is needed in the numerator to factor out the division by
        # its square root.
        / algorithm_volatility)
Esempio n. 5
0
    def process_trade(self, trade_event):
        if zp_math.tolerant_equals(trade_event.volume, 0):
            # there are zero volume trade_events bc some stocks trade
            # less frequently than once per minute.
            return []

        if trade_event.sid in self.open_orders:
            orders = self.open_orders[trade_event.sid]
            orders = sorted(orders, key=lambda o: o.dt)
            # Only use orders for the current day or before
            current_orders = filter(
                lambda o: o.dt <= trade_event.dt,
                orders)
        else:
            return []

        txns = self.transact(trade_event, current_orders)
        for txn in txns:
            self.txns_by_order[txn.order_id].append(txn)
            self.txns_by_sid[txn.sid].append(txn)
            self.orders[txn.order_id].filled += txn.amount

        # update the open orders for the trade_event's sid
        self.open_orders[trade_event.sid] = \
            [order for order in orders if order.open]

        # drop any filled orders.
        filled = \
            [order.id for order in orders if not order.open]

        for order_id in filled:
            del self.orders[order_id]

        return txns
Esempio n. 6
0
def asymmetric_round_price_to_penny(price,
                                    prefer_round_down,
                                    diff=(0.0095 - .005)):
    """
    Asymmetric rounding function for adjusting prices to two places in a way
    that "improves" the price.  For limit prices, this means preferring to
    round down on buys and preferring to round up on sells.  For stop prices,
    it means the reverse.

    If prefer_round_down == True:
        When .05 below to .95 above a penny, use that penny.
    If prefer_round_down == False:
        When .95 below to .05 above a penny, use that penny.

    In math-speak:
    If prefer_round_down: [<X-1>.0095, X.0195) -> round to X.01.
    If not prefer_round_down: (<X-1>.0005, X.0105] -> round to X.01.
    """
    # Subtracting an epsilon from diff to enforce the open-ness of the upper
    # bound on buys and the lower bound on sells.  Using the actual system
    # epsilon doesn't quite get there, so use a slightly less epsilon-ey value.
    epsilon = float_info.epsilon * 10
    diff = diff - epsilon

    # relies on rounding half away from zero, unlike numpy's bankers' rounding
    rounded = round(price - (diff if prefer_round_down else -diff), 2)
    if zp_math.tolerant_equals(rounded, 0.0):
        return 0.0
    return rounded
def information_ratio(algo_volatility, algorithm_return, benchmark_return):
    """
    http://en.wikipedia.org/wiki/Information_ratio

    Args:
        algorithm_returns (np.array-like):
            All returns during algorithm lifetime.
        benchmark_returns (np.array-like):
            All benchmark returns during algo lifetime.

    Returns:
        float. Information ratio.
    """
    if zp_math.tolerant_equals(algo_volatility, 0):
        return np.nan

    # The square of the annualization factor is in the volatility,
    # because the volatility is also annualized,
    # i.e. the sqrt(annual factor) is in the volatility's numerator.
    # So to have the the correct annualization factor for the
    # Sharpe value's numerator, which should be the sqrt(annual factor).
    # The square of the sqrt of the annual factor, i.e. the annual factor
    # itself, is needed in the numerator to factor out the division by
    # its square root.
    return (algorithm_return - benchmark_return) / algo_volatility
Esempio n. 8
0
    def process_trade(self, trade_event):
        if trade_event.type != zp.DATASOURCE_TYPE.TRADE:
            return

        if zp_math.tolerant_equals(trade_event.volume, 0):
            # there are zero volume trade_events bc some stocks trade
            # less frequently than once per minute.
            return

        if trade_event.sid not in self.open_orders:
            return

        orders = self.open_orders[trade_event.sid]
        orders = sorted(orders, key=lambda o: o.dt)
        # Only use orders for the current day or before
        current_orders = filter(
            lambda o: o.dt <= trade_event.dt,
            orders)

        for order, txn in self.process_transactions(trade_event,
                                                    current_orders):
            yield order, txn

        # update the open orders for the trade_event's sid
        self.open_orders[trade_event.sid] = \
            [order for order
             in self.open_orders[trade_event.sid]
             if order.open]
Esempio n. 9
0
    def simulate(self, event, orders):

        txns = []
        for order in orders:
            # TODO: what if we have 2 orders, one for 100 shares long,
            # and one for 100 shares short
            # such as in a hedging scenario?

            # check price limits, continue if the
            # order isn't triggered yet
            order.check_triggers(event)
            if not order.triggered:
                continue

            if zp_math.tolerant_equals(order.amount, 0):
                return txns

            txn = create_transaction(
                event.sid,
                order.amount,
                event.price + (self.spread / 2.0 * order.direction),
                event.dt.replace(tzinfo=pytz.utc),
                order.id
            )

            # mark the last_modified date of the order to match
            order.last_modified = event.dt
            txns.append(txn)
        return txns
Esempio n. 10
0
def asymmetric_round_price_to_penny(price, prefer_round_down,
                                    diff=(0.0095 - .005)):
    """
    Asymmetric rounding function for adjusting prices to two places in a way
    that "improves" the price.  For limit prices, this means preferring to
    round down on buys and preferring to round up on sells.  For stop prices,
    it means the reverse.

    If prefer_round_down == True:
        When .05 below to .95 above a penny, use that penny.
    If prefer_round_down == False:
        When .95 below to .05 above a penny, use that penny.

    In math-speak:
    If prefer_round_down: [<X-1>.0095, X.0195) -> round to X.01.
    If not prefer_round_down: (<X-1>.0005, X.0105] -> round to X.01.
    """
    # Subtracting an epsilon from diff to enforce the open-ness of the upper
    # bound on buys and the lower bound on sells.  Using the actual system
    # epsilon doesn't quite get there, so use a slightly less epsilon-ey value.
    epsilon = float_info.epsilon * 10
    diff = diff - epsilon

    # relies on rounding half away from zero, unlike numpy's bankers' rounding
    rounded = round(price - (diff if prefer_round_down else -diff), 2)
    if zp_math.tolerant_equals(rounded, 0.0):
        return 0.0
    return rounded
Esempio n. 11
0
    def process_trade(self, trade_event):
        if zp_math.tolerant_equals(trade_event.volume, 0):
            # there are zero volume trade_events bc some stocks trade
            # less frequently than once per minute.
            return [], []

        if trade_event.sid in self.open_orders:
            orders = self.open_orders[trade_event.sid]
            orders = sorted(orders, key=lambda o: o.dt)
            # Only use orders for the current day or before
            current_orders = filter(lambda o: o.dt <= trade_event.dt, orders)
        else:
            return [], []

        txns = self.transact(trade_event, current_orders)
        for txn in txns:
            self.orders[txn.order_id].filled += txn.amount
            # mark the date of the order to match the txn
            self.orders[txn.order_id].dt = txn.dt

        modified_orders = [
            order for order in self.open_orders[trade_event.sid]
            if order.dt == trade_event.dt
        ]
        for order in modified_orders:
            if not order.open:
                del self.orders[order.id]

        # update the open orders for the trade_event's sid
        self.open_orders[trade_event.sid] = \
            [order for order
             in self.open_orders[trade_event.sid]
             if order.open]

        return txns, modified_orders
Esempio n. 12
0
def assert_float_equal(
    result,
    expected,
    path=(),
    msg="",
    float_rtol=10e-7,
    float_atol=10e-7,
    float_equal_nan=True,
    **kwargs,
):
    assert tolerant_equals(
        result,
        expected,
        rtol=float_rtol,
        atol=float_atol,
        equal_nan=float_equal_nan,
    ), "%s%s != %s with rtol=%s and atol=%s%s\n%s" % (
        _fmt_msg(msg),
        result,
        expected,
        float_rtol,
        float_atol,
        (" (with nan != nan)" if not float_equal_nan else ""),
        _fmt_path(path),
    )
Esempio n. 13
0
    def order_value(self, sid, value,
                    limit_price=None, stop_price=None, style=None):
        """
        Place an order by desired value rather than desired number of shares.
        If the requested sid is found in the universe, the requested value is
        divided by its price to imply the number of shares to transact.

        value > 0 :: Buy/Cover
        value < 0 :: Sell/Short
        Market order:    order(sid, value)
        Limit order:     order(sid, value, limit_price)
        Stop order:      order(sid, value, None, stop_price)
        StopLimit order: order(sid, value, limit_price, stop_price)
        """
        last_price = self.trading_client.current_data[sid].price
        if tolerant_equals(last_price, 0):
            zero_message = "Price of 0 for {psid}; can't infer value".format(
                psid=sid
            )
            if self.logger:
                self.logger.debug(zero_message)
            # Don't place any order
            return
        else:
            amount = value / last_price
            return self.order(sid, amount,
                              limit_price=limit_price,
                              stop_price=stop_price,
                              style=style)
Esempio n. 14
0
def sharpe_ratio(algorithm_volatility, annualized_return, treasury_return):
    """
    http://en.wikipedia.org/wiki/Sharpe_ratio

    Args:
        algorithm_volatility (float): Algorithm volatility.
        algorithm_return (float): Algorithm return percentage.
        treasury_return (float): Treasury return percentage.

    Returns:
        float. The Sharpe ratio.
    """
    if zp_math.tolerant_equals(algorithm_volatility, 0):
        return np.nan

    return (
        (annualized_return - treasury_return)
        # The square of the annualization factor is in the volatility,
        # because the volatility is also annualized,
        # i.e. the sqrt(annual factor) is in the volatility's numerator.
        # So to have the the correct annualization factor for the
        # Sharpe value's numerator, which should be the sqrt(annual factor).
        # The square of the sqrt of the annual factor, i.e. the annual factor
        # itself, is needed in the numerator to factor out the division by
        # its square root.
        / algorithm_volatility
    )
Esempio n. 15
0
def information_ratio(algorithm_returns, benchmark_returns):
    """
    http://en.wikipedia.org/wiki/Information_ratio

    Args:
        algorithm_returns (np.array-like):
            All returns during algorithm lifetime.
        benchmark_returns (np.array-like):
            All benchmark returns during algo lifetime.

    Returns:
        float. Information ratio.
    """
    relative_returns = algorithm_returns - benchmark_returns

    relative_deviation = relative_returns.std(ddof=1)

    if (
        zp_math.tolerant_equals(relative_deviation, 0)
        or
        np.isnan(relative_deviation)
    ):
        return 0.0

    return np.mean(relative_returns) / relative_deviation
Esempio n. 16
0
def sortino_ratio(algorithm_returns, algorithm_period_return, mar):
    """
    http://en.wikipedia.org/wiki/Sortino_ratio

    Args:
        algorithm_returns (np.array-like):
            Returns from algorithm lifetime.
        algorithm_period_return (float):
            Algorithm return percentage from latest period.
        mar (float): Minimum acceptable return.

    Returns:
        float. The Sortino ratio.
    """
    if len(algorithm_returns) == 0:
        return 0.0

    rets = algorithm_returns
    downside = (rets[rets < mar] - mar)**2
    dr = np.sqrt(downside.sum() / len(rets))

    if zp_math.tolerant_equals(dr, 0):
        return 0.0

    return (algorithm_period_return - mar) / dr
Esempio n. 17
0
def information_ratio(algo_volatility, algorithm_return, benchmark_return):
    """
    http://en.wikipedia.org/wiki/Information_ratio

    Args:
        algorithm_returns (np.array-like):
            All returns during algorithm lifetime.
        benchmark_returns (np.array-like):
            All benchmark returns during algo lifetime.

    Returns:
        float. Information ratio.
    """
    if zp_math.tolerant_equals(algo_volatility, 0):
        return np.nan

    return (
        (algorithm_return - benchmark_return)
        # The square of the annualization factor is in the volatility,
        # because the volatility is also annualized,
        # i.e. the sqrt(annual factor) is in the volatility's numerator.
        # So to have the the correct annualization factor for the
        # Sharpe value's numerator, which should be the sqrt(annual factor).
        # The square of the sqrt of the annual factor, i.e. the annual factor
        # itself, is needed in the numerator to factor out the division by
        # its square root.
        / algo_volatility
    )
Esempio n. 18
0
def sortino_ratio(algorithm_returns, algorithm_period_return, mar):
    """
    http://en.wikipedia.org/wiki/Sortino_ratio

    Args:
        algorithm_returns (np.array-like):
            Returns from algorithm lifetime.
        algorithm_period_return (float):
            Algorithm return percentage from latest period.
        mar (float): Minimum acceptable return.

    Returns:
        float. The Sortino ratio.
    """
    if len(algorithm_returns) == 0:
        return 0.0

    rets = algorithm_returns
    downside = (rets[rets < mar] - mar) ** 2
    dr = np.sqrt(downside.sum() / len(rets))

    if zp_math.tolerant_equals(dr, 0):
        return 0.0

    return (algorithm_period_return - mar) / dr
Esempio n. 19
0
def asymmetric_round_price(price, prefer_round_down, tick_size, diff=0.95):
    """
    Asymmetric rounding function for adjusting prices to the specified number
    of places in a way that "improves" the price. For limit prices, this means
    preferring to round down on buys and preferring to round up on sells.
    For stop prices, it means the reverse.

    If prefer_round_down == True:
        When .05 below to .95 above a specified decimal place, use it.
    If prefer_round_down == False:
        When .95 below to .05 above a specified decimal place, use it.

    In math-speak:
    If prefer_round_down: [<X-1>.0095, X.0195) -> round to X.01.
    If not prefer_round_down: (<X-1>.0005, X.0105] -> round to X.01.
    """
    precision = zp_math.number_of_decimal_places(tick_size)
    multiplier = int(tick_size * (10 ** precision))
    diff -= 0.5  # shift the difference down
    diff *= (10 ** -precision)  # adjust diff to precision of tick size
    diff *= multiplier  # adjust diff to value of tick_size

    # Subtracting an epsilon from diff to enforce the open-ness of the upper
    # bound on buys and the lower bound on sells.  Using the actual system
    # epsilon doesn't quite get there, so use a slightly less epsilon-ey value.
    epsilon = float_info.epsilon * 10
    diff = diff - epsilon

    # relies on rounding half away from zero, unlike numpy's bankers' rounding
    rounded = tick_size * consistent_round(
        (price - (diff if prefer_round_down else -diff)) / tick_size
    )
    if zp_math.tolerant_equals(rounded, 0.0):
        return 0.0
    return rounded
Esempio n. 20
0
    def test_split_long_position(self):
        events = factory.create_trade_history(1, [20, 20], [100, 100], oneday,
                                              self.sim_params)

        # set up a long position in sid 1
        # 100 shares at $20 apiece = $2000 position
        txns = [create_txn(events[0], 20, 100)]

        # set up a split with ratio 3 occurring at the start of the second
        # day.
        splits = [
            factory.create_split(
                1,
                3,
                events[1].dt,
            ),
        ]

        results = calculate_results(self, events, txns=txns, splits=splits)

        # should have 33 shares (at $60 apiece) and $20 in cash
        self.assertEqual(2, len(results))

        latest_positions = results[1]['daily_perf']['positions']
        self.assertEqual(1, len(latest_positions))

        # check the last position to make sure it's been updated
        position = latest_positions[0]

        self.assertEqual(1, position['sid'])
        self.assertEqual(33, position['amount'])
        self.assertEqual(60, position['cost_basis'])
        self.assertEqual(60, position['last_sale_price'])

        # since we started with $10000, and we spent $2000 on the
        # position, but then got $20 back, we should have $8020
        # (or close to it) in cash.

        # we won't get exactly 8020 because sometimes a split is
        # denoted as a ratio like 0.3333, and we lose some digits
        # of precision.  thus, make sure we're pretty close.
        daily_perf = results[1]['daily_perf']

        self.assertTrue(
            zp_math.tolerant_equals(8020, daily_perf['ending_cash'], 1))

        for i, result in enumerate(results):
            for perf_kind in ('daily_perf', 'cumulative_perf'):
                perf_result = result[perf_kind]
                # prices aren't changing, so pnl and returns should be 0.0
                self.assertEqual(
                    0.0, perf_result['pnl'],
                    "day %s %s pnl %s instead of 0.0" %
                    (i, perf_kind, perf_result['pnl']))
                self.assertEqual(
                    0.0, perf_result['returns'],
                    "day %s %s returns %s instead of 0.0" %
                    (i, perf_kind, perf_result['returns']))
Esempio n. 21
0
    def test_split_long_position(self):
        with trading.TradingEnvironment() as env:
            events = factory.create_trade_history(
                1,
                [20, 20],
                [100, 100],
                oneday,
                self.sim_params
            )

            # set up a long position in sid 1
            # 100 shares at $20 apiece = $2000 position
            events.insert(0, create_txn(events[0], 20, 100))

            # set up a split with ratio 3
            events.append(factory.create_split(1, 3,
                          env.next_trading_day(events[1].dt)))

            results = calculate_results(self, events)

            # should have 33 shares (at $60 apiece) and $20 in cash
            self.assertEqual(2, len(results))

            latest_positions = results[1]['daily_perf']['positions']
            self.assertEqual(1, len(latest_positions))

            # check the last position to make sure it's been updated
            position = latest_positions[0]

            self.assertEqual(1, position['sid'])
            self.assertEqual(33, position['amount'])
            self.assertEqual(60, position['cost_basis'])
            self.assertEqual(60, position['last_sale_price'])

            # since we started with $10000, and we spent $2000 on the
            # position, but then got $20 back, we should have $8020
            # (or close to it) in cash.

            # we won't get exactly 8020 because sometimes a split is
            # denoted as a ratio like 0.3333, and we lose some digits
            # of precision.  thus, make sure we're pretty close.
            daily_perf = results[1]['daily_perf']

            self.assertTrue(
                zp_math.tolerant_equals(8020,
                                        daily_perf['ending_cash'], 1))

            for i, result in enumerate(results):
                for perf_kind in ('daily_perf', 'cumulative_perf'):
                    perf_result = result[perf_kind]
                    # prices aren't changing, so pnl and returns should be 0.0
                    self.assertEqual(0.0, perf_result['pnl'],
                                     "day %s %s pnl %s instead of 0.0" %
                                     (i, perf_kind, perf_result['pnl']))
                    self.assertEqual(0.0, perf_result['returns'],
                                     "day %s %s returns %s instead of 0.0" %
                                     (i, perf_kind, perf_result['returns']))
Esempio n. 22
0
def transact_stub(slippage, commission, event, open_orders):
    """
    This is intended to be wrapped in a partial, so that the
    slippage and commission models can be enclosed.
    """
    for order, transaction in slippage(event, open_orders):
        if (transaction
                and not zp_math.tolerant_equals(transaction.amount, 0)):
            direction = math.copysign(1, transaction.amount)
            per_share, total_commission = commission.calculate(transaction)
            transaction.price = transaction.price + (per_share * direction)
            transaction.commission = total_commission
        yield order, transaction
Esempio n. 23
0
    def get_stddev(self):
        # Sample standard deviation is undefined for a single event or
        # no events.
        if len(self) <= 1:
            return None

        else:
            average = self.sum / len(self)
            s_squared = (self.sum_sqr - self.sum * average) \
                / (len(self) - 1)

            if zp_math.tolerant_equals(0, s_squared):
                return 0.0
            stddev = sqrt(s_squared)
        return stddev
Esempio n. 24
0
    def get_stddev(self):
        # Sample standard deviation is undefined for a single event or
        # no events.
        if len(self) <= 1:
            return None

        else:
            average = self.sum / len(self)
            s_squared = (self.sum_sqr - self.sum * average) \
                / (len(self) - 1)

            if zp_math.tolerant_equals(0, s_squared):
                return 0.0
            stddev = sqrt(s_squared)
        return stddev
Esempio n. 25
0
def assert_float_equal(
    result, expected, path=(), msg="", float_rtol=10e-7, float_atol=10e-7, float_equal_nan=True, **kwargs
):
    assert tolerant_equals(result, expected, rtol=float_rtol, atol=float_atol, equal_nan=float_equal_nan), (
        "%s%s != %s with rtol=%s and atol=%s%s\n%s"
        % (
            _fmt_msg(msg),
            result,
            expected,
            float_rtol,
            float_atol,
            (" (with nan != nan)" if not float_equal_nan else ""),
            _fmt_path(path),
        )
    )
Esempio n. 26
0
    def process_trade(self, trade_event):
        if trade_event.type != zp.DATASOURCE_TYPE.TRADE:
            return

        if zp_math.tolerant_equals(trade_event.volume, 0):
            # there are zero volume trade_events bc some stocks trade
            # less frequently than once per minute.
            return

        if trade_event.sid in self.open_orders:
            orders = self.open_orders[trade_event.sid]
            orders = sorted(orders, key=lambda o: o.dt)
            # Only use orders for the current day or before
            current_orders = filter(
                lambda o: o.dt <= trade_event.dt,
                orders)
        else:
            return

        for order, txn in self.transact(trade_event, current_orders):
            if txn.type == zp.DATASOURCE_TYPE.COMMISSION:
                order.commission = (order.commission or 0.0) + txn.cost
            else:
                if txn.amount == 0:
                    raise zipline.errors.TransactionWithNoAmount(txn=txn)
                if math.copysign(1, txn.amount) != order.direction:
                    raise zipline.errors.TransactionWithWrongDirection(
                        txn=txn, order=order)
                if abs(txn.amount) > abs(self.orders[txn.order_id].amount):
                    raise zipline.errors.TransactionVolumeExceedsOrder(
                        txn=txn, order=order)

                order.filled += txn.amount
                if txn.commission is not None:
                    order.commission = ((order.commission or 0.0)
                                        + txn.commission)

            # mark the date of the order to match the transaction
            # that is filling it.
            order.dt = txn.dt

            yield txn, order

        # update the open orders for the trade_event's sid
        self.open_orders[trade_event.sid] = \
            [order for order
             in self.open_orders[trade_event.sid]
             if order.open]
Esempio n. 27
0
def sharpe_ratio(algorithm_volatility, algorithm_return, treasury_return):
    """
    http://en.wikipedia.org/wiki/Sharpe_ratio

    Args:
        algorithm_volatility (float): Algorithm volatility.
        algorithm_return (float): Algorithm return percentage.
        treasury_return (float): Treasury return percentage.

    Returns:
        float. The Sharpe ratio.
    """
    if zp_math.tolerant_equals(algorithm_volatility, 0):
        return 0.0

    return (algorithm_return - treasury_return) / algorithm_volatility
    def _calculate_order_value_amount(self, asset, value):
        """
        # on live trading the delisted check is delegated to the broker
        """
        last_price = self.trading_client.current_data.current(asset, "price")
        if tolerant_equals(last_price, 0):
            zero_message = "Price of 0 for {psid}; can't infer value".format(
                psid=asset)
            if self.logger:
                self.logger.debug(zero_message)
            # Don't place any order
            return 0

        value_multiplier = asset.price_multiplier

        return value / (last_price * value_multiplier)
Esempio n. 29
0
def sharpe_ratio(algorithm_volatility, algorithm_return, treasury_return):
    """
    http://en.wikipedia.org/wiki/Sharpe_ratio

    Args:
        algorithm_volatility (float): Algorithm volatility.
        algorithm_return (float): Algorithm return percentage.
        treasury_return (float): Treasury return percentage.

    Returns:
        float. The Sharpe ratio.
    """
    if zp_math.tolerant_equals(algorithm_volatility, 0):
        return np.nan

    return (algorithm_return - treasury_return) / algorithm_volatility
Esempio n. 30
0
def transact_stub(slippage, commission, event, open_orders):
    """
    This is intended to be wrapped in a partial, so that the
    slippage and commission models can be enclosed.
    """
    for order, transaction in slippage(event, open_orders):
        if (
            transaction
            and not
            zp_math.tolerant_equals(transaction.amount, 0)
        ):
            direction = math.copysign(1, transaction.amount)
            per_share, total_commission = commission.calculate(transaction)
            transaction.price = transaction.price + (per_share * direction)
            transaction.commission = total_commission
        yield order, transaction
Esempio n. 31
0
    def test_split_long_position(self):
        with trading.TradingEnvironment() as env:
            events = factory.create_trade_history(
                1,
                [20, 20],
                [100, 100],
                oneday,
                self.sim_params
            )

            # set up a long position in sid 1
            # 100 shares at $20 apiece = $2000 position
            events.insert(0, create_txn(events[0], 20, 100))

            # set up a split with ratio 3
            events.append(factory.create_split(1, 3,
                          env.next_trading_day(events[1].dt)))

            results = calculate_results(self, events)

            # should have 33 shares (at $60 apiece) and $20 in cash
            self.assertEqual(2, len(results))

            latest_positions = results[1]['daily_perf']['positions']
            self.assertEqual(1, len(latest_positions))

            # check the last position to make sure it's been updated
            position = latest_positions[0]

            self.assertEqual(1, position['sid'])
            self.assertEqual(33, position['amount'])
            self.assertEqual(60, position['cost_basis'])
            self.assertEqual(60, position['last_sale_price'])

            # since we started with $10000, and we spent $2000 on the
            # position, but then got $20 back, we should have $8020
            # (or close to it) in cash.

            # we won't get exactly 8020 because sometimes a split is
            # denoted as a ratio like 0.3333, and we lose some digits
            # of precision.  thus, make sure we're pretty close.
            daily_perf = results[1]['daily_perf']

            self.assertTrue(
                zp_math.tolerant_equals(8020,
                                        daily_perf['ending_cash'], 1))
Esempio n. 32
0
    def process_trade(self, trade_event):
        if trade_event.type != zp.DATASOURCE_TYPE.TRADE:
            return

        if zp_math.tolerant_equals(trade_event.volume, 0):
            # there are zero volume trade_events bc some stocks trade
            # less frequently than once per minute.
            return

        if trade_event.sid in self.open_orders:
            orders = self.open_orders[trade_event.sid]
            orders = sorted(orders, key=lambda o: o.dt)
            # Only use orders for the current day or before
            current_orders = filter(lambda o: o.dt <= trade_event.dt, orders)
        else:
            return

        for order, txn in self.transact(trade_event, current_orders):
            if txn.type == zp.DATASOURCE_TYPE.COMMISSION:
                order.commission = (order.commission or 0.0) + txn.cost
            else:
                if txn.amount == 0:
                    raise zipline.errors.TransactionWithNoAmount(txn=txn)
                if math.copysign(1, txn.amount) != order.direction:
                    raise zipline.errors.TransactionWithWrongDirection(
                        txn=txn, order=order)
                if abs(txn.amount) > abs(self.orders[txn.order_id].amount):
                    raise zipline.errors.TransactionVolumeExceedsOrder(
                        txn=txn, order=order)

                order.filled += txn.amount
                if txn.commission is not None:
                    order.commission = ((order.commission or 0.0) +
                                        txn.commission)

            # mark the date of the order to match the transaction
            # that is filling it.
            order.dt = txn.dt

            yield txn, order

        # update the open orders for the trade_event's sid
        self.open_orders[trade_event.sid] = \
            [order for order
             in self.open_orders[trade_event.sid]
             if order.open]
Esempio n. 33
0
def sortino_ratio(annualized_algorithm_return, treasury_return, downside_risk):
    """
    http://en.wikipedia.org/wiki/Sortino_ratio

    Args:
        algorithm_returns (np.array-like):
            Returns from algorithm lifetime.
        algorithm_period_return (float):
            Algorithm return percentage from latest period.
        mar (float): Minimum acceptable return.

    Returns:
        float. The Sortino ratio.
    """
    if np.isnan(downside_risk) or zp_math.tolerant_equals(downside_risk, 0):
        return 0.0

    return (annualized_algorithm_return - treasury_return) / downside_risk
Esempio n. 34
0
    def simulate(self, event, current_orders):

        self._volume_for_bar = 0

        for order in current_orders:

            if zp_math.tolerant_equals(order.open_amount, 0):
                continue

            order.check_triggers(event)
            if not order.triggered:
                continue

            txn = self.process_order(event, order)

            if txn:
                self._volume_for_bar += abs(txn.amount)
                yield order, txn
Esempio n. 35
0
def sortino_ratio(algorithm_period_return, treasury_period_return, mar):
    """
    http://en.wikipedia.org/wiki/Sortino_ratio

    Args:
        algorithm_returns (np.array-like):
            Returns from algorithm lifetime.
        algorithm_period_return (float):
            Algorithm return percentage from latest period.
        mar (float): Minimum acceptable return.

    Returns:
        float. The Sortino ratio.
    """
    if zp_math.tolerant_equals(mar, 0):
        return 0.0

    return (algorithm_period_return - treasury_period_return) / mar
Esempio n. 36
0
def sortino_ratio(annualized_algorithm_return, treasury_return, downside_risk):
    """
    http://en.wikipedia.org/wiki/Sortino_ratio

    Args:
        algorithm_returns (np.array-like):
            Returns from algorithm lifetime.
        algorithm_period_return (float):
            Algorithm return percentage from latest period.
        mar (float): Minimum acceptable return.

    Returns:
        float. The Sortino ratio.
    """
    if np.isnan(downside_risk) or zp_math.tolerant_equals(downside_risk, 0):
        return 0.0

    return (annualized_algorithm_return - treasury_return) / downside_risk
Esempio n. 37
0
    def simulate(self, event, current_orders):

        self._volume_for_bar = 0

        for order in current_orders:

            if zp_math.tolerant_equals(order.open_amount, 0):
                continue

            order.check_triggers(event)
            if not order.triggered:
                continue

            txn = self.process_order(event, order)

            if txn:
                self._volume_for_bar += abs(txn.amount)
                yield order, txn
Esempio n. 38
0
def sortino_ratio(algorithm_period_return, treasury_period_return, mar):
    """
    http://en.wikipedia.org/wiki/Sortino_ratio

    Args:
        algorithm_returns (np.array-like):
            Returns from algorithm lifetime.
        algorithm_period_return (float):
            Algorithm return percentage from latest period.
        mar (float): Minimum acceptable return.

    Returns:
        float. The Sortino ratio.
    """
    if zp_math.tolerant_equals(mar, 0):
        return 0.0

    return (algorithm_period_return - treasury_period_return) / mar
Esempio n. 39
0
    def _calculate_order_value_amount(self, asset, value):
        """
        Calculates how many shares/contracts to order based on the type of
        asset being ordered.
        """
        last_price = self.trading_client.current_data[asset].price

        if tolerant_equals(last_price, 0):
            zero_message = "Price of 0 for {psid}; can't infer value".format(psid=asset)
            if self.logger:
                self.logger.debug(zero_message)
            # Don't place any order
            return 0

        if isinstance(asset, Future):
            value_multiplier = asset.contract_multiplier
        else:
            value_multiplier = 1

        return value / (last_price * value_multiplier)
Esempio n. 40
0
    def _calculate_order_value_amount(self, asset, value):
        """
        Calculates how many shares/contracts to order based on the type of
        asset being ordered.
        """
        last_price = self.trading_client.current_data[asset].price

        if tolerant_equals(last_price, 0):
            zero_message = "Price of 0 for {psid}; can't infer value".format(
                psid=asset)
            if self.logger:
                self.logger.debug(zero_message)
            # Don't place any order
            return 0

        if isinstance(asset, Future):
            value_multiplier = asset.contract_multiplier
        else:
            value_multiplier = 1

        return value / (last_price * value_multiplier)
Esempio n. 41
0
 def order_target_value(self, sid, target,
                        limit_price=None, stop_price=None, style=None):
     """
     Place an order to adjust a position to a target value. If
     the position doesn't already exist, this is equivalent to placing a new
     order. If the position does exist, this is equivalent to placing an
     order for the difference between the target value and the
     current value.
     """
     last_price = self.trading_client.current_data[sid].price
     if tolerant_equals(last_price, 0):
         # Don't place an order
         if self.logger:
             zero_message = "Price of 0 for {psid}; can't infer value"
             self.logger.debug(zero_message.format(psid=sid))
         return
     target_amount = target / last_price
     return self.order_target(sid, target_amount,
                              limit_price=limit_price,
                              stop_price=stop_price,
                              style=style)
Esempio n. 42
0
def transact_stub(slippage, commission, event, open_orders):
    """
    This is intended to be wrapped in a partial, so that the
    slippage and commission models can be enclosed.
    """
    def inject_algo_dt(record):
        if not 'algo_dt' in record.extra:
            record.extra['algo_dt'] = event['dt']

    with Processor(inject_algo_dt).threadbound():

        transactions = slippage.simulate(event, open_orders)

        for transaction in transactions:
            if (transaction
                    and not zp_math.tolerant_equals(transaction.amount, 0)):
                direction = math.copysign(1, transaction.amount)
                per_share, total_commission = commission.calculate(transaction)
                transaction.price = transaction.price + (per_share * direction)
                transaction.commission = total_commission
        return transactions
Esempio n. 43
0
def information_ratio(algorithm_returns, benchmark_returns):
    """
    http://en.wikipedia.org/wiki/Information_ratio

    Args:
        algorithm_returns (np.array-like):
            All returns during algorithm lifetime.
        benchmark_returns (np.array-like):
            All benchmark returns during algo lifetime.

    Returns:
        float. Information ratio.
    """
    relative_returns = algorithm_returns - benchmark_returns

    relative_deviation = relative_returns.std(ddof=1)

    if zp_math.tolerant_equals(relative_deviation, 0) or \
       np.isnan(relative_deviation):
        return 0.0

    return np.mean(relative_returns) / relative_deviation
Esempio n. 44
0
def assert_float_equal(result,
                       expected,
                       path=(),
                       msg='',
                       float_rtol=10e-7,
                       float_atol=10e-7,
                       float_equal_nan=True,
                       **kwargs):
    assert tolerant_equals(
        result,
        expected,
        rtol=float_rtol,
        atol=float_atol,
        equal_nan=float_equal_nan,
    ), '%s%s != %s with rtol=%s and atol=%s%s\n%s' % (
        _fmt_msg(msg),
        result,
        expected,
        float_rtol,
        float_atol,
        (' (with nan != nan)' if not float_equal_nan else ''),
        _fmt_path(path),
    )
Esempio n. 45
0
def assert_float_equal(result,
                       expected,
                       path=(),
                       msg='',
                       float_rtol=10e-7,
                       float_atol=10e-7,
                       float_equal_nan=True,
                       **kwargs):
    assert tolerant_equals(
        result,
        expected,
        rtol=float_rtol,
        atol=float_atol,
        equal_nan=float_equal_nan,
    ), '{}{} != {} with rtol={} and atol={}{}\n{}'.format(
        _fmt_msg(msg),
        result,
        expected,
        float_rtol,
        float_atol,
        (' (with nan != nan)' if not float_equal_nan else ''),
        _fmt_path(path),
    )
Esempio n. 46
0
def assert_float_equal(result,
                       expected,
                       path=(),
                       msg='',
                       float_rtol=10e-7,
                       float_atol=10e-7,
                       float_equal_nan=True,
                       **kwargs):
    assert tolerant_equals(
        result,
        expected,
        rtol=float_rtol,
        atol=float_atol,
        equal_nan=float_equal_nan,
    ), '%s%s != %s with rtol=%s and atol=%s%s\n%s' % (
        _fmt_msg(msg),
        result,
        expected,
        float_rtol,
        float_atol,
        (' (with nan != nan)' if not float_equal_nan else ''),
        _fmt_path(path),
    )
Esempio n. 47
0
    def simulate(self, event, current_orders):

        self._volume_for_bar = 0

        txns = []
        for order in current_orders:

            open_amount = order.amount - order.filled

            if zp_math.tolerant_equals(open_amount, 0):
                continue

            order.check_triggers(event)
            if not order.triggered:
                continue

            txn = self.process_order(event, order)

            if txn:
                txns.append(txn)
                self._volume_for_bar += math.copysign(txn.amount, 1)

        return txns
Esempio n. 48
0
    def process_trade(self, trade_event):
        if trade_event.type != zp.DATASOURCE_TYPE.TRADE:
            return [], []

        if zp_math.tolerant_equals(trade_event.volume, 0):
            # there are zero volume trade_events bc some stocks trade
            # less frequently than once per minute.
            return [], []

        if trade_event.sid in self.open_orders:
            orders = self.open_orders[trade_event.sid]
            orders = sorted(orders, key=lambda o: o.dt)
            # Only use orders for the current day or before
            current_orders = filter(
                lambda o: o.dt <= trade_event.dt,
                orders)
        else:
            return [], []

        txns = self.transact(trade_event, current_orders)
        for txn in txns:
            self.orders[txn.order_id].filled += txn.amount
            # mark the date of the order to match the transaction
            # that is filling it.
            self.orders[txn.order_id].dt = txn.dt

        modified_orders = [order for order
                           in self.open_orders[trade_event.sid]
                           if order.dt == trade_event.dt]

        # update the open orders for the trade_event's sid
        self.open_orders[trade_event.sid] = \
            [order for order
             in self.open_orders[trade_event.sid]
             if order.open]

        return txns, modified_orders
Esempio n. 49
0
    def process_order(self, event, order):

        max_volume = self.volume_limit * event.volume

        # price impact accounts for the total volume of transactions
        # created against the current minute bar
        remaining_volume = max_volume - self.volume_for_bar
        if (
            remaining_volume <= 0
            or
            zp_math.tolerant_equals(remaining_volume, 0)
        ):
            # we can't fill any more transactions
            return

        # the current order amount will be the min of the
        # volume available in the bar or the open amount.
        cur_volume = min(remaining_volume, abs(order.open_amount))
        # tally the current amount into our total amount ordered.
        # total amount will be used to calculate price impact
        total_volume = self.volume_for_bar + cur_volume

        volume_share = min(total_volume / event.volume,
                           self.volume_limit)

        simulated_impact = (volume_share) ** 2 \
            * math.copysign(self.price_impact, order.direction) \
            * event.price

        return create_transaction(
            event,
            order,
            # In the future, we may want to change the next line
            # for limit pricing
            event.price + simulated_impact,
            math.copysign(cur_volume, order.direction)
        )
Esempio n. 50
0
def transact_stub(slippage, commission, event, open_orders):
    """
    This is intended to be wrapped in a partial, so that the
    slippage and commission models can be enclosed.
    """
    def inject_algo_dt(record):
        if not 'algo_dt' in record.extra:
            record.extra['algo_dt'] = event['dt']

    with Processor(inject_algo_dt).threadbound():

        transactions = slippage.simulate(event, open_orders)

        for transaction in transactions:
            if (
                transaction
                and not
                zp_math.tolerant_equals(transaction.amount, 0)
            ):
                direction = math.copysign(1, transaction.amount)
                per_share, total_commission = commission.calculate(transaction)
                transaction.price = transaction.price + (per_share * direction)
                transaction.commission = total_commission
        return transactions
Esempio n. 51
0
    def simulate(self, event, orders):

        txns = []
        for order in orders:
            # TODO: what if we have 2 orders, one for 100 shares long,
            # and one for 100 shares short
            # such as in a hedging scenario?

            order.check_triggers(event)
            if not order.triggered:
                continue

            if zp_math.tolerant_equals(order.amount, 0):
                return txns

            txn = create_transaction(
                event.sid, order.amount,
                event.price + (self.spread / 2.0 * order.direction),
                event.dt.replace(tzinfo=pytz.utc), order.id)

            # mark the date of the order to match the transaction
            order.dt = event.dt
            txns.append(txn)
        return txns
Esempio n. 52
0
def round_for_minimum_price_variation(x, is_buy, diff=(0.0095 - .005)):
    # relies on rounding half away from zero, unlike numpy's bankers' rounding
    rounded = round(x - (diff if is_buy else -diff), 2)
    if zp_math.tolerant_equals(rounded, 0.0):
        return 0.0
    return rounded
Esempio n. 53
0
def round_for_minimum_price_variation(x, is_buy, diff=(0.0095 - .005)):
    # relies on rounding half away from zero, unlike numpy's bankers' rounding
    rounded = round(x - (diff if is_buy else -diff), 2)
    if zp_math.tolerant_equals(rounded, 0.0):
        return 0.0
    return rounded