Spaces:
Sleeping
Sleeping
| 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() | |