Example #1
0
def test_valid_isub():

    # Add to remove from unlocked balance
    wallet = Wallet(exchange, 10000 * USD)
    wallet.withdraw(
        quantity=500 * USD,
        reason="test"
    )
    assert wallet.balance == 9500 * USD
    assert len(wallet.locked) == 0

    # Add to balance with locked path_id
    wallet = Wallet(exchange, 10000 * USD)
    wallet.deposit(
        quantity=Quantity(USD, 750, path_id=path_id),
        reason="test"
    )
    wallet.deposit(
        quantity=Quantity(USD, 1000, path_id=other_id),
        reason="test"
    )

    wallet.withdraw(
        quantity=Quantity(USD, 500, path_id=path_id),
        reason="test"
    )
    assert wallet.balance == 10000 * USD
    assert wallet.locked[path_id] == 250 * USD

    wallet.withdraw(
        quantity=Quantity(USD, 500, path_id=other_id),
        reason="test"
    )
    assert wallet.balance == 10000 * USD
    assert wallet.locked[other_id] == 500 * USD
Example #2
0
def test_total_balance():

    wallet = Wallet(exchange, 10000 * USD)
    wallet.deposit(quantity=Quantity(USD, 500, path_id=path_id), reason="test")
    wallet.deposit(quantity=Quantity(USD, 700, path_id=other_id),
                   reason="test")

    assert wallet.total_balance == 11200 * USD
def BinancePortfolio(exchange):

    # setup instruments. Must include base currency and your crypto
    instruments = [Quantity(USD, 10000), Quantity(BTC, 1)]

    # setup wallet with instruments
    wallets = []
    for instrument in instruments:
        wallets.append(Wallet(exchange, instrument))

    # define portfolio with a wallet
    portfolio = Portfolio(base_instrument=USD, wallets=wallets)

    return portfolio
Example #4
0
def execute_close_order(order: 'Order', base_wallet: 'Wallet',
                        quote_wallet: 'Wallet', current_price: float,
                        options: 'ExchangeOptions', clock: 'Clock') -> 'Trade':
    """Executes a sell order on the exchange.

    Parameters
    ----------
    order : `Order`
        The order that is being filled.
    base_wallet : `Wallet`
        The wallet of the base instrument.
    quote_wallet : `Wallet`
        The wallet of the quote instrument.
    current_price : float
        The current price of the exchange pair.
    options : `ExchangeOptions`
        The exchange options.
    clock : `Clock`
        The clock for the trading process..

    Returns
    -------
    `Trade`
        The executed trade that was made.
    """
    if order.type == TradeType.LIMIT and order.price > current_price:
        return None

    filled = order.remaining.contain(order.exchange_pair)

    commission = Quantity(instrument=filled.instrument, size=0)
    quantity = filled - commission
    '''
    # Transfer Funds from Quote Wallet to Base Wallet
    transfer = Wallet.transfer(
        source=quote_wallet,
        target=base_wallet,
        quantity=quantity,
        commission=commission,
        exchange_pair=order.exchange_pair,
        reason="CLOSE"
    )
    '''

    # to be replaced by broker
    position = quote_wallet.get_positions()[order.path_id]
    if position:
        # mark position as closed
        position.is_open = False

    trade = Trade(order_id=order.id,
                  step=clock.step,
                  exchange_pair=order.exchange_pair,
                  side=order.side,
                  trade_type=order.type,
                  quantity=order.quantity,
                  price=order.price,
                  commission=commission)

    return trade
Example #5
0
    def total_worth_quantity(self) -> 'Quantity':
        """The total worth quantity of the wallet based on positions. (`Quantity`, read-only)"""
        total_worth_quantity = 0

        for position in self._positions.values():
            total_worth_quantity += position.get_worth_quantity()
        return Quantity(instrument=self.instrument, size=total_worth_quantity)
Example #6
0
    def locked_balance(self) -> 'Quantity':
        """The total balance of the wallet locked in orders. (`Quantity`, read-only)"""
        locked_balance = Quantity(self.instrument, 0)

        for quantity in self._locked.values():
            locked_balance += quantity.size

        return locked_balance
Example #7
0
def test_invalid_isub():

    # Add to balance with locked path_id
    wallet = Wallet(exchange, 10000 * USD)
    wallet.deposit(quantity=Quantity(USD, 500, path_id=path_id), reason="test")
    wallet.deposit(quantity=Quantity(USD, 700, path_id=other_id),
                   reason="test")

    with pytest.raises(InsufficientFunds):
        wallet.withdraw(quantity=11000 * USD, reason="test")

    with pytest.raises(InsufficientFunds):
        wallet.withdraw(quantity=Quantity(USD, 750, path_id), reason="test")

    with pytest.raises(InsufficientFunds):
        wallet.withdraw(quantity=Quantity(USD, 750, path_id), reason="test")

    with pytest.raises(IncompatibleInstrumentOperation):
        wallet.withdraw(quantity=500 * BTC, reason="test")
Example #8
0
def test_valid_deposit():

    # Add to free unlocked balance
    wallet = Wallet(exchange, 10000 * USD)
    wallet.deposit(500 * USD, reason="test")
    assert wallet.balance == 10500 * USD
    assert len(wallet.locked) == 0

    # Add to balance with locked path_id
    wallet = Wallet(exchange, 10000 * USD)
    wallet.deposit(Quantity(USD, 500, path_id=path_id), reason="test")
    assert wallet.balance == 10000 * USD
    assert wallet.locked[path_id] == 500 * USD

    # Add to more balance with locked path_id
    wallet.deposit(Quantity(USD, 500, path_id=path_id), reason="test")
    assert wallet.balance == 10000 * USD
    assert wallet.locked[path_id] == 1000 * USD

    # Add to balance that has another locked path_id
    wallet.deposit(Quantity(USD, 500, path_id=other_id), reason="test")
    assert wallet.balance == 10000 * USD
    assert wallet.locked[path_id] == 1000 * USD
    assert wallet.locked[other_id] == 500 * USD
Example #9
0
    def from_tuple(cls, wallet_tuple: 'Tuple[Exchange, Instrument, float]') -> 'Wallet':
        """Creates a wallet from a wallet tuple.

        Parameters
        ----------
        wallet_tuple : `Tuple[Exchange, Instrument, float]`
            A tuple containing an exchange, instrument, and amount.

        Returns
        -------
        `Wallet`
            A wallet corresponding to the arguments given in the tuple.
        """
        exchange, instrument, balance = wallet_tuple
        return cls(exchange, Quantity(instrument, balance))
Example #10
0
    def locked_balance(self, instrument: Instrument) -> 'Quantity':
        """Gets the total balance a specific instrument locked in orders over
        the entire portfolio.

        Parameters
        ----------
        instrument : `Instrument`
            The instrument to find locked balances for.

        Returns
        -------
        `Quantity`
            The total locked balance of the instrument.
        """
        balance = Quantity(instrument, 0)

        for (_, symbol), wallet in self._wallets.items():
            if symbol == instrument.symbol:
                balance += wallet.locked_balance

        return balance
Example #11
0
    def balance(self, instrument: Instrument) -> 'Quantity':
        """Gets the total balance of the portfolio in a specific instrument
        available for use.

        Parameters
        ----------
        instrument : `Instrument`
            The instrument to compute the balance for.

        Returns
        -------
        `Quantity`
            The balance of the instrument over all wallets.
        """
        balance = Quantity(instrument, 0)

        for (_, symbol), wallet in self._wallets.items():
            if symbol == instrument.symbol:
                balance += wallet.balance

        return balance
Example #12
0
def test_exchange_with_wallets_feed():

    ex1 = Exchange("bitfinex", service=execute_order)(
        Stream.source([7000, 7500, 8300], dtype="float").rename("USD-BTC"),
        Stream.source([200, 212, 400], dtype="float").rename("USD-ETH"))

    ex2 = Exchange("binance", service=execute_order)(
        Stream.source([7005, 7600, 8200], dtype="float").rename("USD-BTC"),
        Stream.source([201, 208, 402], dtype="float").rename("USD-ETH"),
        Stream.source([56, 52, 60], dtype="float").rename("USD-LTC"))

    wallet_btc = Wallet(ex1, 10 * BTC)
    wallet_btc_ds = _create_wallet_source(wallet_btc)

    wallet_usd = Wallet(ex2, 1000 * USD)
    wallet_usd.withdraw(quantity=400 * USD, reason="test")
    wallet_usd.deposit(quantity=Quantity(USD, 400, path_id="fake_id"),
                       reason="test")
    wallet_usd_ds = _create_wallet_source(wallet_usd, include_worth=False)

    streams = ex1.streams() + ex2.streams() + wallet_btc_ds + wallet_usd_ds
    feed = DataFeed(streams)

    assert feed.next() == {
        "bitfinex:/USD-BTC": 7000,
        "bitfinex:/USD-ETH": 200,
        "bitfinex:/BTC:/free": 10,
        "bitfinex:/BTC:/locked": 0,
        "bitfinex:/BTC:/total": 10,
        "bitfinex:/BTC:/worth": 70000,
        "binance:/USD-BTC": 7005,
        "binance:/USD-ETH": 201,
        "binance:/USD-LTC": 56,
        "binance:/USD:/free": 600,
        "binance:/USD:/locked": 400,
        "binance:/USD:/total": 1000
    }
Example #13
0
 def reset(self) -> None:
     """Resets the wallet."""
     self.balance = Quantity(self.instrument, self._initial_size).quantize()
     self._locked = {}
Example #14
0
    def transfer(source: 'Wallet', target: 'Wallet', quantity: 'Quantity',
                 commission: 'Quantity', exchange_pair: 'ExchangePair',
                 reason: str) -> 'Transfer':
        """Transfers funds from one wallet to another.

        Parameters
        ----------
        source : `Wallet`
            The wallet in which funds will be transferred from
        target : `Wallet`
            The wallet in which funds will be transferred to
        quantity : `Quantity`
            The quantity to be transferred from the source to the target.
            In terms of the instrument of the source wallet.
        commission :  `Quantity`
            The commission to be taken from the source wallet for performing
            the transfer of funds.
        exchange_pair : `ExchangePair`
            The exchange pair associated with the transfer
        reason : str
            The reason for transferring the funds.

        Returns
        -------
        `Transfer`
            A transfer object describing the transaction.

        Raises
        ------
        Exception
            Raised if an equation that describes the conservation of funds
            is broken.
        """
        quantity = quantity.quantize()
        commission = commission.quantize()

        pair = source.instrument / target.instrument
        poid = quantity.path_id

        lsb1 = source.locked.get(poid).size
        ltb1 = target.locked.get(poid, 0 * pair.quote).size

        commission = source.withdraw(commission, "COMMISSION")
        quantity = source.withdraw(quantity, "FILL ORDER")

        if quantity.instrument == exchange_pair.pair.base:
            instrument = exchange_pair.pair.quote
            converted_size = quantity.size / exchange_pair.price
        else:
            instrument = exchange_pair.pair.base
            converted_size = quantity.size * exchange_pair.price

        converted = Quantity(instrument, converted_size,
                             quantity.path_id).quantize()

        converted = target.deposit(
            converted, 'TRADED {} {} @ {}'.format(quantity, exchange_pair,
                                                  exchange_pair.price))

        lsb2 = source.locked.get(poid).size
        ltb2 = target.locked.get(poid, 0 * pair.quote).size

        q = quantity.size
        c = commission.size
        cv = converted.size
        p = exchange_pair.inverse_price if pair == exchange_pair.pair else exchange_pair.price

        source_quantization = Decimal(10)**-source.instrument.precision
        target_quantization = Decimal(10)**-target.instrument.precision

        lhs = Decimal((lsb1 - lsb2) - (q + c)).quantize(source_quantization)
        rhs = Decimal(ltb2 - ltb1 - cv).quantize(target_quantization)

        lhs_eq_zero = np.isclose(float(lhs),
                                 0,
                                 atol=float(source_quantization))
        rhs_eq_zero = np.isclose(float(rhs),
                                 0,
                                 atol=float(target_quantization))

        if not lhs_eq_zero or not rhs_eq_zero:
            equation = "({} - {}) - ({} + {}) != ({} - {}) - {}   [LHS = {}, RHS = {}, Price = {}]".format(
                lsb1, lsb2, q, c, ltb2, ltb1, cv, lhs, rhs, p)

            raise Exception("Invalid Transfer: " + equation)

        return Transfer(quantity, commission, exchange_pair.price)
Example #15
0
def execute_sell_order(order: 'Order', base_wallet: 'Wallet',
                       quote_wallet: 'Wallet', current_price: float,
                       options: 'ExchangeOptions', clock: 'Clock') -> 'Trade':
    """Executes a sell order on the exchange.

    Parameters
    ----------
    order : `Order`
        The order that is being filled.
    base_wallet : `Wallet`
        The wallet of the base instrument.
    quote_wallet : `Wallet`
        The wallet of the quote instrument.
    current_price : float
        The current price of the exchange pair.
    options : `ExchangeOptions`
        The exchange options.
    clock : `Clock`
        The clock for the trading process..

    Returns
    -------
    `Trade`
        The executed trade that was made.
    """
    if order.type == TradeType.LIMIT and order.price < current_price:
        return None

    filled = order.remaining.contain(order.exchange_pair)

    if order.type == TradeType.MARKET:
        scale = order.price / max(current_price, order.price)
        filled = scale * filled

    commission = options.commission * filled
    quantity = filled - commission

    if commission.size < Decimal(10)**-quantity.instrument.precision:
        logging.warning(
            "Commission is less than instrument precision. Canceling order. "
            "Consider defining a custom instrument with a higher precision.")
        order.cancel("COMMISSION IS LESS THAN PRECISION.")
        return None

    # Todo: Fill Order with Position-Data got by Broker
    if quantity.instrument == order.exchange_pair.pair.base:
        instrument = order.exchange_pair.pair.quote
        converted_size = quantity.size / order.price
    else:
        instrument = order.exchange_pair.pair.base
        converted_size = quantity.size * order.price
    converted = Quantity(instrument, converted_size,
                         quantity.path_id).quantize()
    position = Position(
        id=order.id,  # to be replaced by borker-id
        order=order,
        quantity=converted,
    )
    quote_wallet.add_position(position)

    transfer = Wallet.transfer(source=base_wallet,
                               target=quote_wallet,
                               quantity=quantity,
                               commission=commission,
                               exchange_pair=order.exchange_pair,
                               reason="SELL")

    trade = Trade(order_id=order.id,
                  step=clock.step,
                  exchange_pair=order.exchange_pair,
                  side=TradeSide.SELL,
                  trade_type=order.type,
                  quantity=transfer.quantity,
                  price=transfer.price,
                  commission=transfer.commission)

    return trade
Example #16
0
def test_on_fill_with_complex_order(mock_trade_class, mock_exchange_class):

    exchange = mock_exchange_class.return_value
    exchange.options.max_trade_size = 1e6
    exchange.id = "fake_exchange_id"
    exchange.name = "bitfinex"
    exchange.quote_price = lambda pair: Decimal(7000.00)

    broker = Broker()
    broker.exchanges = [exchange]

    wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)]
    portfolio = Portfolio(USD, wallets)

    side = TradeSide.BUY

    order = Order(step=0,
                  exchange_pair=ExchangePair(exchange, USD / BTC),
                  side=TradeSide.BUY,
                  trade_type=TradeType.MARKET,
                  quantity=5200.00 * USD,
                  portfolio=portfolio,
                  price=Decimal(7000.00))

    risk_criteria = Stop("down", 0.03) ^ Stop("up", 0.02)

    risk_management = OrderSpec(
        side=TradeSide.SELL if side == TradeSide.BUY else TradeSide.BUY,
        trade_type=TradeType.MARKET,
        exchange_pair=ExchangePair(exchange, USD / BTC),
        criteria=risk_criteria)

    order.add_order_spec(risk_management)

    order.attach(broker)
    order.execute()

    broker.executed[order.id] = order

    # Execute fake trade
    price = Decimal(7000.00)
    scale = order.price / price
    commission = 3.00 * USD

    base_size = scale * order.size - commission.size

    trade = mock_trade_class.return_value
    trade.order_id = order.id
    trade.size = base_size
    trade.quantity = base_size * USD
    trade.price = price
    trade.commission = commission

    base_wallet = portfolio.get_wallet(exchange.id, USD)
    quote_wallet = portfolio.get_wallet(exchange.id, BTC)

    base_size = trade.size + trade.commission.size
    quote_size = (order.price / trade.price) * (trade.size / trade.price)

    base_wallet.withdraw(quantity=Quantity(USD,
                                           size=base_size,
                                           path_id=order.path_id),
                         reason="test")
    quote_wallet.deposit(quantity=Quantity(BTC,
                                           size=quote_size,
                                           path_id=order.path_id),
                         reason="test")

    assert trade.order_id in broker.executed.keys()
    assert trade not in broker.trades
    assert broker.unexecuted == []

    order.fill(trade)

    assert order.remaining == 0
    assert trade in broker.trades[order.id]
    assert broker.unexecuted != []