Spaces:
Sleeping
Sleeping
QAway-to
commited on
Commit
·
d97fa05
1
Parent(s):
9e2ccc7
test5
Browse files
app.py
CHANGED
|
@@ -9,7 +9,7 @@ client = OpenAI(
|
|
| 9 |
api_key="rc_2b641fdc25954733a5ef7ccd10ce6a8e1db603e386a0fca262626daf21562b4b"
|
| 10 |
)
|
| 11 |
|
| 12 |
-
#
|
| 13 |
def extract_portfolio_id(text: str) -> str | None:
|
| 14 |
match = re.search(
|
| 15 |
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}",
|
|
@@ -17,69 +17,118 @@ def extract_portfolio_id(text: str) -> str | None:
|
|
| 17 |
)
|
| 18 |
return match.group(0) if match else None
|
| 19 |
|
| 20 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
def analyze_portfolio_streaming(text: str):
|
| 22 |
portfolio_id = extract_portfolio_id(text)
|
| 23 |
if not portfolio_id:
|
| 24 |
-
yield "❗ Укажите корректный portfolioId или
|
| 25 |
return
|
| 26 |
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
response.raise_for_status()
|
| 32 |
-
json_data = response.json()
|
| 33 |
-
extended = json_data.get("data", {}).get("extended", {})
|
| 34 |
-
|
| 35 |
-
metrics_keys = ["alphaRatio", "betaRatio", "cagr", "sharpe", "sortino", "volatility"]
|
| 36 |
-
filtered = {k: v for k, v in extended.items() if k in metrics_keys and isinstance(v, (int, float))}
|
| 37 |
-
|
| 38 |
-
if not filtered:
|
| 39 |
-
yield "❗ Ключевые метрики отсутствуют в ответе API."
|
| 40 |
-
return
|
| 41 |
-
|
| 42 |
-
metrics_text = ", ".join([f"{k}: {v}" for k, v in filtered.items()])
|
| 43 |
-
prompt = f"""Вот метрики портфеля: {metrics_text}.
|
| 44 |
-
Проанализируй их и объясни сильные и слабые стороны на русском языке, как финансовый аналитик."""
|
| 45 |
-
|
| 46 |
-
response_llm = client.chat.completions.create(
|
| 47 |
-
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
| 48 |
-
messages=[
|
| 49 |
-
{"role": "system", "content": "Ты — финансовый аналитик."},
|
| 50 |
-
{"role": "user", "content": prompt}
|
| 51 |
-
],
|
| 52 |
-
stream=True
|
| 53 |
-
)
|
| 54 |
-
|
| 55 |
-
partial = ""
|
| 56 |
-
for chunk in response_llm:
|
| 57 |
-
delta = chunk.choices[0].delta.content
|
| 58 |
-
if delta:
|
| 59 |
-
partial += delta
|
| 60 |
-
yield partial
|
| 61 |
-
|
| 62 |
-
except Exception as e:
|
| 63 |
-
yield f"❌ Ошибка: {e}"
|
| 64 |
-
|
| 65 |
-
# Интерфейс Gradio (Blocks)
|
| 66 |
-
with gr.Blocks() as demo:
|
| 67 |
-
gr.Markdown("## 🧠 Анализ инвестиционного портфеля Tradelink")
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
-
|
|
|
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
)
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
if __name__ == "__main__":
|
| 85 |
demo.launch()
|
|
|
|
| 9 |
api_key="rc_2b641fdc25954733a5ef7ccd10ce6a8e1db603e386a0fca262626daf21562b4b"
|
| 10 |
)
|
| 11 |
|
| 12 |
+
# Извлечение UUID из строки
|
| 13 |
def extract_portfolio_id(text: str) -> str | None:
|
| 14 |
match = re.search(
|
| 15 |
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}",
|
|
|
|
| 17 |
)
|
| 18 |
return match.group(0) if match else None
|
| 19 |
|
| 20 |
+
# Получение метрик
|
| 21 |
+
def fetch_metrics(portfolio_id: str) -> dict | None:
|
| 22 |
+
try:
|
| 23 |
+
url = f"https://api.tradelink.pro/portfolio/get?portfolioId={portfolio_id}&extended=1&declaration=1&step=day&lang=en&incViews=1"
|
| 24 |
+
response = requests.get(url)
|
| 25 |
+
response.raise_for_status()
|
| 26 |
+
extended = response.json().get("data", {}).get("extended", {})
|
| 27 |
+
keys = [
|
| 28 |
+
# 🔹 Доходность и риск
|
| 29 |
+
"alphaRatio", "betaRatio", "cagr", "sharpe", "sortino", "volatility",
|
| 30 |
+
"kSortino", "kCalmar", "kSharpe",
|
| 31 |
+
|
| 32 |
+
# 🔹 Просадки
|
| 33 |
+
"maxDD", "mddDuration", "maxBalance",
|
| 34 |
+
|
| 35 |
+
# 🔹 Поведение стратегии
|
| 36 |
+
"losingDays", "winningDays", "selfProfitRate",
|
| 37 |
+
|
| 38 |
+
# 🔹 Актуальные показатели доходности
|
| 39 |
+
"lastWeekNetProfit", "lastMonthNetProfit", "lastQuarterGrowth", "lastYearNetGrowth"
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
return {k: extended[k] for k in keys if isinstance(extended.get(k), (int, float))}
|
| 43 |
+
except:
|
| 44 |
+
return None
|
| 45 |
+
|
| 46 |
+
# Анализ одного портфеля
|
| 47 |
def analyze_portfolio_streaming(text: str):
|
| 48 |
portfolio_id = extract_portfolio_id(text)
|
| 49 |
if not portfolio_id:
|
| 50 |
+
yield "❗ Укажите корректный portfolioId или ссылку."
|
| 51 |
return
|
| 52 |
|
| 53 |
+
metrics = fetch_metrics(portfolio_id)
|
| 54 |
+
if not metrics:
|
| 55 |
+
yield "❗ Не удалось получить метрики портфеля."
|
| 56 |
+
return
|
| 57 |
|
| 58 |
+
metrics_text = ", ".join([f"{k}: {v}" for k, v in metrics.items()])
|
| 59 |
+
prompt = f"Вот метрики портфеля: {metrics_text}. Проанализируй их и объясни сильные и слабые стороны на русском языке, как финансовый аналитик."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
+
response_llm = client.chat.completions.create(
|
| 62 |
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
| 63 |
+
messages=[
|
| 64 |
+
{"role": "system", "content": "Ты — финансовый аналитик."},
|
| 65 |
+
{"role": "user", "content": prompt}
|
| 66 |
+
],
|
| 67 |
+
stream=True
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
partial = ""
|
| 71 |
+
for chunk in response_llm:
|
| 72 |
+
delta = chunk.choices[0].delta.content
|
| 73 |
+
if delta:
|
| 74 |
+
partial += delta
|
| 75 |
+
yield partial
|
| 76 |
+
|
| 77 |
+
# Сравнение двух портфелей
|
| 78 |
+
def compare_portfolios_streaming(text1: str, text2: str):
|
| 79 |
+
id1 = extract_portfolio_id(text1)
|
| 80 |
+
id2 = extract_portfolio_id(text2)
|
| 81 |
+
if not id1 or not id2:
|
| 82 |
+
yield "❗ Один или оба portfolioId некорректны."
|
| 83 |
+
return
|
| 84 |
+
|
| 85 |
+
m1 = fetch_metrics(id1)
|
| 86 |
+
m2 = fetch_metrics(id2)
|
| 87 |
+
if not m1 or not m2:
|
| 88 |
+
yield "❗ Не удалось получить метрики одного из портфелей."
|
| 89 |
+
return
|
| 90 |
+
|
| 91 |
+
m1_text = ", ".join([f"{k}: {v}" for k, v in m1.items()])
|
| 92 |
+
m2_text = ", ".join([f"{k}: {v}" for k, v in m2.items()])
|
| 93 |
+
prompt = f"""Сравни два инвестиционных портфеля:
|
| 94 |
|
| 95 |
+
Портфель 1: {m1_text}
|
| 96 |
+
Портфель 2: {m2_text}
|
| 97 |
|
| 98 |
+
Сделай сравнительный анализ на русском языке как финансовый аналитик. Укажи, какой портфель сильнее, в чём риски, где преимущества."""
|
| 99 |
+
|
| 100 |
+
response = client.chat.completions.create(
|
| 101 |
+
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
| 102 |
+
messages=[
|
| 103 |
+
{"role": "system", "content": "Ты — финансовый аналитик."},
|
| 104 |
+
{"role": "user", "content": prompt}
|
| 105 |
+
],
|
| 106 |
+
stream=True
|
| 107 |
)
|
| 108 |
|
| 109 |
+
partial = ""
|
| 110 |
+
for chunk in response:
|
| 111 |
+
delta = chunk.choices[0].delta.content
|
| 112 |
+
if delta:
|
| 113 |
+
partial += delta
|
| 114 |
+
yield partial
|
| 115 |
+
|
| 116 |
+
# Gradio интерфейс
|
| 117 |
+
with gr.Blocks() as demo:
|
| 118 |
+
gr.Markdown("## 🧠 Анализ и сравнение инвестиционных портфелей Tradelink")
|
| 119 |
+
|
| 120 |
+
with gr.Tab("📊 Анализ"):
|
| 121 |
+
portfolio_input = gr.Textbox(label="Введите ссылку или portfolioId", placeholder="ea856c1b-...")
|
| 122 |
+
analyze_button = gr.Button("🔍 Проанализировать")
|
| 123 |
+
output_box = gr.Textbox(label="📈 Результат анализа", lines=15)
|
| 124 |
+
analyze_button.click(fn=analyze_portfolio_streaming, inputs=portfolio_input, outputs=output_box)
|
| 125 |
+
|
| 126 |
+
with gr.Tab("⚖️ Сравнение"):
|
| 127 |
+
compare_input_1 = gr.Textbox(label="Портфель 1", placeholder="ea856c1b-...")
|
| 128 |
+
compare_input_2 = gr.Textbox(label="Портфель 2", placeholder="d52f55cc-...")
|
| 129 |
+
compare_button = gr.Button("📊 Сравнить")
|
| 130 |
+
compare_output = gr.Textbox(label="📉 Результат сравнения", lines=20)
|
| 131 |
+
compare_button.click(fn=compare_portfolios_streaming, inputs=[compare_input_1, compare_input_2], outputs=compare_output)
|
| 132 |
+
|
| 133 |
if __name__ == "__main__":
|
| 134 |
demo.launch()
|