Инициализация публичного репозитория

This commit is contained in:
Dmitry
2026-04-24 20:30:33 +03:00
commit 0c6e1591d6
15 changed files with 1082 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
TINVEST_TOKEN=t.***
TINVEST_ACCOUNT_ID=your_account_id
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
TELEGRAM_USER_ID=your_telegram_user_id
+40
View File
@@ -0,0 +1,40 @@
name: Weekly Portfolio Rebalance
on:
# 1. Запуск по расписанию (каждый понедельник в 10:30 МСК)
schedule:
- cron: '30 7 * * 1' # Время в UTC (7:30 UTC = 10:30 МСК)
# 2. Возможность запустить вручную кнопкой в интерфейсе GitHub
workflow_dispatch:
inputs:
dry_run:
description: 'Режим симуляции (без реальных сделок)'
required: true
type: boolean
default: true
jobs:
rebalance:
runs-on: ubuntu-latest
environment: tinvest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Set up Python
run: uv python install 3.13
- name: Run Rebalance Bot
env:
# Передаем секреты в переменные окружения скрипта
TINVEST_TOKEN: ${{ secrets.TINVEST_TOKEN }}
TINVEST_ACCOUNT_ID: ${{ secrets.TINVEST_ACCOUNT_ID }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_USER_ID: ${{ secrets.TELEGRAM_USER_ID }}
DRY_RUN_OVERRIDE: ${{ github.event.inputs.dry_run }}
run: uv run main.py
+14
View File
@@ -0,0 +1,14 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
.env
# IDE / AI assistant config
.claude/
+1
View File
@@ -0,0 +1 @@
3.13
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Dmitry
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+168
View File
@@ -0,0 +1,168 @@
# etf-gyro
Бот для автоматического ребалансирования инвестиционного портфеля через API T-Invest (Т-Инвестиции).
Бот рассчитан на балансировку ETF-портфеля (т.е. с использованием паевых фондов для гибкости долей), но при большом портфеле можно использовать и для иных фин. инструментов.
## Возможности
- Расчёт текущих долей активов и сравнение с целевыми весами
- Автоматическое планирование покупок и продаж при отклонении от цели
- Коридор допустимого отклонения — мелкие расхождения игнорируются
- Автоматическая продажа активов, не входящих в конфиг
- Распределение избытка рублёвого кэша пропорционально целевым весам
- Режим dry-run — расчёт плана без реальных сделок
- Уведомления в Telegram с отчётом по каждому запуску
## Требования
- Python 3.13+
- Токен T-Invest API
- Telegram-бот
## Установка
```bash
git clone https://github.com/ada-dmitry/etf-gyro.git
cd etf-gyro
pip install -r requirements.txt
```
Или через `uv`:
```bash
uv sync
```
## Конфигурация
Скопируй `.env.example` в `.env` и заполни:
```env
TINVEST_TOKEN=t.your_token_here
TINVEST_ACCOUNT_ID=your_account_id
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
TELEGRAM_USER_ID=your_telegram_user_id
```
- **TINVEST_TOKEN** — токен T-Invest API: [Настройки → Токены](https://www.tbank.ru/invest/settings/)
- **TINVEST_ACCOUNT_ID** — ID счёта; узнать через `python tools/get_instrument.py accounts`
- **TELEGRAM_BOT_TOKEN** — создать бота через [@BotFather](https://t.me/botfather)
- **TELEGRAM_USER_ID** — узнать через [@userinfobot](https://t.me/userinfobot)
## Настройка портфеля
Отредактируй `config/target.yaml`:
```yaml
portfolio:
TMOS@:
name: "Крупнейшие компании РФ"
figi: "TCS60A101X76"
uid: "f509af83-6e71-462f-901f-bcb073f6773b"
weight: 0.98
RUB:
name: "Рублевый кэш"
figi: "RUB000UTSTOM"
uid: "a92e2e25-a698-45cc-a781-167cf465257c"
weight: 0.02
corridor: 0.02
```
- `uid` — уникальный идентификатор инструмента в T-Invest; найти через `tools/get_instrument.py yaml <ticker>`
- `weight` — целевая доля (0.0–1.0); сумма всех весов должна быть ≈ 1.0
- `corridor` — порог отклонения, ниже которого сделка не создаётся (0.02 = 2%)
- `RUB` — специальный тикер для кэша; избыток сверх `weight` автоматически распределяется по остальным активам
## Запуск
Утилиту можно запускать локально, если хочется контроля над операциями (сначала прогнать на dry-run, а потом уже на реальных сделках).
```bash
# Dry-run — только расчёт плана, без реальных сделок
DRY_RUN_OVERRIDE=true python main.py
# С реальными сделками (по умолчанию)
python main.py
```
## Автоматизация через cron
Для запуска на сервере (VPS, HomeLab и др.) задание в crontab:
```bash
crontab -e
```
```cron
# Ребалансировка каждый понедельник в 10:30
30 10 * * 1 cd /path/to/etf-gyro && /path/to/uv run main.py >> /var/log/gyro.log 2>&1
```
Пути к `uv` и проекту — абсолютные, иначе cron не найдёт их в своём окружении. Узнать путь к `uv`: `which uv`.
Переменные окружения cron не наследует из шелла, поэтому `.env` файл должен быть заполнен — скрипт читает его через `python-dotenv`.
Проверить, что задание отработало:
```bash
tail -f /var/log/gyro.log
```
## Автоматизация через GitHub Actions
Если своего сервера нет или хочется держать на стабильной инфраструктуре, то можно использовать GitHub Actions.
Воркфлоу `.github/workflows/rebalance.yml` запускает ребалансировку каждый понедельник в 10:30 МСК.
Добавь в настройках репозитория → **Settings → Environments** окружение `tinvest` и пропиши секреты:
```
TINVEST_TOKEN
TINVEST_ACCOUNT_ID
TELEGRAM_BOT_TOKEN
TELEGRAM_USER_ID
```
Запуск вручную доступен через **Actions → Weekly Portfolio Rebalance → Run workflow**, с возможностью включить режим симуляции.
## Утилита поиска инструментов
`tools/get_instrument.py` — CLI для работы с T-Invest API без запуска основного бота:
```bash
# Список счетов и их ID
python tools/get_instrument.py accounts
# Поиск инструмента по тикеру, ISIN или названию
python tools/get_instrument.py search TMOS@
python tools/get_instrument.py search "Тинькофф" -n 5
# Готовый сниппет для вставки в config/target.yaml
python tools/get_instrument.py yaml TMOS@
# Текущие позиции портфеля с долями и стоимостью
python tools/get_instrument.py portfolio
python tools/get_instrument.py portfolio --account <id>
```
## Структура проекта
```
etf-gyro/
├── main.py # Точка входа
├── config/
│ └── target.yaml # Целевые веса активов и коридор
├── app/
│ ├── load_config.py # Загрузка конфига из YAML
│ ├── notifier.py # Telegram-уведомления
│ └── rebalance.py # Логика ребалансирования
└── tools/
└── get_instrument.py # CLI: search / yaml / accounts / portfolio
```
## Лицензия
MIT
+38
View File
@@ -0,0 +1,38 @@
import yaml
from decimal import Decimal
from typing import Dict, Any
def load_config(config_path: str = "config/target.yaml") -> Dict[str, Any]:
with open(config_path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
# Результирующий словарь для портфеля
portfolio = {}
corridor = Decimal(str(data.get("corridor", 0)))
# Проходим по всем инструментам в блоке portfolio
for ticker, info in data.get("portfolio", {}).items():
# Важно: переводим weight в Decimal сразу при чтении
portfolio[ticker] = {
"name": info.get("name"),
"figi": info.get("figi"),
"uid": info.get("uid"),
"weight": Decimal(str(info.get("weight", 0))),
}
# Проверка суммы весов
total_weight = sum(item["weight"] for item in portfolio.values())
if abs(total_weight - Decimal("1.0")) > Decimal("0.0001"):
print(
f"⚠️ ВНИМАНИЕ: Сумма весов в конфиге = {total_weight}. Проверьте коэффициенты!"
)
return portfolio, corridor # type: ignore
if __name__ == "__main__":
config, corridor = load_config()
for ticker, info in config.items(): # type: ignore
print(f"{ticker}: {info}")
print(f"Corridor: {corridor}")
+27
View File
@@ -0,0 +1,27 @@
import logging
from typing import Optional
import requests
class Notifier:
def __init__(self, token: str):
self.token = token
self.api_url = f"https://api.telegram.org/bot{token}"
self.logger = logging.getLogger(__name__)
def send_message(self, user_id: int, text: str) -> Optional[dict]:
try:
url = f"{self.api_url}/sendMessage"
payload = {
"chat_id": user_id,
"text": text,
"parse_mode": "HTML",
"disable_web_page_preview": True,
}
response = requests.post(url, json=payload, timeout=10)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
self.logger.error(f"Failed to send message to {user_id}: {e}")
return None
+223
View File
@@ -0,0 +1,223 @@
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, target_config) -> tuple[list[dict], str]:
plan = []
plan_report = "\n<b>📊 План ребаланса</b>\n"
allowed_uids = {info["uid"] for info in target_config.values()}
current_values = {}
current_prices = {}
for pos in positions:
# Пропускаем кэш (рубли) только при проверке лишних активов,
# т.к. кэш учитывается в target_config как RUB
if pos.instrument_type == "currency":
continue
if pos.instrument_uid not in allowed_uids:
ticker = pos.ticker
qty = to_dec(pos.quantity)
if qty > 0:
# Получаем данные об инструменте для определения лотности
instr_info = self.get_instrument_data(pos.instrument_uid)
# В Тинькофф продажа идет в лотах
lots_to_sell = int(qty / instr_info.lot)
if lots_to_sell > 0:
print(
f"🗑️ ЛИШНИЙ АКТИВ: {ticker} (нет в конфиге). Продаем {lots_to_sell} лотов."
)
plan_report += f"• <b>SELL</b> <code>{ticker}</code>: {lots_to_sell} лотов <i>(не в конфиге)</i>\n"
plan.append(
{
"ticker": ticker,
"figi": pos.figi,
"action": "SELL",
"lots": lots_to_sell,
"reason": "not_in_config",
}
)
current_map = {pos.instrument_uid: pos for pos in positions}
for ticker, info in target_config.items():
uid = info["uid"]
target_weight = info["weight"]
pos = current_map.get(uid)
if pos is None:
current_weight = Decimal("0")
current_value = Decimal("0")
else:
price = to_dec(pos.current_price)
qty = to_dec(pos.quantity)
current_value = price * qty
current_weight = current_value / self.portfolio_value
if ticker != "RUB":
current_prices[ticker] = price
current_values[ticker] = current_value
delta = target_weight - current_weight
money_delta = delta * self.portfolio_value
print(
f"{ticker}, Текущая доля {current_weight:.2%}, Цель {target_weight:.2%}, Дельта: {delta:.2%}, Дельта в рублях: {money_delta:.6}"
)
if abs(delta) > self.corridor:
# Для RUB не создаём ордера, только выводим информацию
if ticker == "RUB":
if delta > 0:
plan_report += f"• ⚠️ <b>Недостаток кэша</b>: нужно {abs(delta * self.portfolio_value):.2f} руб. (уменьшите покупки)\n"
else:
plan_report += f"• 💰 <b>Избыток кэша</b>: {abs(delta * self.portfolio_value):.2f} руб. доступно для инвестиций\n"
else:
money_to_trade = self.portfolio_value * delta
instrument_info = self.get_instrument_data(uid)
one_lot_price = price * instrument_info.lot
lots = int(money_to_trade / one_lot_price)
if lots != 0:
plan.append(
{
"figi": instrument_info.figi,
"ticker": ticker,
"uid": uid,
"action": "BUY" if lots > 0 else "SELL",
"lots": abs(lots),
"delta_pct": delta * 100,
}
)
plan_report += f"• <b>{'BUY' if lots > 0 else 'SELL'}</b> <code>{ticker}</code>: {abs(lots)} лотов; на сумму <code>{money_to_trade:.2f} руб.</code>\n"
print(
f"{ticker:12} | Доля: {current_weight:6.2%} | Цель: {target_weight:6.2%}"
)
# Инвестируем избыток кэша, даже если остальные отклонения в пределах коридора
if "RUB" in target_config and "RUB" in current_values:
target_cash = self.portfolio_value * target_config["RUB"]["weight"]
cash_excess = current_values["RUB"] - target_cash
if cash_excess > 0:
plan_report += f"• 💰 <b>Избыток кэша</b>: {cash_excess:.2f} руб. будет распределён по целям\n"
non_rub_weights = {
t: info["weight"]
for t, info in target_config.items()
if t != "RUB"
}
total_non_rub_weight = sum(non_rub_weights.values())
if total_non_rub_weight > 0:
for ticker, weight in non_rub_weights.items():
uid = target_config[ticker]["uid"]
share = weight / total_non_rub_weight
money_to_trade = cash_excess * share
price = current_prices.get(ticker)
if price is None or price <= 0:
plan_report += f"• ⚠️ <b>Пропуск</b> <code>{ticker}</code>: нет цены для расчёта лотов\n"
continue
instrument_info = self.get_instrument_data(uid)
one_lot_price = price * instrument_info.lot
lots = int(money_to_trade / one_lot_price)
if lots > 0:
plan.append(
{
"figi": instrument_info.figi,
"ticker": ticker,
"uid": uid,
"action": "BUY",
"lots": lots,
"delta_pct": Decimal("0"),
"reason": "cash_excess",
}
)
plan_report += f"• <b>BUY</b> <code>{ticker}</code>: {lots} лотов; на сумму <code>{money_to_trade:.2f} руб.</code> <i>(излишек кэша)</i>\n"
plan_report += "\n<i>— Конец плана ребаланса —</i>\n"
return plan, plan_report
def execute_orders(self, trades, group_name) -> str:
execute_report = f"\n<b>⚙️ Исполнение: {group_name}</b>\n"
if not trades:
execute_report += "<i>Нет сделок для исполнения.</i>\n"
return execute_report
print(f"\n--- Исполнение блока: {group_name} ---")
for trade in trades:
action_ru = "КУПИТЬ" if trade["action"] == "BUY" else "ПРОДАТЬ"
if self.dry_run:
execute_report += f"• <i>Симуляция</i>: <b>{action_ru}</b> <code>{trade['ticker']}</code> — {trade['lots']} лотов\n"
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()),
)
execute_report += f"• ✅ <b>{action_ru}</b> <code>{trade['ticker']}</code> — {trade['lots']} лотов. <i>ID:</i> <code>{response.order_id}</code>\n"
print(
f"[ИСПОЛНЕНО]\n{trade['ticker']} на {trade['lots']} лотов. ID: {response.order_id}"
)
except Exception as e:
execute_report += f"• ❌ <b>Ошибка</b> по <code>{trade['ticker']}</code>: <code>{e}</code>\n"
print(f"[ОШИБКА]\nПри сделке с {trade['ticker']}: {e}")
return execute_report
+14
View File
@@ -0,0 +1,14 @@
portfolio:
TMOS@:
name: "Крупнейшие компании РФ"
figi: "TCS60A101X76"
uid: "f509af83-6e71-462f-901f-bcb073f6773b"
weight: 0.98
RUB:
name: "Рублевый кэш"
figi: "RUB000UTSTOM"
uid: "a92e2e25-a698-45cc-a781-167cf465257c"
weight: 0.02
corridor: 0.02
+62
View File
@@ -0,0 +1,62 @@
import os
from dotenv import load_dotenv
from t_tech.invest import Client
from t_tech.invest.utils import quotation_to_decimal as to_dec
from app.rebalance import RebalanceBot
from app.load_config import load_config
from app.notifier import Notifier
load_dotenv()
def main():
report = "<b>🚀 Запуск бота ребаланса портфеля</b>\n"
tg = Notifier(token=os.getenv("TELEGRAM_BOT_TOKEN")) # type: ignore
config, corridor = load_config()
with Client(token=os.getenv("TINVEST_TOKEN")) as client: # type: ignore
bot = RebalanceBot(
client=client,
account_id=os.getenv("TINVEST_ACCOUNT_ID"), # type: ignore
target_weights=config, # type: ignore
corridor=corridor, # type: ignore
dry_run=os.getenv("DRY_RUN_OVERRIDE", "false").lower() == "true",
)
pos = bot.fetch_portfolio()
print(pos)
plan, plan_report = bot.calculate_rebalance(pos, bot.target_weights)
report += plan_report
report += "\n<i>——————————————</i>\n"
sales = [trade for trade in plan if trade["action"] == "SELL"]
buys = [trade for trade in plan if trade["action"] == "BUY"]
sales_report = bot.execute_orders(sales, "ПРОДАЖИ")
report += sales_report
report += "\n<i>——————————————</i>\n"
buys_report = bot.execute_orders(buys, "ПОКУПКИ")
report += buys_report
report += "\n<i>——————————————</i>\n"
pos_after = bot.fetch_portfolio()
cash = next(
(
p
for p in pos_after
if p.instrument_type == "currency" and p.ticker == "RUB000UTSTOM"
),
None,
)
cash_amount = to_dec(cash.quantity) # type: ignore
report += f"\n<b>💰 Остаток кэша после ребаланса:</b> <code>{cash_amount:.2f} RUB</code>\n"
tg.send_message(user_id=int(os.getenv("TELEGRAM_USER_ID")), text=report) # type: ignore
if __name__ == "__main__":
main()
# with Client(token=os.getenv("TINVEST_TOKEN")) as client:
# print(client.users.get_accounts())
+12
View File
@@ -0,0 +1,12 @@
[project]
name = "t-tech-gyro"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"dotenv>=0.9.9",
"pyyaml>=6.0.3",
"requests>=2.32.5",
"t-tech-investments>=0.3.3",
]
+4
View File
@@ -0,0 +1,4 @@
t-tech-investments
pyyaml
dotenv
requests
+158
View File
@@ -0,0 +1,158 @@
#!/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()
Generated
+296
View File
@@ -0,0 +1,296 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "cachetools"
version = "5.5.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" },
]
[[package]]
name = "certifi"
version = "2025.11.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
]
[[package]]
name = "deprecation"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788, upload-time = "2020-04-20T14:23:38.738Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" },
]
[[package]]
name = "dotenv"
version = "0.9.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "python-dotenv" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
]
[[package]]
name = "grpcio"
version = "1.76.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" },
{ url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" },
{ url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" },
{ url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" },
{ url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" },
{ url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" },
{ url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" },
{ url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" },
{ url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" },
{ url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" },
{ url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417, upload-time = "2025-10-21T16:22:15.02Z" },
{ url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219, upload-time = "2025-10-21T16:22:17.954Z" },
{ url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826, upload-time = "2025-10-21T16:22:20.721Z" },
{ url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550, upload-time = "2025-10-21T16:22:23.637Z" },
{ url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564, upload-time = "2025-10-21T16:22:26.016Z" },
{ url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236, upload-time = "2025-10-21T16:22:28.362Z" },
{ url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795, upload-time = "2025-10-21T16:22:31.075Z" },
{ url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214, upload-time = "2025-10-21T16:22:33.831Z" },
{ url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961, upload-time = "2025-10-21T16:22:36.468Z" },
{ url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462, upload-time = "2025-10-21T16:22:39.772Z" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "protobuf"
version = "4.25.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/df/01/34c8d2b6354906d728703cb9d546a0e534de479e25f1b581e4094c4a85cc/protobuf-4.25.8.tar.gz", hash = "sha256:6135cf8affe1fc6f76cced2641e4ea8d3e59518d1f24ae41ba97bcad82d397cd", size = 380920, upload-time = "2025-05-28T14:22:25.153Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/45/ff/05f34305fe6b85bbfbecbc559d423a5985605cad5eda4f47eae9e9c9c5c5/protobuf-4.25.8-cp310-abi3-win32.whl", hash = "sha256:504435d831565f7cfac9f0714440028907f1975e4bed228e58e72ecfff58a1e0", size = 392745, upload-time = "2025-05-28T14:22:10.524Z" },
{ url = "https://files.pythonhosted.org/packages/08/35/8b8a8405c564caf4ba835b1fdf554da869954712b26d8f2a98c0e434469b/protobuf-4.25.8-cp310-abi3-win_amd64.whl", hash = "sha256:bd551eb1fe1d7e92c1af1d75bdfa572eff1ab0e5bf1736716814cdccdb2360f9", size = 413736, upload-time = "2025-05-28T14:22:13.156Z" },
{ url = "https://files.pythonhosted.org/packages/28/d7/ab27049a035b258dab43445eb6ec84a26277b16105b277cbe0a7698bdc6c/protobuf-4.25.8-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ca809b42f4444f144f2115c4c1a747b9a404d590f18f37e9402422033e464e0f", size = 394537, upload-time = "2025-05-28T14:22:14.768Z" },
{ url = "https://files.pythonhosted.org/packages/bd/6d/a4a198b61808dd3d1ee187082ccc21499bc949d639feb948961b48be9a7e/protobuf-4.25.8-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:9ad7ef62d92baf5a8654fbb88dac7fa5594cfa70fd3440488a5ca3bfc6d795a7", size = 294005, upload-time = "2025-05-28T14:22:16.052Z" },
{ url = "https://files.pythonhosted.org/packages/d6/c6/c9deaa6e789b6fc41b88ccbdfe7a42d2b82663248b715f55aa77fbc00724/protobuf-4.25.8-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:83e6e54e93d2b696a92cad6e6efc924f3850f82b52e1563778dfab8b355101b0", size = 294924, upload-time = "2025-05-28T14:22:17.105Z" },
{ url = "https://files.pythonhosted.org/packages/0c/c1/6aece0ab5209981a70cd186f164c133fdba2f51e124ff92b73de7fd24d78/protobuf-4.25.8-py3-none-any.whl", hash = "sha256:15a0af558aa3b13efef102ae6e4f3efac06f1eea11afb3a57db2901447d9fb59", size = 156757, upload-time = "2025-05-28T14:22:24.135Z" },
]
[[package]]
name = "python-dateutil"
version = "2.9.0.post0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
]
[[package]]
name = "python-dotenv"
version = "1.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "sentry-sdk"
version = "2.48.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/40/f0/0e9dc590513d5e742d7799e2038df3a05167cba084c6ca4f3cdd75b55164/sentry_sdk-2.48.0.tar.gz", hash = "sha256:5213190977ff7fdff8a58b722fb807f8d5524a80488626ebeda1b5676c0c1473", size = 384828, upload-time = "2025-12-16T14:55:41.722Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/19/8d77f9992e5cbfcaa9133c3bf63b4fbbb051248802e1e803fed5c552fbb2/sentry_sdk-2.48.0-py2.py3-none-any.whl", hash = "sha256:6b12ac256769d41825d9b7518444e57fa35b5642df4c7c5e322af4d2c8721172", size = 414555, upload-time = "2025-12-16T14:55:40.152Z" },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
]
[[package]]
name = "t-tech-gyro"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "dotenv" },
{ name = "pyyaml" },
{ name = "requests" },
{ name = "t-tech-investments" },
]
[package.metadata]
requires-dist = [
{ name = "dotenv", specifier = ">=0.9.9" },
{ name = "pyyaml", specifier = ">=6.0.3" },
{ name = "requests", specifier = ">=2.32.5" },
{ name = "t-tech-investments", specifier = ">=0.3.3" },
]
[[package]]
name = "t-tech-investments"
version = "0.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cachetools" },
{ name = "deprecation" },
{ name = "grpcio" },
{ name = "protobuf" },
{ name = "python-dateutil" },
{ name = "sentry-sdk" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/1d/7744d4cded9416725e22f94352fc8080527d2f6924a26690b49c691d8c24/t_tech_investments-0.3.3.tar.gz", hash = "sha256:0657271e79c518bb5796614eea5a940587d66fee5db0eeb8a377a2a3c047820b", size = 227231, upload-time = "2025-12-30T06:55:19.32Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/41/ca4f7b8985c74035744313af8af999d82e5793f8f3fc676b7580dadc9653/t_tech_investments-0.3.3-py3-none-any.whl", hash = "sha256:cdcce4e608d43e63b8dd73765dc831cb1022f64eb7ea7e8e53968b1b280aafcd", size = 260460, upload-time = "2025-12-30T06:55:17.6Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "urllib3"
version = "2.6.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
]