示例#1
0
 def test_performance_metrics(self):
     trade_fee = AddedToCostTradeFee(
         flat_fees=[TokenAmount(quote, Decimal("0"))])
     trades = [
         TradeFill(
             config_file_path="some-strategy.yml",
             strategy="pure_market_making",
             market="binance",
             symbol=trading_pair,
             base_asset=base,
             quote_asset=quote,
             timestamp=int(time.time()),
             order_id="someId0",
             trade_type="BUY",
             order_type="LIMIT",
             price=100,
             amount=10,
             trade_fee=trade_fee.to_json(),
             exchange_trade_id="someExchangeId0",
             position=PositionAction.NIL.value,
         ),
         TradeFill(
             config_file_path="some-strategy.yml",
             strategy="pure_market_making",
             market="binance",
             symbol=trading_pair,
             base_asset=base,
             quote_asset=quote,
             timestamp=int(time.time()),
             order_id="someId1",
             trade_type="SELL",
             order_type="LIMIT",
             price=120,
             amount=15,
             trade_fee=trade_fee.to_json(),
             exchange_trade_id="someExchangeId1",
             position=PositionAction.NIL.value,
         )
     ]
     cur_bals = {base: 100, quote: 10000}
     metrics = asyncio.get_event_loop().run_until_complete(
         PerformanceMetrics.create("hbot_exchange", trading_pair, trades,
                                   cur_bals))
     self.assertEqual(Decimal("200"), metrics.trade_pnl)
     print(metrics)
示例#2
0
    def test_attribute_names_for_file_export_are_valid(self):
        trade_fill = TradeFill(config_file_path=self.config_file_path,
                               strategy=self.strategy_name,
                               market=self.display_name,
                               symbol=self.symbol,
                               base_asset=self.base,
                               quote_asset=self.quote,
                               timestamp=int(time.time()),
                               order_id="OID1",
                               trade_type=TradeType.BUY.name,
                               order_type=OrderType.LIMIT.name,
                               price=Decimal(1000),
                               amount=Decimal(1),
                               leverage=1,
                               trade_fee=AddedToCostTradeFee().to_json(),
                               exchange_trade_id="EOID1",
                               position="NILL")

        values = [
            getattr(trade_fill, attribute)
            for attribute in TradeFill.attribute_names_for_file_export()
        ]

        expected_values = [
            trade_fill.exchange_trade_id,
            trade_fill.config_file_path,
            trade_fill.strategy,
            trade_fill.market,
            trade_fill.symbol,
            trade_fill.base_asset,
            trade_fill.quote_asset,
            trade_fill.timestamp,
            trade_fill.order_id,
            trade_fill.trade_type,
            trade_fill.order_type,
            trade_fill.price,
            trade_fill.amount,
            trade_fill.leverage,
            trade_fill.trade_fee,
            trade_fill.position,
        ]

        self.assertEqual(expected_values, values)
 def save_trade_fill_records(
         self, trade_price_amount_list,
         market_trading_pair_tuple: MarketTradingPairTuple, order_type,
         start_time, strategy):
     trade_records: List[TradeFill] = []
     for trade in self.create_trade_fill_records(trade_price_amount_list,
                                                 market_trading_pair_tuple,
                                                 order_type, start_time,
                                                 strategy):
         trade_records.append(TradeFill(**trade))
     self.trade_fill_sql.get_shared_session().add_all(trade_records)
示例#4
0
    def _did_fill_order(self,
                        event_tag: int,
                        market: ConnectorBase,
                        evt: OrderFilledEvent):
        if threading.current_thread() != threading.main_thread():
            self._ev_loop.call_soon_threadsafe(self._did_fill_order, event_tag, market, evt)
            return

        base_asset, quote_asset = evt.trading_pair.split("-")
        timestamp: int = int(evt.timestamp * 1e3) if evt.timestamp is not None else self.db_timestamp
        event_type: MarketEvent = self.market_event_tag_map[event_tag]
        order_id: str = evt.order_id

        with self._sql_manager.get_new_session() as session:
            with session.begin():
                # Try to find the order record, and update it if necessary.
                order_record: Optional[Order] = session.query(Order).filter(Order.id == order_id).one_or_none()
                if order_record is not None:
                    order_record.last_status = event_type.name
                    order_record.last_update_timestamp = timestamp

                # Order status and trade fill record should be added even if the order record is not found, because it's
                # possible for fill event to come in before the order created event for market orders.
                order_status: OrderStatus = OrderStatus(order_id=order_id,
                                                        timestamp=timestamp,
                                                        status=event_type.name)

                trade_fill_record: TradeFill = TradeFill(
                    config_file_path=self.config_file_path,
                    strategy=self.strategy_name,
                    market=market.display_name,
                    symbol=evt.trading_pair,
                    base_asset=base_asset,
                    quote_asset=quote_asset,
                    timestamp=timestamp,
                    order_id=order_id,
                    trade_type=evt.trade_type.name,
                    order_type=evt.order_type.name,
                    price=Decimal(
                        evt.price) if evt.price == evt.price else Decimal(0),
                    amount=Decimal(evt.amount),
                    leverage=evt.leverage if evt.leverage else 1,
                    trade_fee=evt.trade_fee.to_json(),
                    exchange_trade_id=evt.exchange_trade_id,
                    position=evt.position if evt.position else PositionAction.NIL.value,
                )
                session.add(order_status)
                session.add(trade_fill_record)
                self.save_market_states(self._config_file_path, market, session=session)

                market.add_trade_fills_from_market_recorder({TradeFillOrderDetails(trade_fill_record.market,
                                                                                   trade_fill_record.exchange_trade_id,
                                                                                   trade_fill_record.symbol)})
                self.append_to_csv(trade_fill_record)
示例#5
0
    def calculate_asset_delta_from_trades(self,
                                          analysis_start_time: int,
                                          current_startegy_name: str,
                                          market_trading_pair_tuples: List[MarketTradingPairTuple]
                                          ) -> Dict[MarketTradingPairTuple, Dict[str, Decimal]]:
        """
        Calculate spent and acquired amount for each asset from trades.
        Example:
        A buy trade of ETH_USD for price 100 and amount 1, will have 1 ETH as acquired and 100 USD as spent amount.

        :param analysis_start_time: Start timestamp for the trades to be quired
        :param current_startegy_name: Name of the currently configured strategy
        :param market_trading_pair_tuples: Current MarketTradingPairTuple
        :return: Dictionary consisting of spent and acquired amount for each assets
        """
        market_trading_pair_stats: Dict[MarketTradingPairTuple, Dict[str, Decimal]] = {}
        for market_trading_pair_tuple in market_trading_pair_tuples:
            asset_stats: Dict[str, Decimal] = {
                market_trading_pair_tuple.base_asset.upper(): {"spent": s_decimal_0, "acquired": s_decimal_0},
                market_trading_pair_tuple.quote_asset.upper(): {"spent": s_decimal_0, "acquired": s_decimal_0}
            }
            queried_trades: List[TradeFill] = TradeFill.get_trades(self.sql_manager.get_shared_session(),
                                                                   start_time=analysis_start_time,
                                                                   market=market_trading_pair_tuple.market.display_name,
                                                                   strategy=current_startegy_name)
            if not queried_trades:
                market_trading_pair_stats[market_trading_pair_tuple] = {
                    "starting_quote_rate": market_trading_pair_tuple.get_mid_price(),
                    "asset": asset_stats
                }
                continue

            for trade in queried_trades:
                # For each trade, calculate the spent and acquired amount of the corresponding base and quote asset
                trade_side: str = trade.trade_type
                base_asset: str = trade.base_asset.upper()
                quote_asset: str = trade.quote_asset.upper()
                base_delta, quote_delta = PerformanceAnalysis.calculate_trade_asset_delta_with_fees(trade)
                if trade_side == TradeType.SELL.name:
                    asset_stats[base_asset]["spent"] += base_delta
                    asset_stats[quote_asset]["acquired"] += quote_delta
                elif trade_side == TradeType.BUY.name:
                    asset_stats[base_asset]["acquired"] += base_delta
                    asset_stats[quote_asset]["spent"] += quote_delta

            market_trading_pair_stats[market_trading_pair_tuple] = {
                "starting_quote_rate": Decimal(repr(queried_trades[0].price)),
                "asset": asset_stats
            }

        return market_trading_pair_stats
示例#6
0
    async def submit_trades(self):
        try:
            trades: List[TradeFill] = await self.get_unsubmitted_trades()
            # only submit 5000 at a time
            formatted_trades: List[Dict[str, Any]] = [TradeFill.to_bounty_api_json(trade) for trade in trades[:5000]]

            if self._last_submitted_trade_timestamp >= 0 and len(formatted_trades) > 0:
                url = f"{self.LIQUIDITY_BOUNTY_REST_API}/trade"
                results = await self.authenticated_request("POST", url, json={"trades": formatted_trades})
                num_submitted = results.get("trades_submitted", 0)
                num_recorded = results.get("trades_recorded", 0)
                if num_submitted != num_recorded:
                    self.logger().warning(f"Failed to submit {num_submitted - num_recorded} trade(s)")
                if num_recorded > 0:
                    self.logger().info(f"Successfully sent {num_recorded} trade(s) to claim bounty")
        except Exception:
            raise
示例#7
0
    def _did_fill_order(self, event_tag: int, market: MarketBase,
                        evt: OrderFilledEvent):
        if threading.current_thread() != threading.main_thread():
            self._ev_loop.call_soon_threadsafe(self._did_fill_order, event_tag,
                                               market, evt)
            return

        session: Session = self.session
        base_asset, quote_asset = market.split_trading_pair(evt.trading_pair)
        timestamp: int = self.db_timestamp
        event_type: MarketEvent = self.market_event_tag_map[event_tag]
        order_id: str = evt.order_id

        # Try to find the order record, and update it if necessary.
        order_record: Optional[Order] = session.query(Order).filter(
            Order.id == order_id).one_or_none()
        if order_record is not None:
            order_record.last_status = event_type.name
            order_record.last_update_timestamp = timestamp

        # Order status and trade fill record should be added even if the order record is not found, because it's
        # possible for fill event to come in before the order created event for market orders.
        order_status: OrderStatus = OrderStatus(order_id=order_id,
                                                timestamp=timestamp,
                                                status=event_type.name)
        trade_fill_record: TradeFill = TradeFill(
            config_file_path=self.config_file_path,
            strategy=self.strategy_name,
            market=market.display_name,
            symbol=evt.trading_pair,
            base_asset=base_asset,
            quote_asset=quote_asset,
            timestamp=timestamp,
            order_id=order_id,
            trade_type=evt.trade_type.name,
            order_type=evt.order_type.name,
            price=float(evt.price) if evt.price == evt.price else 0,
            amount=float(evt.amount),
            trade_fee=TradeFee.to_json(evt.trade_fee),
            exchange_trade_id=evt.exchange_trade_id)
        session.add(order_status)
        session.add(trade_fill_record)
        self.save_market_states(self._config_file_path, market, no_commit=True)
        session.commit()
        self.append_to_csv(trade_fill_record)
示例#8
0
    def test_calculate_fees_in_quote_for_one_trade_fill_with_fees_different_tokens(
            self):
        rate_oracle = RateOracle()
        rate_oracle._prices["DAI-COINALPHA"] = Decimal("2")
        rate_oracle._prices["USDT-DAI"] = Decimal("0.9")
        RateOracle._shared_instance = rate_oracle

        performance_metric = PerformanceMetrics()
        flat_fees = [
            TokenAmount(token="USDT", amount=Decimal("10")),
            TokenAmount(token="DAI", amount=Decimal("5")),
        ]
        trade = TradeFill(
            config_file_path="some-strategy.yml",
            strategy="pure_market_making",
            market="binance",
            symbol="HBOT-COINALPHA",
            base_asset="HBOT",
            quote_asset="COINALPHA",
            timestamp=int(time.time()),
            order_id="someId0",
            trade_type="BUY",
            order_type="LIMIT",
            price=1000,
            amount=1,
            trade_fee=AddedToCostTradeFee(percent=Decimal("0.1"),
                                          percent_token="COINALPHA",
                                          flat_fees=flat_fees).to_json(),
            exchange_trade_id="someExchangeId0",
            position=PositionAction.NIL.value,
        )

        self.async_run_with_timeout(
            performance_metric._calculate_fees(quote="COINALPHA",
                                               trades=[trade]))

        expected_fee_amount = Decimal(str(trade.amount)) * Decimal(
            str(trade.price)) * Decimal("0.1")
        expected_fee_amount += flat_fees[0].amount * Decimal("0.9") * Decimal(
            "2")
        expected_fee_amount += flat_fees[1].amount * Decimal("2")
        self.assertEqual(expected_fee_amount, performance_metric.fee_in_quote)
 def get_trades(self) -> List[TradeFill]:
     trade_fee = AddedToCostTradeFee(percent=Decimal("5"))
     trades = [
         TradeFill(
             config_file_path=f"{self.mock_strategy_name}.yml",
             strategy=self.mock_strategy_name,
             market="binance",
             symbol="BTC-USDT",
             base_asset="BTC",
             quote_asset="USDT",
             timestamp=int(time.time()),
             order_id="someId",
             trade_type="BUY",
             order_type="LIMIT",
             price=1,
             amount=2,
             leverage=1,
             trade_fee=trade_fee.to_json(),
             exchange_trade_id="someExchangeId",
         )
     ]
     return trades
示例#10
0
    def test_attribute_names_for_file_export(self):
        expected_attributes = [
            "exchange_trade_id",
            "config_file_path",
            "strategy",
            "market",
            "symbol",
            "base_asset",
            "quote_asset",
            "timestamp",
            "order_id",
            "trade_type",
            "order_type",
            "price",
            "amount",
            "leverage",
            "trade_fee",
            "position",
        ]

        self.assertEqual(expected_attributes,
                         TradeFill.attribute_names_for_file_export())
示例#11
0
    def append_to_csv(self, trade: TradeFill):
        csv_filename = "trades_" + trade.config_file_path[:-4] + ".csv"
        csv_path = os.path.join(data_path(), csv_filename)

        field_names = tuple(trade.attribute_names_for_file_export())
        field_data = tuple(getattr(trade, attr) for attr in field_names)

        # adding extra field "age"
        # // indicates order is a paper order so 'n/a'. For real orders, calculate age.
        age = pd.Timestamp(int((trade.timestamp * 1e-3) - (trade.order.creation_timestamp * 1e-3)), unit='s').strftime(
            '%H:%M:%S') if (trade.order is not None and "//" not in trade.order_id) else "n/a"
        field_names += ("age",)
        field_data += (age,)

        if (os.path.exists(csv_path) and (not self._csv_matches_header(csv_path, field_names))):
            move(csv_path, csv_path[:-4] + '_old_' + pd.Timestamp.utcnow().strftime("%Y%m%d-%H%M%S") + ".csv")

        if not os.path.exists(csv_path):
            df_header = pd.DataFrame([field_names])
            df_header.to_csv(csv_path, mode='a', header=False, index=False)
        df = pd.DataFrame([field_data])
        df.to_csv(csv_path, mode='a', header=False, index=False)
示例#12
0
    def export_trades(
            self,  # type: HummingbotApplication
            path: str = ""):
        if threading.current_thread() != threading.main_thread():
            self.ev_loop.call_soon_threadsafe(self.export_trades, path)
            return

        if not path:
            fname = f"trades_{pd.Timestamp.now().strftime('%Y-%m-%d-%H-%M-%S')}.csv"
            path = join(dirname(__file__), f"../../../logs/{fname}")

        trades: List[TradeFill] = self._get_trades_from_session(self.init_time)

        if len(trades) > 0:
            try:
                df: pd.DataFrame = TradeFill.to_pandas(trades)
                df.to_csv(path, header=True)
                self._notify(f"Successfully saved trades to {path}")
            except Exception as e:
                self._notify(f"Error saving trades to {path}: {e}")
        else:
            self._notify("No past trades to export.")
示例#13
0
 def get_trades() -> List[TradeFill]:
     trade_fee = TradeFee(percent=Decimal("5"))
     trades = [
         TradeFill(
             id=1,
             config_file_path="some-strategy.yml",
             strategy="pure_market_making",
             market="binance",
             symbol="BTC-USDT",
             base_asset="BTC",
             quote_asset="USDT",
             timestamp=int(time.time()),
             order_id="someId",
             trade_type="BUY",
             order_type="LIMIT",
             price=1,
             amount=2,
             leverage=1,
             trade_fee=TradeFee.to_json(trade_fee),
             exchange_trade_id="someExchangeId",
         )
     ]
     return trades
示例#14
0
 async def export_trades(
         self,  # type: HummingbotApplication
 ):
     trades: List[TradeFill] = self._get_trades_from_session(self.init_time)
     if len(trades) == 0:
         self._notify("No past trades to export.")
         return
     self.placeholder_mode = True
     self.app.hide_input = True
     path = global_config_map["log_file_path"].value
     if path is None:
         path = DEFAULT_LOG_FILE_PATH
     file_name = await self.prompt_new_export_file_name(path)
     file_path = os.path.join(path, file_name)
     try:
         df: pd.DataFrame = TradeFill.to_pandas(trades)
         df.to_csv(file_path, header=True)
         self._notify(f"Successfully exported trades to {file_path}")
     except Exception as e:
         self._notify(f"Error exporting trades to {path}: {e}")
     self.app.change_prompt(prompt=">>> ")
     self.placeholder_mode = False
     self.app.hide_input = False
    def test_list_trades(self, notify_mock):
        global_config_map["tables_format"].value = "psql"

        captures = []
        notify_mock.side_effect = lambda s: captures.append(s)
        self.app.strategy_file_name = f"{self.mock_strategy_name}.yml"

        trade_fee = AddedToCostTradeFee(percent=Decimal("5"))
        order_id = PaperTradeExchange.random_order_id(order_side="BUY",
                                                      trading_pair="BTC-USDT")
        with self.app.trade_fill_db.get_new_session() as session:
            o = Order(
                id=order_id,
                config_file_path=f"{self.mock_strategy_name}.yml",
                strategy=self.mock_strategy_name,
                market="binance",
                symbol="BTC-USDT",
                base_asset="BTC",
                quote_asset="USDT",
                creation_timestamp=0,
                order_type="LMT",
                amount=4,
                leverage=0,
                price=3,
                last_status="PENDING",
                last_update_timestamp=0,
            )
            session.add(o)
            for i in [1, 2]:
                t = TradeFill(
                    config_file_path=f"{self.mock_strategy_name}.yml",
                    strategy=self.mock_strategy_name,
                    market="binance",
                    symbol="BTC-USDT",
                    base_asset="BTC",
                    quote_asset="USDT",
                    timestamp=i,
                    order_id=order_id,
                    trade_type="BUY",
                    order_type="LIMIT",
                    price=i,
                    amount=2,
                    leverage=1,
                    trade_fee=trade_fee.to_json(),
                    exchange_trade_id=f"someExchangeId{i}",
                )
                session.add(t)
            session.commit()

        self.app.list_trades(start_time=0)

        self.assertEqual(1, len(captures))

        creation_time_str = str(datetime.datetime.fromtimestamp(0))

        df_str_expected = (
            f"\n  Recent trades:"
            f"\n    +---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+-------+"  # noqa: E501
            f"\n    | Timestamp           | Exchange   | Market   | Order_type   | Side   |   Price |   Amount |   Leverage | Position   | Age   |"  # noqa: E501
            f"\n    |---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+-------|"  # noqa: E501
            f"\n    | {creation_time_str} | binance    | BTC-USDT | limit        | buy    |       1 |        2 |          1 | NIL        | n/a   |"  # noqa: E501
            f"\n    | {creation_time_str} | binance    | BTC-USDT | limit        | buy    |       2 |        2 |          1 | NIL        | n/a   |"  # noqa: E501
            f"\n    +---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+-------+"  # noqa: E501
        )

        self.assertEqual(df_str_expected, captures[0])