from decimal import Decimal from t_tech.invest import OrderDirection, OrderType from t_tech.invest.utils import quotation_to_decimal as to_dec import uuid class RebalanceBot: def __init__( self, client, account_id: str, target_weights: dict, corridor: Decimal, dry_run=True, ): self.client = client self.account_id = account_id self.target_weights = target_weights self.corridor = corridor self.portfolio_value = Decimal("0") self.instrument_cache = {} self.dry_run = dry_run def fetch_portfolio(self): account = self.client.operations.get_portfolio(account_id=self.account_id) self.portfolio_value = to_dec(account.total_amount_portfolio) return account.positions def get_instrument_data(self, instrument_uid): if instrument_uid not in self.instrument_cache: resp = self.client.instruments.find_instrument(query=instrument_uid) self.instrument_cache[instrument_uid] = resp.instruments[0] return self.instrument_cache[instrument_uid] def calculate_rebalance(self, positions): plan = [] for pos in positions: ticker = pos.ticker uid = pos.instrument_uid asset_type = pos.instrument_type price = to_dec(pos.current_price) qty = to_dec(pos.quantity) if asset_type == "currency": continue elif asset_type == "bond": instrument_info = self.get_instrument_data(uid) nkd = to_dec(pos.current_nkd) current_value = (price + nkd) * qty else: instrument_info = self.get_instrument_data(uid) current_value = price * qty target_weight = self.target_weights.get(ticker, Decimal("0")) current_weight = current_value / self.portfolio_value delta = target_weight - current_weight money_delta = delta * self.portfolio_value print( f"{ticker}, Тип: {asset_type}, Текущая доля {current_weight:.2%}, Цель {target_weight:.2%}, Дельта: {delta:.2%}, Дельта в рублях: {money_delta:.6}" ) if abs(delta) > self.corridor: money_to_trade = self.portfolio_value * delta one_lot_price = price * instrument_info.lot lots = int(money_to_trade / one_lot_price) if lots != 0: plan.append( { "ticker": ticker, "uid": uid, "action": "BUY" if lots > 0 else "SELL", "lots": abs(lots), "delta_pct": delta * 100, } ) print( f"{ticker:12} | Доля: {current_weight:6.2%} | Цель: {target_weight:6.2%}" ) return plan def execute_orders(self, trades, group_name): if not trades: return print(f"\n--- Исполнение блока: {group_name} ---") for trade in trades: action_ru = "КУПИТЬ" if trade["action"] == "BUY" else "ПРОДАТЬ" if self.dry_run: print( f"[СИМУЛЯЦИЯ] {action_ru} {trade['ticker']}: {trade['lots']} лотов" ) continue try: direction = ( OrderDirection.ORDER_DIRECTION_BUY if trade["action"] == "BUY" else OrderDirection.ORDER_DIRECTION_SELL ) response = self.client.orders.post_order( figi=trade["figi"], quantity=int(trade["lots"]), direction=direction, account_id=self.account_id, order_type=OrderType.ORDER_TYPE_MARKET, order_id=str(uuid.uuid4()), ) print( f"[ИСПОЛНЕНО]\n{trade['ticker']} на {trade['lots']} лотов. ID: {response.order_id}" ) except Exception as e: print(f"[ОШИБКА]\nПри сделке с {trade['ticker']}: {e}")