def test_iadd(mock_exchange_class, mock_order_spec_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) wallets = [Wallet(exchange, 10000 * USD), Wallet(exchange, 0 * BTC)] portfolio = Portfolio(USD, wallets) # Market order order = Order(step=0, exchange_pair=ExchangePair(exchange, USD / BTC), side=TradeSide.BUY, trade_type=TradeType.MARKET, quantity=5000.00 * USD, portfolio=portfolio, price=Decimal(7000.00)) order_spec = mock_order_spec_class.return_value assert len(order._specs) == 0 order.add_order_spec(order_spec) assert len(order._specs) == 1 assert order_spec in order._specs
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 != []
def proportion_order(portfolio: 'Portfolio', source: 'Wallet', target: 'Wallet', proportion: float) -> 'Order': """Creates an order that sends a proportion of funds from one wallet to another. Parameters ---------- portfolio : `Portfolio` The portfolio that contains both wallets. source : `Wallet` The source wallet for the funds. target : `Wallet` The target wallet for the funds. proportion : float The proportion of funds to send. """ assert 0.0 < proportion <= 1.0 exchange = source.exchange base_params = { 'step': portfolio.clock.step, 'portfolio': portfolio, 'trade_type': TradeType.MARKET, 'start': portfolio.clock.step, 'end': portfolio.clock.step + 1 } pair = None is_source_base = (source.instrument == portfolio.base_instrument) is_target_base = (target.instrument == portfolio.base_instrument) if is_source_base or is_target_base: if is_source_base: pair = source.instrument / target.instrument else: pair = target.instrument / source.instrument exchange_pair = ExchangePair(exchange, pair) balance = source.balance.as_float() size = min(balance * proportion, balance) quantity = (size * source.instrument).quantize() params = { **base_params, 'side': TradeSide.BUY if is_source_base else TradeSide.SELL, 'exchange_pair': exchange_pair, 'price': exchange_pair.price, 'quantity': quantity } return Order(**params) pair = portfolio.base_instrument / source.instrument exchange_pair = ExchangePair(exchange, pair) balance = source.balance.as_float() size = min(balance * proportion, balance) quantity = (size * source.instrument).quantize() params = { **base_params, 'side': TradeSide.SELL, 'exchange_pair': exchange_pair, 'price': exchange_pair.price, 'quantity': quantity } order = Order(**params) pair = portfolio.base_instrument / target.instrument order.add_order_spec( OrderSpec(side=TradeSide.BUY, trade_type=TradeType.MARKET, exchange_pair=ExchangePair(exchange, pair), criteria=None)) return order
def risk_managed_order(side: "TradeSide", trade_type: "TradeType", exchange_pair: "ExchangePair", price: float, quantity: "Quantity", down_percent: float, up_percent: float, portfolio: "Portfolio", start: int = None, end: int = None): """Create a stop order that manages for percentages above and below the entry price of the order. Parameters ---------- side : `TradeSide` The side of the order. trade_type : `TradeType` The type of trade to make when going in. exchange_pair : `ExchangePair` The exchange pair to perform the order for. price : float The current price. down_percent: float The percentage the price is allowed to drop before exiting. up_percent : float The percentage the price is allowed to rise before exiting. quantity : `Quantity` The quantity of the order. portfolio : `Portfolio` The portfolio being used in the order. start : int, optional The start time of the order. end : int, optional The end time of the order. Returns ------- `Order` A stop order controlling for the percentages above and below the entry price. """ side = TradeSide(side) instrument = side.instrument(exchange_pair.pair) order = Order(step=portfolio.clock.step, side=side, trade_type=TradeType(trade_type), exchange_pair=exchange_pair, price=price, start=start, end=end, quantity=quantity, portfolio=portfolio) risk_criteria = Stop("down", down_percent) ^ Stop("up", up_percent) risk_management = OrderSpec( side=TradeSide.SELL if side == TradeSide.BUY else TradeSide.BUY, trade_type=TradeType.MARKET, exchange_pair=exchange_pair, criteria=risk_criteria) order.add_order_spec(risk_management) return order
def test_complete_complex_order(mock_trade_class, mock_exchange_class): exchange = mock_exchange_class.return_value exchange.options = ExchangeOptions() exchange.id = "fake_exchange_id" exchange.name = "coinbase" exchange.clock = mock.Mock() exchange.clock.step = 0 exchange.quote_price = mock.Mock(return_value=Decimal(7000.00)) 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.execute() # Execute fake trade price = Decimal(7010.00) scale = order.price / price commission = 3.00 * USD base_size = scale * order.size - commission.size trade = mock_trade_class.return_value trade.size = Decimal(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") # Fill fake trade order.fill(trade) assert order.path_id in portfolio.get_wallet(exchange.id, USD).locked assert order.status == OrderStatus.PARTIALLY_FILLED next_order = order.complete() assert order.status == OrderStatus.FILLED assert next_order assert next_order.path_id == order.path_id assert next_order.size assert next_order.status == OrderStatus.PENDING assert next_order.side == TradeSide.SELL assert next_order.exchange_pair == ExchangePair(exchange, USD / BTC)