async def inventory_price_prompt( self, # type: HummingbotApplication config_map, input_value=None, ): key = "inventory_price" if input_value: config_map[key].value = Decimal(input_value) else: exchange = config_map["exchange"].value market = config_map["market"].value base_asset, quote_asset = market.split("-") if exchange.endswith("paper_trade"): balances = global_config_map[ "paper_trade_account_balance"].value else: balances = await UserBalances.instance().balances( exchange, base_asset, quote_asset) if balances.get(base_asset) is None: return cvar = ConfigVar( key="temp_config", prompt= f"On {exchange}, you have {balances[base_asset]:.4f} {base_asset}. " f"What was the price for this amount in {quote_asset}? >>> ", required_if=lambda: True, type_str="decimal", validator=lambda v: validate_decimal( v, min_value=Decimal("0"), inclusive=True), ) await self.prompt_a_config(cvar) config_map[key].value = cvar.value try: quote_volume = balances[base_asset] * cvar.value except TypeError: # TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'NoneType' - bad input / no input self._notify("Inventory price not updated due to bad input") return with self.trade_fill_db.get_new_session() as session: with session.begin(): InventoryCost.add_volume( session, base_asset=base_asset, quote_asset=quote_asset, base_volume=balances[base_asset], quote_volume=quote_volume, overwrite=True, )
def test_process_order_fill_event_buy(self): amount = Decimal("1") price = Decimal("9000") event = OrderFilledEvent( timestamp=1, order_id="order1", trading_pair=self.trading_pair, trade_type=TradeType.BUY, order_type=OrderType.LIMIT, price=price, amount=amount, trade_fee=AddedToCostTradeFee(percent=Decimal("0"), flat_fees=[]), ) # first event creates DB record self.delegate.process_order_fill_event(event) with self.trade_fill_sql.get_new_session() as session: count = session.query(InventoryCost).count() self.assertEqual(count, 1) # second event causes update to existing record self.delegate.process_order_fill_event(event) record = InventoryCost.get_record(session, self.base_asset, self.quote_asset) self.assertEqual(record.base_volume, amount * 2) self.assertEqual(record.quote_volume, price * 2)
def process_order_fill_event(self, fill_event: OrderFilledEvent) -> None: base_asset, quote_asset = fill_event.trading_pair.split("-") quote_volume = fill_event.amount * fill_event.price base_volume = fill_event.amount for fee_asset, fee_amount in fill_event.trade_fee.flat_fees: if fill_event.trade_type == TradeType.BUY: if fee_asset == base_asset: base_volume -= fee_amount elif fee_asset == quote_asset: quote_volume += fee_amount else: # Ok, some other asset used (like BNB), assume that we paid in base asset for simplicity base_volume /= 1 + fill_event.trade_fee.percent else: if fee_asset == base_asset: base_volume += fee_amount elif fee_asset == quote_asset: # TODO: with new logic, this quote volume adjustment does not impacts anything quote_volume -= fee_amount else: # Ok, some other asset used (like BNB), assume that we paid in base asset for simplicity base_volume /= 1 + fill_event.trade_fee.percent with self.sql_manager.get_new_session() as session: with session.begin(): if fill_event.trade_type == TradeType.SELL: record = InventoryCost.get_record(session, base_asset, quote_asset) if not record: raise RuntimeError( "Sold asset without having inventory price set. This should not happen." ) # We're keeping initial buy price intact. Profits are not changing inventory price intentionally. quote_volume = -(Decimal(record.quote_volume / record.base_volume) * base_volume) base_volume = -base_volume InventoryCost.add_volume(session, base_asset, quote_asset, base_volume, quote_volume)
def test_get_price_by_type_zero_division(self): # Test for situation when position was fully closed with profit amount = Decimal("0") record = InventoryCost( base_asset=self.base_asset, quote_asset=self.quote_asset, base_volume=amount, quote_volume=amount, ) self._session.add(record) self._session.commit() self.assertIsNone(self.delegate.get_price())
def get_price(self) -> Optional[Decimal]: record = InventoryCost.get_record(self._session, self.base_asset, self.quote_asset) if record is None or record.base_volume is None or record.base_volume is None: return None try: price = record.quote_volume / record.base_volume except InvalidOperation: # decimal.InvalidOperation: [<class 'decimal.DivisionUndefined'>] - both volumes are 0 return None return Decimal(price)
def get_price(self) -> Optional[Decimal]: record = InventoryCost.get_record(self._session, self.base_asset, self.quote_asset) if record is None or record.base_volume is None or record.base_volume is None: return None try: price = record.quote_volume / record.base_volume except InvalidOperation: # use both volume = 0 return None return Decimal(price)
def test_process_order_fill_event_sell(self): amount = Decimal("1") price = Decimal("9000") # Test when no records self.assertIsNone(self.delegate.get_price()) with self.trade_fill_sql.get_new_session() as session: with session.begin(): record = InventoryCost( base_asset=self.base_asset, quote_asset=self.quote_asset, base_volume=amount, quote_volume=amount * price, ) session.add(record) amount_sell = Decimal("0.5") price_sell = Decimal("10000") event = OrderFilledEvent( timestamp=1, order_id="order1", trading_pair=self.trading_pair, trade_type=TradeType.SELL, order_type=OrderType.LIMIT, price=price_sell, amount=amount_sell, trade_fee=AddedToCostTradeFee(percent=Decimal("0"), flat_fees=[]), ) self.delegate.process_order_fill_event(event) with self.trade_fill_sql.get_new_session() as session: record = InventoryCost.get_record(session, self.base_asset, self.quote_asset) # Remaining base volume reduced by sold amount self.assertEqual(record.base_volume, amount - amount_sell) # Remaining quote volume has been reduced using original price self.assertEqual(record.quote_volume, amount_sell * price)
def process_order_fill_event(self, fill_event: OrderFilledEvent) -> None: base_asset, quote_asset = fill_event.trading_pair.split("-") quote_volume = fill_event.amount * fill_event.price base_volume = fill_event.amount for fee_asset, fee_amount in fill_event.trade_fee.flat_fees: if fill_event.trade_type == TradeType.BUY: if fee_asset == base_asset: base_volume -= fee_amount elif fee_asset == quote_asset: quote_volume += fee_amount else: # used for exchange native token used as base fee . base_volume /= 1 + fill_event.trade_fee.percent else: if fee_asset == base_asset: base_volume += fee_amount elif fee_asset == quote_asset: # volume adjustment with new logic. quote_volume -= fee_amount else: base_volume /= 1 + fill_event.trade_fee.percent if fill_event.trade_type == TradeType.SELL: record = InventoryCost.get_record(self._session, base_asset, quote_asset) if not record: raise RuntimeError( "Sold asset without having inventory price set. This should not happen." ) # keeping initial buy price intact. Profits are not changing inventory price intentionally. quote_volume = -(Decimal(record.quote_volume / record.base_volume) * base_volume) base_volume = -base_volume InventoryCost.add_volume(self._session, base_asset, quote_asset, base_volume, quote_volume)
def test_get_price_by_type_zero_division(self): # Test for situation when position was fully closed with profit amount = Decimal("0") with self.trade_fill_sql.get_new_session() as session: with session.begin(): record = InventoryCost( base_asset=self.base_asset, quote_asset=self.quote_asset, base_volume=amount, quote_volume=amount, ) session.add(record) self.assertIsNone(self.delegate.get_price())
def get_price(self) -> Optional[Decimal]: with self.sql_manager.get_new_session() as session: with session.begin(): record = InventoryCost.get_record(session, self.base_asset, self.quote_asset) if record is None or record.base_volume is None or record.base_volume is None: return None try: price = record.quote_volume / record.base_volume except InvalidOperation: return None return Decimal(price)
def test_get_price_by_type(self): amount = Decimal("1") price = Decimal("9000") # Test when no records self.assertIsNone(self.delegate.get_price()) record = InventoryCost( base_asset=self.base_asset, quote_asset=self.quote_asset, base_volume=amount, quote_volume=amount * price, ) self._session.add(record) self._session.commit() delegate_price = self.delegate.get_price() self.assertEqual(delegate_price, price)
def test_get_price_by_type(self): amount = Decimal("1") price = Decimal("9000") # Test when no records self.assertIsNone(self.delegate.get_price()) with self.trade_fill_sql.get_new_session() as session: with session.begin(): record = InventoryCost( base_asset=self.base_asset, quote_asset=self.quote_asset, base_volume=amount, quote_volume=amount * price, ) session.add(record) delegate_price = self.delegate.get_price() self.assertEqual(delegate_price, price)