docstr: Calculation of trade info
  **NOTE**: Order will be changed in this function
  :param order:
  :param position: Position
  :param dealt_order_amount: the dealt order amount dict with the format of {stock_id: float}
  :return: trade_price, trade_val, trade_cost
docstr_tok: ["Calculation","of","trade","info","*","*","NOTE","*","*",":","Order","will","be","changed","in","this","function",":","param","order",":",":","param","position",":","Position",":","param","dealt_order_amount",":","the","dealt","order","amount","dict","with","the","format","of","{","stock_id",":","float","}",":","return",":","trade_price",",","trade_val",",","trade_cost"]
code: def _calc_trade_info_by_order(
      self,
      order: Order,
      position: Optional[BasePosition],
      dealt_order_amount: dict,
  ) -> Tuple[float, float, float]:
      """
      Calculation of trade info
      **NOTE**: Order will be changed in this function
      :param order:
      :param position: Position
      :param dealt_order_amount: the dealt order amount dict with the format of {stock_id: float}
      :return: trade_price, trade_val, trade_cost
      """
      trade_price = cast(
          float,
          self.get_deal_price(
              order.stock_id,
              order.start_time,
              order.end_time,
              direction=order.direction,
          ),
      )
      total_trade_val = (
          cast(
              float,
              self.get_volume(
                  order.stock_id, order.start_time, order.end_time
              ),
          )
          * trade_price
      )
      order.factor = self.get_factor(
          order.stock_id, order.start_time, order.end_time
      )
      order.deal_amount = (
          order.amount
      )  # set to full amount and clip it step by step
      # Clipping amount first
      # - It simulates that the order is rejected directly by the exchange due to large order
      # Another choice is placing it after rounding the order
      # - It simulates that the large order is submitted, but partial is dealt regardless of rounding by trading unit.
      self._clip_amount_by_volume(order, dealt_order_amount)
  
      # TODO: the adjusted cost ratio can be overestimated as deal_amount will be clipped in the next steps
      trade_val = order.deal_amount * trade_price
      if not total_trade_val or np.isnan(total_trade_val):
          # TODO: assert trade_val == 0, f"trade_val != 0, total_trade_val: {total_trade_val}; order info: {order}"
          adj_cost_ratio = self.impact_cost
      else:
          adj_cost_ratio = (
              self.impact_cost * (trade_val / total_trade_val) ** 2
          )
  
      if order.direction == Order.SELL:
          cost_ratio = self.close_cost + adj_cost_ratio
          # sell
          # if we don't know current position, we choose to sell all
          # Otherwise, we clip the amount based on current position
          if position is not None:
              current_amount = (
                  position.get_stock_amount(order.stock_id)
                  if position.check_stock(order.stock_id)
                  else 0
              )
              if not np.isclose(order.deal_amount, current_amount):
                  # when not selling last stock. rounding is necessary
                  order.deal_amount = self.round_amount_by_trade_unit(
                      min(current_amount, order.deal_amount),
                      order.factor,
                  )
  
              # in case of negative value of cash
              if (
                  position.get_cash() + order.deal_amount * trade_price
                  < max(
                      order.deal_amount * trade_price * cost_ratio,
                      self.min_cost,
                  )
              ):
                  order.deal_amount = 0
                  self.logger.debug(
                      f"Order clipped due to cash limitation: {order}"
                  )
  
      elif order.direction == Order.BUY:
          cost_ratio = self.open_cost + adj_cost_ratio
          # buy
          if position is not None:
              cash = position.get_cash()
              trade_val = order.deal_amount * trade_price
              if cash < max(trade_val * cost_ratio, self.min_cost):
                  # cash cannot cover cost
                  order.deal_amount = 0
                  self.logger.debug(
                      f"Order clipped due to cost higher than cash: {order}"
                  )
              elif cash < trade_val + max(
                  trade_val * cost_ratio, self.min_cost
              ):
                  # The money is not enough
                  max_buy_amount = self._get_buy_amount_by_cash_limit(
                      trade_price, cash, cost_ratio
                  )
                  order.deal_amount = self.round_amount_by_trade_unit(
                      min(max_buy_amount, order.deal_amount),
                      order.factor,
                  )
                  self.logger.debug(
                      f"Order clipped due to cash limitation: {order}"
                  )
              else:
                  # The money is enough
                  order.deal_amount = self.round_amount_by_trade_unit(
                      order.deal_amount, order.factor
                  )
          else:
              # Unknown amount of money. Just round the amount
              order.deal_amount = self.round_amount_by_trade_unit(
                  order.deal_amount, order.factor
              )
  
      else:
          raise NotImplementedError(
              "order direction {} error".format(order.direction)
          )
  
      trade_val = order.deal_amount * trade_price
      trade_cost = max(trade_val * cost_ratio, self.min_cost)
      if trade_val <= 1e-5:
          # if dealing is not successful, the trade_cost should be zero.
          trade_cost = 0
      return trade_price, trade_val, trade_cost
  
imports: from __future__ import annotations
  from collections import defaultdict
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union, cast
  from ..utils.index_data import IndexData
  import random
  import numpy
  import pandas
  from qlib.backtest.position import BasePosition
  from ..config import C
  from ..constant import REG_CN, REG_TW
  from ..data.data import D
  from ..log import get_module_logger
  from .decision import Order, OrderDir, OrderHelper
  from .high_performance_ds import BaseQuote, NumpyQuote
label_desc: Use image node_id 25 for calling the Exchange obj's underlying member method code with example usage: obj._calc_trade_info_by_order(order, position, dealt_order_amount) and returns: trade_price,  trade_val,  trade_cost