#!/usr/bin/env python3 import os import sys import logging import argparse from dotenv import load_dotenv logging.getLogger("t_tech.invest._grpc_helpers").setLevel(logging.ERROR) from t_tech.invest import Client from t_tech.invest.utils import quotation_to_decimal as to_dec load_dotenv() def _account_type(t) -> str: return {0: "неизвестно", 1: "Брокер", 2: "ИИС", 3: "Копилка", 4: "Фонд"}.get(t.value, f"тип:{t.value}") def _account_status(s) -> str: return {0: "неизвестно", 1: "новый", 2: "открыт", 3: "закрыт"}.get(s.value, f"статус:{s.value}") def cmd_accounts(client, _args): resp = client.users.get_accounts() if not resp.accounts: print("Счета не найдены.") return print(f"\n{'ID':<45} {'Тип':<12} {'Статус':<10} Название") print("─" * 90) for acc in resp.accounts: print(f"{acc.id:<45} {_account_type(acc.type):<12} {_account_status(acc.status):<10} {acc.name}") print() def cmd_search(client, args): resp = client.instruments.find_instrument(query=args.query) if not resp.instruments: print(f"Ничего не найдено по запросу '{args.query}'") return instruments = resp.instruments[: args.limit] total = len(resp.instruments) print(f"\nНайдено {total}, показано {len(instruments)}:\n") print(f"{'Тикер':<14} {'Тип':<10} {'Лот':>5} {'Название':<42} UID") print("─" * 110) for i in instruments: name = (i.name[:40] + "..") if len(i.name) > 40 else i.name print(f"{i.ticker:<14} {i.instrument_type:<10} {i.lot:>5} {name:<42} {i.uid}") print() def cmd_yaml(client, args): resp = client.instruments.find_instrument(query=args.query) if not resp.instruments: print(f"Ничего не найдено по запросу '{args.query}'") return instruments = resp.instruments[: args.limit] if len(instruments) == 1: instr = instruments[0] else: print(f"\nНайдено {len(resp.instruments)} результат(ов):\n") for idx, i in enumerate(instruments): name = (i.name[:42] + "..") if len(i.name) > 42 else i.name print(f" [{idx}] {i.ticker:<14} {i.instrument_type:<10} {name}") print() try: choice = int(input("Введите номер: ")) instr = instruments[choice] except (ValueError, IndexError): print("Неверный выбор.") return print(f"\n# Вставьте в config/target.yaml → portfolio:") print(f" {instr.ticker}:") print(f' name: "{instr.name}"') print(f' figi: "{instr.figi}"') print(f' uid: "{instr.uid}"') print(f' weight: 0.00 # TODO: укажите целевой вес') print() def cmd_portfolio(client, args): account_id = args.account_id or os.getenv("TINVEST_ACCOUNT_ID") if not account_id: print("TINVEST_ACCOUNT_ID не задан. Используйте --account или добавьте в .env") print("Подсказка: запустите 'accounts', чтобы узнать ID счёта.") sys.exit(1) resp = client.operations.get_portfolio(account_id=account_id) total = to_dec(resp.total_amount_portfolio) positions = sorted( resp.positions, key=lambda p: to_dec(p.current_price) * to_dec(p.quantity), reverse=True, ) print(f"\nСтоимость портфеля: {total:.2f} RUB\n") print(f"{'Тикер':<14} {'Тип':<12} {'Кол-во':>10} {'Цена':>12} {'Сумма':>14} {'Доля':>7} UID") print("─" * 115) for pos in positions: qty = to_dec(pos.quantity) price = to_dec(pos.current_price) value = qty * price share = float(value / total * 100) if total else 0 print( f"{pos.ticker:<14} {pos.instrument_type:<12} {qty:>10.2f} " f"{price:>12.4f} {value:>14.2f} {share:>6.2f}% {pos.instrument_uid}" ) print() def main(): parser = argparse.ArgumentParser( description="Утилита поиска инструментов T-Invest", formatter_class=argparse.RawDescriptionHelpFormatter, epilog="""\ примеры: python tools/get_instrument.py accounts python tools/get_instrument.py search TMOS@ python tools/get_instrument.py search "Тинькофф" -n 5 python tools/get_instrument.py yaml TMOS@ python tools/get_instrument.py portfolio python tools/get_instrument.py portfolio --account """, ) parser.add_argument("--token", help="Токен T-Invest (переопределяет TINVEST_TOKEN из .env)") sub = parser.add_subparsers(dest="cmd", required=True) sub.add_parser("accounts", help="Список счетов и их ID") p_search = sub.add_parser("search", help="Поиск инструментов по тикеру, ISIN или названию") p_search.add_argument("query", help="Тикер, ISIN или часть названия") p_search.add_argument("-n", "--limit", type=int, default=10, metavar="N", help="Макс. результатов (по умолчанию: 10)") p_yaml = sub.add_parser("yaml", help="Сгенерировать сниппет для config/target.yaml") p_yaml.add_argument("query", help="Тикер, ISIN или часть названия") p_yaml.add_argument("-n", "--limit", type=int, default=8, metavar="N", help="Макс. результатов для выбора (по умолчанию: 8)") p_port = sub.add_parser("portfolio", help="Текущие позиции портфеля") p_port.add_argument("--account", dest="account_id", metavar="ID", help="ID счёта (переопределяет TINVEST_ACCOUNT_ID из .env)") args = parser.parse_args() token = args.token or os.getenv("TINVEST_TOKEN") if not token: print("Ошибка: TINVEST_TOKEN не задан. Передайте --token или добавьте в .env") sys.exit(1) with Client(token=token) as client: {"accounts": cmd_accounts, "search": cmd_search, "yaml": cmd_yaml, "portfolio": cmd_portfolio}[args.cmd](client, args) if __name__ == "__main__": main()