Files
t_tech-gyro/app/rebalance.py

124 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(
{
"figi": pos.figi,
"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}")