mirror of
https://github.com/ada-dmitry/etf-gyro.git
synced 2026-09-24 07:30:14 +00:00
159 lines
6.3 KiB
Python
159 lines
6.3 KiB
Python
#!/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 <id>
|
|
""",
|
|
)
|
|
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()
|