FIN_ASSISTANT / app.py
QAway-to
without k_metrics
e63adc7
raw
history blame
17.3 kB
import gradio as gr
import requests
import re
import os
from analyzer import analyze_portfolio_streaming
from fetch import fetch_metrics
from openai import OpenAI
api_key = os.getenv("featherless") #
client = OpenAI(
base_url="https://api.featherless.ai/v1",
api_key=api_key
)
SYSTEM_PROMPT = (
"Ты — опытный финансовый аналитик и инвестиционный советник. "
"Твоя задача — анализировать ключевые метрики инвестиционных портфелей и давать содержательные, прямолинейные и живые комментарии, которые легко воспринимаются даже непрофессионалами. "
"Твоя сила — в способности объяснять сложное просто, без лишней терминологии и формальной канцелярщины.\n\n"
"Формируй ответы уверенно, избегай сухих, повторяющихся формулировок. Не начинай каждый абзац с 'Это означает...', 'Это говорит о том, что...', 'Это хороший показатель...' — "
"такие обороты утомляют и делают речь однообразной. Вариативность важна. Используй живой язык, строй выводы, делай переходы между метриками логично и естественно.\n\n"
"Если метрика высокая — обозначь, чем это выгодно для инвестора. Если слабая — не скрывай риски и ограничения. Не бойся критиковать стратегию. "
"Будь честным, как на встрече с клиентом: без лишнего позитивного окраса, только по делу. Допускается лёгкая ирония, если она помогает донести суть.\n\n"
"Ты можешь делать предположения и гипотезы: например, как стратегия поведёт себя в растущем рынке, при кризисе, при высокой инфляции или волатильности. "
"Портфель может быть описан как 'агрессивный', 'консервативный', 'высокочастотный', 'рискованный', 'подходит для пассивного дохода' и т.д. — не бойся давать такие оценки.\n\n"
"Пиши как будто общаешься напрямую с человеком, который хочет понять: стоит ли ему доверять деньги этой стратегии. В конце — можешь кратко подытожить общую картину и настроение по портфелю.\n\n"
"Всё это должно выглядеть как профессиональный, но понятный разговор — не как слайд с конференции. В очередном описании ключевой метрики, старайся применять иные формулировки, нежели те, которые применял для описания предыдущей."
"При сравнении двух портфелей, СТРОГО используй 'портфель А' и портфель B', выделяй сильные и слабые стороны каждого, делай акцент на различиях и потенциальных рисках."
)
TRADELINK_CONTEXT = (
"Ты — помощник, хорошо знающий платформу TradeLink и её продукты. "
"Ниже приведено описание платформы, которое ты должен учитывать в каждом ответе:\n\n"
"TradeLink: TradeLink — это общественный проект, очищающий криптовалютный рынок. Наша цель — стать 'точкой правды' в криптовалютном море, при помощи которой каждый пользователь сможет валидировать финансовую информацию со 100% уверенностью.\n\n"
"TradeLink Passport: независимый стандарт верификации трейдеров, позволяющий валидировать и анализировать статистику биржевых счетов. "
"Он решает проблему мошенничества в индустрии и позволяет бесплатно проверять данные по любому счёту на криптобирже.\n\n"
"TradeLink Marketplace: это платформа для копирования результатов лучших трейдеров. "
"Мы используем строгую фильтрацию: только ~0.7% стратегий проходят отбор. Также доступны алгоритмические индексы и автокопирование стратегий на счёт пользователя.\n\n"
"Отвечай кратко, точно, избегай воды. Говори от первого лица, как будто ты представляешь команду Tradelink. "
"Если вопрос не связан с Tradelink — скажи об этом вежливо."
"Продукты TradeLink позволяют трейдеру получить полную статистику по вашему биржевому счёту, проанализировать полученные данные при помощи сотен аналитических показателей, продемонстрировать возможным инвесторам отчёт о доходах с независимой платформы, тем самым повысив их доверие."
"TradeLink отбирает лучшие стратегии из тысяч, позволяя инвестору выбирать только среди проверенных и качественных стратегий. Уникальная система алгоритмических индексов позволяет инвестору получить наилучший уровень диверсификации своих инвестиций, зарабатывая на любом рынке инвестируя сразу в десятки отобранных стратегий."
"Рефеальная программа: Прозрачные условия, один реферальный уровень и много заработка для всех: в будущих обновлениях появится информация о рекордном заработке одного рефовода за предыдущий месяц; подробная панель аналитики, улучшение условий по мере прогресса, один уровень реферальной программы, комиссия до 25% от прибыли реферала, без минимального порога выплат — зарабатывай, пока другие работают: пригласите своих друзей, смотрите, как они приглашают своих друзей, и богатейте; чем больше общий AUM ваших рефералов — тем лучше условия; зарабатывайте деньги с первого уровня рефералов; если вы имеете доступ к большому объёму трафика и хотите получать особенные условия — подайте заявку в нашу программу для инфлюенсеров (до 35% комиссионных); пригласить друзей в TradeLink проще, чем вы думаете: скопируйте уникальную реферальную ссылку в личном кабинете, отправьте реферальный код друзьям, а общее количество зарегистрированных друзей вы увидите на вкладке аналитики — зарабатывайте пассивно, пока ваши друзья зарабатывают деньги."
"Ты даёшь сылки в каждом ответе, в зависимости от контекста: https://tradelink.pro, https://tradelink.pro/passport, https://tradelink.pro/trader-cabinet ,https://tradelink.pro/marketplace, https://tradelink.pro/referral, https://tradelink.pro/terms"
)
# Извлечение UUID из строки
def extract_portfolio_id(text: str) -> str | None:
match = re.search(
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}",
text
)
return match.group(0) if match else None
# # Получение метрик
# def fetch_metrics(portfolio_id: str) -> dict | None:
# try:
# url = f"https://api.tradelink.pro/portfolio/get?portfolioId={portfolio_id}&extended=1&declaration=1&step=day&lang=en&incViews=1"
# response = requests.get(url)
# response.raise_for_status()
# extended = response.json().get("data", {}).get("extended", {})
# keys = [
# # 🔹 Доходность и риск
# "alphaRatio", "betaRatio", "cagr", "sharpe", "sortino", "volatility",
# "kSortino", "kCalmar", "kSharpe",
#
# # 🔹 Просадки
# "maxDD", "mddDuration", "maxBalance",
#
# # 🔹 Поведение стратегии
# "losingDays", "winningDays", "selfProfitRate",
#
# # 🔹 Актуальные показатели доходности
# "lastWeekNetProfit", "lastMonthNetProfit", "lastQuarterGrowth", "lastYearNetGrowth"
# ]
#
# return {k: extended[k] for k in keys if isinstance(extended.get(k), (int, float))}
# except:
# return None
# # Анализ одного портфеля
# def analyze_portfolio_streaming(text: str):
# portfolio_id = extract_portfolio_id(text)
# if not portfolio_id:
# yield "❗ Укажите корректный portfolioId или ссылку."
# return
#
# metrics = fetch_metrics(portfolio_id)
# if not metrics:
# yield "❗ Не удалось получить метрики портфеля."
# return
#
# metrics_text = ", ".join([f"{k}: {v}" for k, v in metrics.items()])
# prompt = f"Вот метрики портфеля: {metrics_text}. Проанализируй их и объясни сильные и слабые стороны на русском языке, как финансовый аналитик."
#
# response_llm = client.chat.completions.create(
# model="meta-llama/Meta-Llama-3.1-8B-Instruct",
# messages=[
# {"role": "system", "content": SYSTEM_PROMPT.py},
# {"role": "user", "content": prompt}
# ],
# stream=True
# )
#
#
# partial = ""
# for chunk in response_llm:
# delta = chunk.choices[0].delta.content
# if delta:
# partial += delta
# yield partial
# Анализ одного портфеля
# def analyze_portfolio_streaming(text: str):
# portfolio_id = extract_portfolio_id(text)
# if not portfolio_id:
# yield "❗ Укажите корректный portfolioId или ссылку."
# return
#
# try:
# # Получение JSON по API
# url = f"https://api.tradelink.pro/portfolio/get?portfolioId={portfolio_id}&extended=1&declaration=1&step=day&lang=en&incViews=1"
# response = requests.get(url)
# json_data = response.json()
# extended = json_data.get("data", {}).get("extended", {})
#
# if not extended:
# yield "❗ Метрики не найдены в ответе API."
# return
#
# # Передаём все числовые метрики, без фильтрации
# metrics = {k: v for k, v in extended.items() if isinstance(v, (int, float))}
# if not metrics:
# yield "❗ Нет числовых метрик для анализа."
# return
#
# metrics_text = ", ".join([f"{k}: {v}" for k, v in metrics.items()])
# prompt = f"""Вот метрики портфеля: {metrics_text}.
# Проанализируй их и объясни сильные и слабые стороны на русском языке, как финансовый аналитик."""
#
# # Запрос к модели
# response_llm = client.chat.completions.create(
# model="nasiruddin15/Mistral-dolphin-2.8-grok-instract-2-7B-slerp",
# messages=[
# {"role": "system", "content": SYSTEM_PROMPT},
# {"role": "user", "content": prompt}
# ],
# stream=True
# )
#
# partial = ""
# for chunk in response_llm:
# delta = chunk.choices[0].delta.content
# if delta:
# partial += delta
# yield partial
#
# except Exception as e:
# yield f"❌ Ошибка при обработке: {e}"
# Сравнение двух портфелей
def compare_portfolios_streaming(text1: str, text2: str):
id1 = extract_portfolio_id(text1)
id2 = extract_portfolio_id(text2)
if not id1 or not id2:
yield "❗ Один или оба portfolioId некорректны."
return
m1 = fetch_metrics(id1)
m2 = fetch_metrics(id2)
if not m1 or not m2:
yield "❗ Не удалось получить метрики одного из портфелей."
return
m1_text = ", ".join([f"{k}: {v}" for k, v in m1.items()])
m2_text = ", ".join([f"{k}: {v}" for k, v in m2.items()])
prompt = f"""Сравни два инвестиционных портфеля:
Портфель 1: {m1_text}
Портфель 2: {m2_text}
Сделай сравнительный анализ на русском языке как финансовый аналитик. Укажи, какой портфель сильнее, в чём риски, где преимущества."""
response = client.chat.completions.create(
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": prompt}
],
stream=True
)
partial = ""
for chunk in response:
delta = chunk.choices[0].delta.content
if delta:
partial += delta
yield partial
def handle_chat_streaming(user_input):
response = client.chat.completions.create(
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
messages=[
{"role": "system", "content": TRADELINK_CONTEXT},
{"role": "user", "content": user_input}
],
stream=True
)
partial = ""
for chunk in response:
delta = chunk.choices[0].delta.content
if delta:
partial += delta
yield partial
# Gradio интерфейс
with gr.Blocks() as demo:
gr.Markdown("## 🧠 Анализ и сравнение инвестиционных портфелей Tradelink")
with gr.Tab("📊 Анализ"):
portfolio_input = gr.Textbox(label="Введите ссылку или portfolioId", placeholder="ea856c1b-...")
analyze_button = gr.Button("🔍 Проанализировать")
output_box = gr.Textbox(label="📈 Результат анализа", lines=15)
analyze_button.click(fn=analyze_portfolio_streaming, inputs=portfolio_input, outputs=output_box)
with gr.Tab("⚖️ Сравнение"):
compare_input_1 = gr.Textbox(label="Портфель 1", placeholder="ea856c1b-...")
compare_input_2 = gr.Textbox(label="Портфель 2", placeholder="d52f55cc-...")
compare_button = gr.Button("📊 Сравнить")
compare_output = gr.Textbox(label="📉 Результат сравнения", lines=20)
compare_button.click(fn=compare_portfolios_streaming, inputs=[compare_input_1, compare_input_2], outputs=compare_output)
with gr.Tab("💬 Диалог"):
chat_input = gr.Textbox(label="Ваш вопрос", placeholder="Что такое TradeLink Passport?")
chat_button = gr.Button("Отправить")
chat_output = gr.Textbox(label="Ответ", lines=8)
chat_button.click(fn=handle_chat_streaming, inputs=chat_input, outputs=chat_output)
if __name__ == "__main__":
demo.launch()