示例#1
0
    def test_volume_share_slippage(self):

        slippage_model = VolumeShareSlippage()

        open_orders = [
            Order(dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                  amount=100,
                  filled=0,
                  asset=self.ASSET133)
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[0], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 1)
        _, txn = orders_txns[0]

        expected_txn = {
            'price': float(3.0001875),
            'dt': datetime.datetime(2006, 1, 5, 14, 31, tzinfo=pytz.utc),
            'amount': int(5),
            'asset': self.ASSET133,
            'commission': None,
            'type': DATASOURCE_TYPE.TRANSACTION,
            'order_id': open_orders[0].id
        }

        self.assertIsNotNone(txn)

        # TODO: Make expected_txn an Transaction object and ensure there
        # is a __eq__ for that class.
        self.assertEquals(expected_txn, txn.__dict__)

        open_orders = [
            Order(dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                  amount=100,
                  filled=0,
                  asset=self.ASSET133)
        ]

        # Set bar_data to be a minute ahead of last trade.
        # Volume share slippage should not execute when there is no trade.
        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[1], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)
示例#2
0
    def _create_order(self, order_status):
        log.info(
            'creating catalyst order from Bittrex {}'.format(order_status))
        if order_status['CancelInitiated']:
            status = ORDER_STATUS.CANCELLED
        elif order_status['Closed'] is not None:
            status = ORDER_STATUS.FILLED
        else:
            status = ORDER_STATUS.OPEN

        date = pd.to_datetime(order_status['Opened'], utc=True)
        amount = order_status['Quantity']
        filled = amount - order_status['QuantityRemaining']
        order = Order(
            dt=date,
            asset=self.assets[order_status['Exchange']],
            amount=amount,
            stop=None,  # Not yet supported by Bittrex
            limit=order_status['Limit'],
            filled=filled,
            id=order_status['OrderUuid'],
            commission=order_status['CommissionPaid'])
        order.status = status

        executed_price = order_status['PricePerUnit']

        return order, executed_price
示例#3
0
    def _create_order(self, order_status):
        """
        Create a Catalyst order object from the Exchange order dictionary
        :param order_status:
        :return: Order
        """
        # if order_status['is_cancelled']:
        #    status = ORDER_STATUS.CANCELLED
        # elif not order_status['is_live']:
        #    log.info('found executed order {}'.format(order_status))
        #    status = ORDER_STATUS.FILLED
        # else:
        status = ORDER_STATUS.OPEN

        amount = float(order_status['amount'])
        # filled = float(order_status['executed_amount'])
        filled = None

        if order_status['type'] == 'sell':
            amount = -amount
            # filled = -filled

        price = float(order_status['rate'])
        order_type = order_status['type']

        stop_price = None
        limit_price = None

        # TODO: is this comprehensive enough?
        # if order_type.endswith('limit'):
        #    limit_price = price
        # elif order_type.endswith('stop'):
        #    stop_price = price

        # executed_price = float(order_status['avg_execution_price'])
        executed_price = price

        # TODO: bitfinex does not specify comission. I could calculate it but not sure if it's worth it.
        commission = None

        # date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp']))
        # date = pytz.utc.localize(date)
        date = None

        order = Order(
            dt=date,
            asset=self.assets[order_status['symbol']],
            # No such field in Poloniex
            amount=amount,
            stop=stop_price,
            limit=limit_price,
            filled=filled,
            id=str(order_status['orderNumber']),
            commission=commission
        )
        order.status = status

        return order, executed_price
示例#4
0
    def _create_order(self, order_status):
        """
        Create a Catalyst order object from a Bitfinex order dictionary
        :param order_status:
        :return: Order
        """
        if order_status['is_cancelled']:
            status = ORDER_STATUS.CANCELLED
        elif not order_status['is_live']:
            log.info('found executed order {}'.format(order_status))
            status = ORDER_STATUS.FILLED
        else:
            status = ORDER_STATUS.OPEN

        amount = float(order_status['original_amount'])
        filled = float(order_status['executed_amount'])

        if order_status['side'] == 'sell':
            amount = -amount
            filled = -filled

        price = float(order_status['price'])
        order_type = order_status['type']

        stop_price = None
        limit_price = None

        # TODO: is this comprehensive enough?
        if order_type.endswith('limit'):
            limit_price = price
        elif order_type.endswith('stop'):
            stop_price = price

        executed_price = float(order_status['avg_execution_price'])

        # TODO: bitfinex does not specify comission.
        # I could calculate it but not sure if it's worth it.
        commission = None

        date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp']))
        date = pytz.utc.localize(date)
        order = Order(dt=date,
                      asset=self.assets[order_status['symbol']],
                      amount=amount,
                      stop=stop_price,
                      limit=limit_price,
                      filled=filled,
                      id=str(order_status['id']),
                      commission=commission)
        order.status = status

        return order, executed_price
示例#5
0
    def test_calculate_impact_without_history(self):
        model = VolatilityVolumeShare(volume_limit=1)
        late_start_asset = self.asset_finder.retrieve_asset(1000)
        early_start_asset = self.asset_finder.retrieve_asset(1001)

        cases = [
            # History will look for data before the start date.
            (pd.Timestamp('2006-01-05 11:35AM', tz='UTC'), early_start_asset),
            # Start day of the futures contract; no history yet.
            (pd.Timestamp('2006-02-10 11:35AM', tz='UTC'), late_start_asset),
            # Only a week's worth of history data.
            (pd.Timestamp('2006-02-17 11:35AM', tz='UTC'), late_start_asset),
        ]

        for minute, asset in cases:
            data = self.create_bardata(simulation_dt_func=lambda: minute)

            order = Order(dt=data.current_dt, asset=asset, amount=10)
            price, amount = model.process_order(data, order)

            avg_price = (data.current(asset, 'high') +
                         data.current(asset, 'low')) / 2
            expected_price = \
                avg_price * (1 + model.NO_DATA_VOLATILITY_SLIPPAGE_IMPACT)

            self.assertAlmostEqual(price, expected_price, delta=0.001)
            self.assertEqual(amount, 10)
示例#6
0
    def _handle_request_timeout(self, dt_before, asset, amount, is_buy, style,
                                adj_amount):
        """
        Check if an order was received during the timeout, if it appeared
        on the orders dict return it to the user.
        If an order_id was traced alone, an order is created manually
        and returned to the user. Otherwise, send none to raise an
        error and retry the call.
        :param dt_before: pd.Timestamp
        :param asset: Asset
        :param amount: float
        :param is_buy: Bool
        :param style:
        :param adj_amount: int
        :return: missing_order: Order/ None
        """
        missing_order_id, missing_order = self._fetch_missing_order(
            dt_before=dt_before, symbol=asset.asset_name)

        if missing_order_id:
            final_amount = adj_amount if amount > 0 else -adj_amount
            missing_order = Order(dt=dt_before,
                                  asset=asset,
                                  amount=final_amount,
                                  stop=style.get_stop_price(is_buy),
                                  limit=style.get_limit_price(is_buy),
                                  id=missing_order_id)
        return missing_order
示例#7
0
 def test_process_order_timeout(self):
     """
     in case of a requestTimeout make sure that the process_order method
     returns an exception so the retry method can request the trades again.
     :return:
     """
     asset = [
         pair for pair in self.exchange.assets if pair.symbol == 'eth_usdt'
     ][0]
     amount = 2
     price = 0.0025
     order = Order(dt=pd.to_datetime('2018-05-01 19:54', utc=True),
                   asset=asset,
                   amount=amount,
                   stop=None,
                   limit=price,
                   id='111')
     self.exchange.api = MagicMock(spec=[
         u'create_order', u'fetch_my_trades', u'has', u'fetch_open_orders',
         u'orders', u'fetch_closed_orders'
     ])
     self.exchange.api.has = {
         'fetchClosedOrders': 'emulated',
         'fetchOrders': False,
         'fetchMyTrades': True,
     }
     with patch('catalyst.exchange.ccxt.ccxt_exchange.CCXT.get_trades') as \
             mock_trades:
         mock_trades.side_effect = RequestTimeout
         try:
             observed_transactions = self.exchange.process_order(order)
         except ExchangeRequestError as e:
             pass
示例#8
0
    def generate_order_and_txns(self, sid, order_amount, fill_amounts):
        asset1 = self.asset_finder.retrieve_asset(sid)

        # one order
        order = Order(dt=None, asset=asset1, amount=order_amount)

        # three fills
        txn1 = Transaction(asset=asset1,
                           amount=fill_amounts[0],
                           dt=None,
                           price=100,
                           order_id=order.id)

        txn2 = Transaction(asset=asset1,
                           amount=fill_amounts[1],
                           dt=None,
                           price=101,
                           order_id=order.id)

        txn3 = Transaction(asset=asset1,
                           amount=fill_amounts[2],
                           dt=None,
                           price=102,
                           order_id=order.id)

        return order, [txn1, txn2, txn3]
示例#9
0
    def create_order(self, asset, amount, is_buy, style):
        """
        Creating order on the exchange.

        :param asset:
        :param amount:
        :param is_buy:
        :param style:
        :return:
        """
        exchange_symbol = self.get_symbol(asset)
        if isinstance(style, ExchangeLimitOrder) \
                or isinstance(style, ExchangeStopLimitOrder):
            price = style.get_limit_price(is_buy)
            order_type = 'limit'

        elif isinstance(style, ExchangeStopOrder):
            price = style.get_stop_price(is_buy)
            order_type = 'stop'

        else:
            raise InvalidOrderStyle(exchange=self.name,
                                    style=style.__class__.__name__)

        req = dict(
            symbol=exchange_symbol,
            amount=str(float(abs(amount))),
            price="{:.20f}".format(float(price)),
            side='buy' if is_buy else 'sell',
            type='exchange ' + order_type,  # TODO: support margin trades
            exchange=self.name,
            is_hidden=False,
            is_postonly=False,
            use_all_available=0,
            ocoorder=False,
            buy_price_oco=0,
            sell_price_oco=0)

        date = pd.Timestamp.utcnow()
        try:
            self.ask_request()
            response = self._request('order/new', req)
            order_status = response.json()
        except Exception as e:
            raise ExchangeRequestError(error=e)

        if 'message' in order_status:
            raise ExchangeRequestError(
                error='unable to create Bitfinex order {}'.format(
                    order_status['message']))

        order_id = str(order_status['id'])
        order = Order(dt=date,
                      asset=asset,
                      amount=amount,
                      stop=style.get_stop_price(is_buy),
                      limit=style.get_limit_price(is_buy),
                      id=order_id)

        return order
示例#10
0
    def order(self, asset, amount, style, order_id=None):
        """Place an order.

        Parameters
        ----------
        asset : catalyst.assets.Asset
            The asset that this order is for.
        amount : int
            The amount of shares to order. If ``amount`` is positive, this is
            the number of shares to buy or cover. If ``amount`` is negative,
            this is the number of shares to sell or short.
        style : catalyst.finance.execution.ExecutionStyle
            The execution style for the order.
        order_id : str, optional
            The unique identifier for this order.

        Returns
        -------
        order_id : str or None
            The unique identifier for this order, or None if no order was
            placed.

        Notes
        -----
        amount > 0 :: Buy/Cover
        amount < 0 :: Sell/Short
        Market order:    order(asset, amount)
        Limit order:     order(asset, amount, style=LimitOrder(limit_price))
        Stop order:      order(asset, amount, style=StopOrder(stop_price))
        StopLimit order: order(asset, amount, style=StopLimitOrder(limit_price,
                               stop_price))
        """
        # something could be done with amount to further divide
        # between buy by share count OR buy shares up to a dollar amount
        # numeric == share count  AND  "$dollar.cents" == cost amount

        if amount == 0:
            # Don't bother placing orders for 0 shares.
            return None
        elif amount > self.max_shares:
            # Arbitrary limit of 100 billion (US) shares will never be
            # exceeded except by a buggy algorithm.
            raise OverflowError("Can't order more than %d shares" %
                                self.max_shares)

        is_buy = (amount > 0)
        order = Order(
            dt=self.current_dt,
            asset=asset,
            amount=amount,
            stop=style.get_stop_price(is_buy),
            limit=style.get_limit_price(is_buy),
            id=order_id
        )

        self.open_orders[order.asset].append(order)
        self.orders[order.id] = order
        self.new_orders.append(order)

        return order.id
示例#11
0
    def create_order(self, asset, amount, is_buy, style):
        log.info('creating {} order'.format('buy' if is_buy else 'sell'))
        exchange_symbol = self.get_symbol(asset)

        if isinstance(style, LimitOrder) or isinstance(style, StopLimitOrder):
            if isinstance(style, StopLimitOrder):
                log.warn('{} will ignore the stop price'.format(self.name))

            price = style.get_limit_price(is_buy)
            try:
                if is_buy:
                    order_status = self.api.buylimit(exchange_symbol, amount,
                                                     price)
                else:
                    order_status = self.api.selllimit(exchange_symbol,
                                                      abs(amount), price)
            except Exception as e:
                raise ExchangeRequestError(error=e)

            if 'uuid' in order_status:
                order_id = order_status['uuid']
                order = Order(dt=pd.Timestamp.utcnow(),
                              asset=asset,
                              amount=amount,
                              stop=style.get_stop_price(is_buy),
                              limit=style.get_limit_price(is_buy),
                              id=order_id)
                return order
            else:
                raise CreateOrderError(exchange=self.name, error=order_status)
        else:
            raise InvalidOrderStyle(exchange=self.name,
                                    style=style.__class__.__name__)
示例#12
0
    def test_orders_stop(self, name, order_data, event_data, expected):
        data = order_data
        data['asset'] = self.ASSET133
        order = Order(**data)

        if expected['transaction']:
            expected['transaction']['asset'] = self.ASSET133
        event_data['asset'] = self.ASSET133

        assets = ((133,
                   pd.DataFrame(
                       {
                           'open': [event_data['open']],
                           'high': [event_data['high']],
                           'low': [event_data['low']],
                           'close': [event_data['close']],
                           'volume': [event_data['volume']],
                       },
                       index=[pd.Timestamp('2006-01-05 14:31', tz='UTC')],
                   )), )
        days = pd.date_range(start=normalize_date(self.minutes[0]),
                             end=normalize_date(self.minutes[-1]))
        with tmp_bcolz_equity_minute_bar_reader(self.trading_calendar, days,
                                                assets) as reader:
            data_portal = DataPortal(
                self.env.asset_finder,
                self.trading_calendar,
                first_trading_day=reader.first_trading_day,
                equity_minute_reader=reader,
            )

            slippage_model = VolumeShareSlippage()

            try:
                dt = pd.Timestamp('2006-01-05 14:31', tz='UTC')
                bar_data = BarData(
                    data_portal,
                    lambda: dt,
                    self.sim_params.data_frequency,
                    self.trading_calendar,
                    NoRestrictions(),
                )

                _, txn = next(
                    slippage_model.simulate(
                        bar_data,
                        self.ASSET133,
                        [order],
                    ))
            except StopIteration:
                txn = None

            if expected['transaction'] is None:
                self.assertIsNone(txn)
            else:
                self.assertIsNotNone(txn)

                for key, value in expected['transaction'].items():
                    self.assertEquals(value, txn[key])
示例#13
0
    def create_order(self, asset, amount, is_buy, style):
        symbol = self.get_symbol(asset)

        if isinstance(style, ExchangeLimitOrder):
            price = style.get_limit_price(is_buy)
            order_type = 'limit'

        elif isinstance(style, MarketOrder):
            price = None
            order_type = 'market'

        else:
            raise InvalidOrderStyle(exchange=self.name,
                                    style=style.__class__.__name__)

        side = 'buy' if amount > 0 else 'sell'

        if hasattr(self.api, 'amount_to_lots'):
            adj_amount = self.api.amount_to_lots(
                symbol=symbol,
                amount=abs(amount),
            )
            if adj_amount != abs(amount):
                log.info(
                    'adjusted order amount {} to {} based on lot size'.format(
                        abs(amount),
                        adj_amount,
                    ))
        else:
            adj_amount = abs(amount)

        try:
            result = self.api.create_order(symbol=symbol,
                                           type=order_type,
                                           side=side,
                                           amount=adj_amount,
                                           price=price)
        except ExchangeNotAvailable as e:
            log.debug('unable to create order: {}'.format(e))
            raise ExchangeRequestError(error=e)

        except InvalidOrder as e:
            log.warn('the exchange rejected the order: {}'.format(e))
            raise CreateOrderError(exchange=self.name, error=e)

        if 'info' not in result:
            raise ValueError('cannot use order without info attribute')

        final_amount = adj_amount if side == 'buy' else -adj_amount
        order_id = result['id']
        order = Order(dt=pd.Timestamp.utcnow(),
                      asset=asset,
                      amount=final_amount,
                      stop=style.get_stop_price(is_buy),
                      limit=style.get_limit_price(is_buy),
                      id=order_id)
        return order
示例#14
0
 def create_orders_dict(self, asset, last_order):
     """
     create an orders dict which mocks the .orders object
     :param asset: TradingPair
     :param last_order: bool, adds another order to the dict.
                         mocks the functionality of the fetchOrder methods
     :return: dict(Order)
     """
     orders = dict()
     orders['208612980769'] = Order(
         dt=pd.to_datetime('2018-05-01 17:34', utc=True),
         asset=asset,
         amount=2,
         stop=None,
         limit=0.0025,
         id='208612980769'
     )
     orders['656797594'] = Order(
         dt=pd.to_datetime('2018-05-01 18:34', utc=True),
         asset=asset,
         amount=1,
         stop=None,
         limit=0.0027,
         id='656797594'
     )
     orders['656797494'] = Order(
         dt=pd.to_datetime('2018-05-01 18:54', utc=True),
         asset=asset,
         amount=7,
         stop=None,
         limit=0.00246,
         id='656797494'
     )
     if last_order:
         orders['111'] = Order(
             dt=pd.to_datetime('2018-05-01 19:54', utc=True),
             asset=asset,
             amount=2,
             stop=None,
             limit=0.00254,
             id='111'
         )
     return orders
示例#15
0
    def test_create_order_timeout_open(self):
        """
        create_order method
        tests the handling of a RequestTimeout exception and locating the
        order, if was created, using the fetchOpenOrders method
        :return:
        """
        asset = [pair for pair in self.exchange.assets if
                 pair.symbol == 'eth_usdt'][0]
        amount = 2
        is_buy = True
        self.last_order = False
        price = 0.00254

        self.exchange.api = MagicMock(
            spec=[u'create_order', u'fetch_open_orders',
                  u'fetch_orders', u'orders', u'has', u'amount_to_precision'
                  ]
        )
        self.exchange.api.create_order.side_effect = RequestTimeout

        orders_dict = self.create_orders_dict(asset, self.last_order)
        self.exchange.api.orders = orders_dict
        self.exchange.api.has = {'fetchOpenOrders': True,
                                 'fetchOrders': 'emulated',
                                 'fetchClosedOrders': True
                                 }
        self.exchange.api.fetch_open_orders.side_effect = self.mod_last_order

        mock_style = create_autospec(ExchangeLimitOrder,
                                     return_value=price)
        mock_style.get_limit_price.return_value = price
        style = mock_style

        self.exchange.api.amount_to_precision = Mock(
            return_value=float(amount))

        with patch('catalyst.exchange.ccxt.ccxt_exchange.CCXT.get_symbol') as \
                mock_symbol:
            mock_symbol.return_value = 'ETH/USDT'
            observed_fetchOpen_order = self.exchange.create_order(
                asset, amount, is_buy, style)

        expected_fetchOpen_order = Order(
            dt=pd.to_datetime('2018-05-01 19:54', utc=True),
            asset=asset,
            amount=amount,
            stop=None,
            limit=price,
            id='111'
        )
        assert self.compare_orders(observed_fetchOpen_order,
                                   expected_fetchOpen_order) is True
示例#16
0
 def test_calculate_impact_buy(self):
     answer_key = [
         # We ordered 10 contracts, but are capped at 100 * 0.05 = 5
         (91485.500085168125, 5),
         (91486.500085169057, 5),
         (None, None),
     ]
     order = Order(
         dt=pd.Timestamp.now(tz='utc').round('min'),
         asset=self.ASSET,
         amount=10,
     )
     self._calculate_impact(order, answer_key)
示例#17
0
    def test_low_transaction_volume(self):
        # With a volume limit of 0.001, and a bar volume of 100, we should
        # compute a transaction volume of 100 * 0.001 = 0.1, which gets rounded
        # down to zero. In this case we expect no amount to be transacted.
        model = VolatilityVolumeShare(volume_limit=0.001)

        minute = pd.Timestamp('2006-03-01 11:35AM', tz='UTC')
        data = self.create_bardata(simulation_dt_func=lambda: minute)
        order = Order(dt=data.current_dt, asset=self.ASSET, amount=10)
        price, amount = model.process_order(data, order)

        self.assertIsNone(price)
        self.assertIsNone(amount)
示例#18
0
 def test_calculate_impact_sell(self):
     answer_key = [
         # We ordered -10 contracts, but are capped at -(100 * 0.05) = -5
         (91485.499914831875, -5),
         (91486.499914830943, -5),
         (None, None),
     ]
     order = Order(
         dt=pd.Timestamp.now(tz='utc').round('min'),
         asset=self.ASSET,
         amount=-10,
     )
     self._calculate_impact(order, answer_key)
示例#19
0
    def create_order(self, asset, amount, is_buy, style):
        """
        Creating order on the exchange.

        :param asset:
        :param amount:
        :param is_buy:
        :param style:
        :return:
        """
        exchange_symbol = self.get_symbol(asset)

        if isinstance(style, ExchangeLimitOrder) or isinstance(style,
                                                               ExchangeStopLimitOrder):
            if isinstance(style, ExchangeStopLimitOrder):
                log.warn('{} will ignore the stop price'.format(self.name))

            price = style.get_limit_price(is_buy)

            try:
                if (is_buy):
                    response = self.api.buy(exchange_symbol, amount, price)
                else:
                    response = self.api.sell(exchange_symbol, -amount, price)
            except Exception as e:
                raise ExchangeRequestError(error=e)

            date = pd.Timestamp.utcnow()

            if ('orderNumber' in response):
                order_id = str(response['orderNumber'])
                order = Order(
                    dt=date,
                    asset=asset,
                    amount=amount,
                    stop=style.get_stop_price(is_buy),
                    limit=style.get_limit_price(is_buy),
                    id=order_id
                )
                return order
            else:
                log.warn(
                    '{} order failed: {}'.format('buy' if is_buy else 'sell',
                                                 response['error']))
                return None
        else:
            raise InvalidOrderStyle(exchange=self.name,
                                    style=style.__class__.__name__)
示例#20
0
    def test_impacted_price_worse_than_limit(self):
        model = VolatilityVolumeShare(volume_limit=0.05)

        # Use all the same numbers from the 'calculate_impact' tests. Since the
        # impacted price is 59805.5, which is worse than the limit price of
        # 59800, the model should return None.
        minute = pd.Timestamp('2006-03-01 11:35AM', tz='UTC')
        data = self.create_bardata(simulation_dt_func=lambda: minute)
        order = Order(
            dt=data.current_dt,
            asset=self.ASSET,
            amount=10,
            limit=59800,
        )
        price, amount = model.process_order(data, order)

        self.assertIsNone(price)
        self.assertIsNone(amount)
示例#21
0
    def create_order(self, asset, amount, is_buy, style):
        log.info('creating {} order'.format('buy' if is_buy else 'sell'))
        exchange_symbol = self.get_symbol(asset)

        if isinstance(style, LimitOrder) or isinstance(style, StopLimitOrder):
            if isinstance(style, StopLimitOrder):
                log.warn('{} will ignore the stop price'.format(self.name))

            price = style.get_limit_price(is_buy)
            try:
                self.ask_request()
                if is_buy:
                    order_status = self.api.buylimit(exchange_symbol, amount,
                                                     price)
                else:
                    order_status = self.api.selllimit(exchange_symbol,
                                                      abs(amount), price)
            except Exception as e:
                raise ExchangeRequestError(error=e)

            if 'uuid' in order_status:
                order_id = order_status['uuid']
                order = Order(dt=pd.Timestamp.utcnow(),
                              asset=asset,
                              amount=amount,
                              stop=style.get_stop_price(is_buy),
                              limit=style.get_limit_price(is_buy),
                              id=order_id)
                return order
            else:
                if order_status == 'INSUFFICIENT_FUNDS':
                    log.warn('not enough funds to create order')
                    return None
                elif order_status == 'DUST_TRADE_DISALLOWED_MIN_VALUE_50K_SAT':
                    log.warn('Your order is too small, order at least 50K'
                             ' Satoshi')
                    return None
                else:
                    raise CreateOrderError(exchange=self.name,
                                           error=order_status)
        else:
            raise InvalidOrderStyle(exchange=self.name,
                                    style=style.__class__.__name__)
示例#22
0
    def test_volume_share_slippage_with_future(self):
        slippage_model = VolumeShareSlippage(volume_limit=1, price_impact=0.3)

        open_orders = [
            Order(
                dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                amount=10,
                filled=0,
                asset=self.ASSET1000,
            ),
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[0], )

        orders_txns = list(
            slippage_model.simulate(bar_data, self.ASSET1000, open_orders))

        self.assertEquals(len(orders_txns), 1)
        _, txn = orders_txns[0]

        # We expect to fill the order for all 10 contracts. The volume for the
        # futures contract in this bar is 100, so our volume share is:
        #     10.0 / 100 = 0.1
        # The current price is 5.0 and the price impact is 0.3, so the expected
        # impacted price is:
        #     5.0 + (5.0 * (0.1 ** 2) * 0.3) = 5.015
        expected_txn = {
            'price': 5.015,
            'dt': datetime.datetime(2006, 1, 5, 14, 31, tzinfo=pytz.utc),
            'amount': 10,
            'asset': self.ASSET1000,
            'commission': None,
            'type': DATASOURCE_TYPE.TRANSACTION,
            'order_id': open_orders[0].id,
        }

        self.assertIsNotNone(txn)
        self.assertEquals(expected_txn, txn.__dict__)
示例#23
0
    def test_prune_orders(self):
        blotter = Blotter(self.sim_params.data_frequency)

        blotter.order(self.asset_24, 100, MarketOrder())
        open_order = blotter.open_orders[self.asset_24][0]

        blotter.prune_orders([])
        self.assertEqual(1, len(blotter.open_orders[self.asset_24]))

        blotter.prune_orders([open_order])
        self.assertEqual(0, len(blotter.open_orders[self.asset_24]))

        # prune an order that isn't in our our open orders list, make sure
        # nothing blows up

        other_order = Order(
            dt=blotter.current_dt,
            asset=self.asset_25,
            amount=1
        )

        blotter.prune_orders([other_order])
示例#24
0
    def _calculate_impact(self, test_order, answer_key):
        model = VolatilityVolumeShare(volume_limit=0.05)
        first_minute = pd.Timestamp('2006-03-31 11:35AM', tz='UTC')

        next_3_minutes = self.trading_calendar.minutes_window(first_minute, 3)
        remaining_shares = test_order.open_amount

        for i, minute in enumerate(next_3_minutes):
            data = self.create_bardata(simulation_dt_func=lambda: minute)
            new_order = Order(
                dt=data.current_dt,
                asset=self.ASSET,
                amount=remaining_shares,
            )
            price, amount = model.process_order(data, new_order)

            self.assertEqual(price, answer_key[i][0])
            self.assertEqual(amount, answer_key[i][1])

            amount = amount or 0
            if remaining_shares < 0:
                remaining_shares = min(0, remaining_shares - amount)
            else:
                remaining_shares = max(0, remaining_shares - amount)
示例#25
0
 def make_order(self, amount):
     return Order(
         self.minute,
         self.asset,
         amount,
     )
示例#26
0
    def _create_order(self, order_status):
        """
        Create a Catalyst order object from a CCXT order dictionary

        Parameters
        ----------
        order_status: dict[str, Object]
            The order dict from the CCXT api.

        Returns
        -------
        Order
            The Catalyst order object

        """
        order_id = order_status['id']
        symbol = self.get_symbol(order_status['symbol'], source='ccxt')
        asset = self.get_asset(symbol)

        s = order_status['status']
        amount = order_status['amount']
        filled = order_status['filled']

        if s == 'canceled' or (s == 'closed' and filled == 0):
            status = ORDER_STATUS.CANCELLED

        elif s == 'closed' and filled > 0:
            if filled < amount:
                log.warn(
                    'order {id} is executed but only partially filled:'
                    ' {filled} {symbol} out of {amount}'.format(
                        id=order_status['status'],
                        filled=order_status['filled'],
                        symbol=asset.symbol,
                        amount=order_status['amount'],
                    )
                )
            else:
                log.info(
                    'order {id} executed in full: {filled} {symbol}'.format(
                        id=order_id,
                        filled=filled,
                        symbol=asset.symbol,
                    )
                )

            status = ORDER_STATUS.FILLED

        elif s == 'open':
            status = ORDER_STATUS.OPEN

        elif filled > 0:
            log.info(
                'order {id} partially filled: {filled} {symbol} out of '
                '{amount}, waiting for complete execution'.format(
                    id=order_id,
                    filled=filled,
                    symbol=asset.symbol,
                    amount=amount,
                )
            )
            status = ORDER_STATUS.OPEN

        else:
            log.warn(
                'invalid state {} for order {}'.format(
                    s, order_id
                )
            )
            status = ORDER_STATUS.OPEN

        if order_status['side'] == 'sell':
            amount = -amount
            filled = -filled

        price = order_status['price']
        order_type = order_status['type']

        limit_price = price if order_type == 'limit' else None

        executed_price = order_status['cost'] / order_status['amount']
        commission = order_status['fee']
        date = from_ms_timestamp(order_status['timestamp'])

        order = Order(
            dt=date,
            asset=asset,
            amount=amount,
            stop=None,
            limit=limit_price,
            filled=filled,
            id=order_id,
            commission=commission
        )
        order.status = status

        return order, executed_price
示例#27
0
    def test_create_order_timeout_trade(self):
        """
        create_order method
        tests the handling of a RequestTimeout exception and locating the
        order, if was created, using the fetchTrades method.
        checks as well, the case that the order was not created at all,
        and makes sure an exception is raised in order to retry the
        creation of the order.
        :return:
        """
        asset = [
            pair for pair in self.exchange.assets if pair.symbol == 'eth_usdt'
        ][0]
        amount = 2
        is_buy = True
        self.last_order = False
        self.last_trade = False

        price = 0.00254
        stop_price = 0.00354

        self.exchange.api = MagicMock(spec=[
            u'create_order', u'fetch_my_trades', u'has', u'fetch_open_orders',
            u'orders', u'fetch_closed_orders'
        ])
        self.exchange.api.create_order.side_effect = RequestTimeout

        orders_dict = self.create_orders_dict(asset, self.last_order)
        self.exchange.api.orders = orders_dict
        self.exchange.api.has = {
            'fetchClosedOrders': 'emulated',
            'fetchOrders': False,
            'fetchMyTrades': True,
        }
        self.exchange.api.fetch_my_trades.side_effect = self.create_trades_dict

        mock_style = create_autospec(ExchangeLimitOrder, return_value=price)
        mock_style.get_limit_price.return_value = price
        mock_style.get_stop_price.return_value = stop_price
        style = mock_style

        # check the case there are no new trades and an exception is raised
        with patch('catalyst.exchange.ccxt.ccxt_exchange.CCXT.get_symbol') as \
                mock_symbol:
            mock_symbol.return_value = 'ETH/USDT'
            try:
                observed_fetchTrade_None = self.exchange.create_order(
                    asset, amount, is_buy, style)
            except ExchangeRequestError as e:
                pass

        # check the case there are trades which form a neew order
        self.last_trade = True
        with patch('catalyst.exchange.ccxt.ccxt_exchange.CCXT.get_symbol') as \
                mock_symbol:
            mock_symbol.return_value = 'ETH/USDT'
            observed_fetchTrade_order = self.exchange.create_order(
                asset, amount, is_buy, style)

        expected_fetchTrade_order = Order(dt=pd.Timestamp.utcnow(),
                                          asset=asset,
                                          amount=amount,
                                          stop=stop_price,
                                          limit=price,
                                          id='111')
        assert self.compare_orders(observed_fetchTrade_order,
                                   expected_fetchTrade_order) is True

        # check the case there are no new trades or orders and an exception is
        # raised
        self.last_trade = False
        self.exchange.api.has['fetchOpenOrders'] = True
        with patch('catalyst.exchange.ccxt.ccxt_exchange.CCXT.get_symbol') as \
                mock_symbol:
            mock_symbol.return_value = 'ETH/USDT'
            try:
                observed_fetchTradeOrder_None = self.exchange.create_order(
                    asset, amount, is_buy, style)
            except ExchangeRequestError as e:
                pass
示例#28
0
    def create_order(self, asset, amount, is_buy, style):
        symbol = self.get_symbol(asset)

        if isinstance(style, ExchangeLimitOrder):
            price = style.get_limit_price(is_buy)
            order_type = 'limit'

        elif isinstance(style, MarketOrder):
            price = None
            order_type = 'market'

        else:
            raise InvalidOrderStyle(exchange=self.name,
                                    style=style.__class__.__name__)

        side = 'buy' if amount > 0 else 'sell'
        if hasattr(self.api, 'amount_to_lots'):
            # TODO: is this right?
            if self.api.markets is None:
                self.api.load_markets()

            # https://github.com/ccxt/ccxt/issues/1483
            adj_amount = round(abs(amount), asset.decimals)
            market = self.api.markets[symbol]
            if 'lots' in market and market['lots'] > amount:
                raise CreateOrderError(
                    exchange=self.name,
                    e='order amount lower than the smallest lot: {}'.format(
                        amount))

        else:
            adj_amount = round(abs(amount), asset.decimals)

        before_order_dt = pd.Timestamp.utcnow()
        try:
            result = self.api.create_order(symbol=symbol,
                                           type=order_type,
                                           side=side,
                                           amount=adj_amount,
                                           price=price)
        except InvalidOrder as e:
            log.warn('the exchange rejected the order: {}'.format(e))
            raise CreateOrderError(exchange=self.name, error=e)

        except RequestTimeout as e:
            log.info('received a RequestTimeout exception while creating '
                     'an order on {} / {}\n Checking if an order was filled '
                     'during the timeout'.format(self.name, symbol))

            missing_order = self._handle_request_timeout(
                before_order_dt, asset, amount, is_buy, style, adj_amount)
            if missing_order is None:
                # no order was found
                log.warn(
                    'no order was identified during timeout exception.'
                    'Please double check for inconsistency with the exchange. '
                    'We encourage you to report any issue on GitHub: '
                    'https://github.com/enigmampc/catalyst/issues')
                raise ExchangeRequestError(error=e)
            else:
                return missing_order

        except (ExchangeError, NetworkError) as e:
            log.warn('unable to create order {} / {}: {}'.format(
                self.name, symbol, e))
            raise ExchangeRequestError(error=e)

        exchange_amount = None
        if 'amount' in result and result['amount'] != adj_amount:
            exchange_amount = result['amount']

        elif 'info' in result:
            if 'origQty' in result['info']:
                exchange_amount = float(result['info']['origQty'])

        if exchange_amount:
            log.info('order amount adjusted by {} from {} to {}'.format(
                self.name, adj_amount, exchange_amount))
            adj_amount = exchange_amount

        if 'info' not in result:
            raise ValueError('cannot use order without info attribute')

        final_amount = adj_amount if side == 'buy' else -adj_amount
        order_id = result['id']
        order = Order(dt=pd.Timestamp.utcnow(),
                      asset=asset,
                      amount=final_amount,
                      stop=style.get_stop_price(is_buy),
                      limit=style.get_limit_price(is_buy),
                      id=order_id)
        return order
示例#29
0
    def _create_order(self, order_status):
        """
        Create a Catalyst order object from a CCXT order dictionary

        Parameters
        ----------
        order_status: dict[str, Object]
            The order dict from the CCXT api.

        Returns
        -------
        Order
            The Catalyst order object

        """
        order_id = order_status['id']
        symbol = self.get_symbol(order_status['symbol'], source='ccxt')
        asset = self.get_asset(symbol)

        s = order_status['status']
        amount = order_status['amount']
        filled = order_status['filled']

        if s == 'canceled' or (s == 'closed' and filled == 0):
            status = ORDER_STATUS.CANCELLED

        elif s == 'closed' and filled > 0:
            if filled < amount:
                log.warn(
                    'order {id} is executed but only partially filled:'
                    ' {filled} {symbol} out of {amount}'.format(
                        id=order_status['status'],
                        filled=order_status['filled'],
                        symbol=asset.symbol,
                        amount=order_status['amount'],
                    )
                )
            else:
                log.info(
                    'order {id} executed in full: {filled} {symbol}'.format(
                        id=order_id,
                        filled=filled,
                        symbol=asset.symbol,
                    )
                )

            status = ORDER_STATUS.FILLED

        elif s == 'open':
            status = ORDER_STATUS.OPEN

        elif filled > 0:
            log.info(
                'order {id} partially filled: {filled} {symbol} out of '
                '{amount}, waiting for complete execution'.format(
                    id=order_id,
                    filled=filled,
                    symbol=asset.symbol,
                    amount=amount,
                )
            )
            status = ORDER_STATUS.OPEN

        else:
            log.warn(
                'invalid state {} for order {}'.format(
                    s, order_id
                )
            )
            status = ORDER_STATUS.OPEN

        if order_status['side'] == 'sell':
            amount = -amount
            filled = -filled

        price = order_status['price']
        order_type = order_status['type']

        limit_price = price if order_type == 'limit' else None

        executed_price = order_status['cost'] / order_status['amount']
        commission = order_status['fee']
        date = from_ms_timestamp(order_status['timestamp'])

        order = Order(
            dt=date,
            asset=asset,
            amount=amount,
            stop=None,
            limit=limit_price,
            filled=filled,
            id=order_id,
            commission=commission
        )
        order.status = status

        return order, executed_price
示例#30
0
    def test_orders_limit(self):
        slippage_model = VolumeShareSlippage()
        slippage_model.data_portal = self.data_portal

        # long, does not trade
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': 100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'limit': 3.5
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[3], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        # long, does not trade - impacted price worse than limit price
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': 100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'limit': 3.5
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[3], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        # long, does trade
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': 100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'limit': 3.6
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[3], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 1)
        txn = orders_txns[0][1]

        expected_txn = {
            'price': float(3.50021875),
            'dt': datetime.datetime(2006, 1, 5, 14, 34, tzinfo=pytz.utc),
            # we ordered 100 shares, but default volume slippage only allows
            # for 2.5% of the volume.  2.5% * 2000 = 50 shares
            'amount': int(50),
            'asset': self.ASSET133,
            'order_id': open_orders[0].id
        }

        self.assertIsNotNone(txn)

        for key, value in expected_txn.items():
            self.assertEquals(value, txn[key])

        # short, does not trade
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': -100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'limit': 3.5
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[0], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        # short, does not trade - impacted price worse than limit price
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': -100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'limit': 3.5
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[0], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        # short, does trade
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': -100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'limit': 3.4
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[1], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 1)
        _, txn = orders_txns[0]

        expected_txn = {
            'price': float(3.49978125),
            'dt': datetime.datetime(2006, 1, 5, 14, 32, tzinfo=pytz.utc),
            'amount': int(-50),
            'asset': self.ASSET133,
        }

        self.assertIsNotNone(txn)

        for key, value in expected_txn.items():
            self.assertEquals(value, txn[key])
示例#31
0
    def test_orders_stop_limit(self):
        slippage_model = VolumeShareSlippage()
        slippage_model.data_portal = self.data_portal

        # long, does not trade
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': 100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'stop': 4.0,
                    'limit': 3.0
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[2], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[3], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        # long, does not trade - impacted price worse than limit price
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': 100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'stop': 4.0,
                    'limit': 3.5
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[2], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[3], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        # long, does trade
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': 100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'stop': 4.0,
                    'limit': 3.6
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[2], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[3], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 1)
        _, txn = orders_txns[0]

        expected_txn = {
            'price': float(3.50021875),
            'dt': datetime.datetime(2006, 1, 5, 14, 34, tzinfo=pytz.utc),
            'amount': int(50),
            'asset': self.ASSET133
        }

        for key, value in expected_txn.items():
            self.assertEquals(value, txn[key])

        # short, does not trade

        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': -100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'stop': 3.0,
                    'limit': 4.0
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[0], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[1], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        # short, does not trade - impacted price worse than limit price
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': -100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'stop': 3.0,
                    'limit': 3.5
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[0], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[1], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        # short, does trade
        open_orders = [
            Order(
                **{
                    'dt': datetime.datetime(
                        2006, 1, 5, 14, 30, tzinfo=pytz.utc),
                    'amount': -100,
                    'filled': 0,
                    'asset': self.ASSET133,
                    'stop': 3.0,
                    'limit': 3.4
                })
        ]

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[0], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 0)

        bar_data = self.create_bardata(
            simulation_dt_func=lambda: self.minutes[1], )

        orders_txns = list(
            slippage_model.simulate(
                bar_data,
                self.ASSET133,
                open_orders,
            ))

        self.assertEquals(len(orders_txns), 1)
        _, txn = orders_txns[0]

        expected_txn = {
            'price': float(3.49978125),
            'dt': datetime.datetime(2006, 1, 5, 14, 32, tzinfo=pytz.utc),
            'amount': int(-50),
            'asset': self.ASSET133,
        }

        for key, value in expected_txn.items():
            self.assertEquals(value, txn[key])