Refactor code structure for improved readability and maintainability

This commit is contained in:
Dmitry
2026-01-03 15:20:57 +03:00
commit 90295d21e6
10 changed files with 498 additions and 0 deletions

122
app/rebalance.py Normal file
View File

@@ -0,0 +1,122 @@
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}")