/
dimaWebexplorer
/
investmentAssistant
Обзор
Документация
Войти
/
dimaWebexplorer
/
investmentAssistant
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
llmcode
ml/reasoning.py
253 строки
13 KB
yyeewweee
update ml/reasoning.py
17 дек 2025, 19:51
17 дек 2025, 19:51
8159074
Код
Авторство
О чём код?
import pandas as pd import requests import json from typing import List, Dict, Any import time OPENROUTER_API_KEY = "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" MODEL_NAME = "openrouter/auto" HEADERS = { "Authorization": f"Bearer {OPENROUTER_API_KEY}", "Content-Type": "application/json" } # --- Функция для рассуждения по акциям --- def integrate_openrouter_reasoning_stocks(stocks_df: pd.DataFrame, user_prompt: str) -> pd.DataFrame: """ Интегрирует рассуждение модели OpenRouter в процесс отбора акций. Возвращает DataFrame с отобранными акциями и рассчитанным количеством. """ if stocks_df.empty: return pd.DataFrame() # --- Фаза 1: Первоначальный отбор --- recommended_stocks_df = get_stock_recommendations(stocks_df, user_prompt) if recommended_stocks_df.empty: return pd.DataFrame() # --- Фаза 2: Самосовершенствование (Approach 2) --- # Используем рекомендации как старт refined_portfolio_stocks_df = refine_list_with_reasoning(recommended_stocks_df, user_prompt, asset_type='stock') # --- Фаза 3: Рассчитать количество бумаг --- # Эта функция должна быть реализована, чтобы распределить бюджет по уточненному списку # Она заменит логику из calculate_positions, но применительно к уточненному списку if not refined_portfolio_stocks_df.empty: # Убедимся, что столбец с ценой называется 'Цена'. if 'Цена' not in refined_portfolio_stocks_df.columns: # Попробуем другие возможные названия possible_price_cols = ['LAST', 'Цена', 'Price'] found_price_col = next((col for col in possible_price_cols if col in refined_portfolio_stocks_df.columns), None) if found_price_col: refined_portfolio_stocks_df.rename(columns={found_price_col: 'Цена'}, inplace=True) # Убедимся, что 'Цена' числовая refined_portfolio_stocks_df['Цена'] = pd.to_numeric(refined_portfolio_stocks_df['Цена'], errors='coerce') # Добавляем временный столбец 'Количество бумаг' с NaN, он будет заполнен в create_portfolio_v2 refined_portfolio_stocks_df['Количество бумаг'] = float('nan') # Переименовываем тикер в 'Бумаги' если нужно if 'Тикер' in refined_portfolio_stocks_df.columns: refined_portfolio_stocks_df.rename(columns={'Тикер': 'Бумаги'}, inplace=True) elif 'SECID' in refined_portfolio_stocks_df.columns: refined_portfolio_stocks_df.rename(columns={'SECID': 'Бумаги'}, inplace=True) return refined_portfolio_stocks_df # --- Общая функция для самосовершенствования списка --- def refine_list_with_reasoning(portfolio_df: pd.DataFrame, user_prompt: str, asset_type: str) -> pd.DataFrame: """ Отправляет модель уже сформированный список бумаг и промпт пользователя. Модель анализирует его и вносит коррективы до тех пор, пока результат не будет полностью соответствовать промпту (имитация достижения 'нулевого' скоринга). """ if portfolio_df.empty: return pd.DataFrame() current_portfolio_df = portfolio_df.copy() iteration_count = 0 max_iterations = 3 system_message_base = f""" Ты - опытный инвестиционный консультант. Пользователь предоставил тебе текущий список {asset_type}s и свои инвестиционные цели и предпочтения. Твоя задача - проанализировать, насколько текущий список соответствует целям. Если он не идеален, предложи улучшения: добавь более подходящие {asset_type}s, удали менее подходящие. Цель - добиться максимального соответствия списка промпту пользователя. Верни обновленный список в формате JSON: [{{...}}], сохранив все исходные поля. Если текущий список уже идеален, верни его без изменений. """ while iteration_count < max_iterations: iteration_count += 1 current_portfolio_str = current_portfolio_df.to_json(orient='records', ensure_ascii=False, indent=2) user_message = f""" Вот мой текущий список {asset_type}s: {current_portfolio_str} Вот мои цели и предпочтения: {user_prompt} Проанализируй его и предложи улучшения. Верни обновленный список. """ payload = { "model": MODEL_NAME, "messages": [ {"role": "system", "content": system_message_base}, {"role": "user", "content": user_message} ], "response_format": { "type": "json_object" } } api_response = call_openrouter_api(payload) try: raw_content = api_response['choices'][0]['message']['content'] updated_portfolio_data = json.loads(raw_content) updated_portfolio_df = pd.DataFrame(updated_portfolio_data) current_portfolio_df = updated_portfolio_df if len(current_portfolio_df) == len(portfolio_df) and current_portfolio_df.equals(portfolio_df): break # Выходим, если не было изменений if iteration_count >= 2: break # Имитация достижения "нуля" except (KeyError, json.JSONDecodeError): break # Останавливаем цикл при ошибке return current_portfolio_df # --- Новая функция создания портфеля (только акции) --- def create_portfolio_v2_stocks_only(total_budget: float, refined_stocks_df: pd.DataFrame, original_stocks_df: pd.DataFrame, stock_ratio: float) -> pd.DataFrame: """ Создает портфель, используя уточненные списки от модели, если они есть. Иначе использует оригинальные списки. Работает только с акциями. """ # Все бюджет идет на акции budget_for_stocks = total_budget selected_stocks_df = refined_stocks_df if not refined_stocks_df.empty else original_stocks_df # Рассчитываем количество для выбранных акций final_stock_positions = pd.DataFrame() if not selected_stocks_df.empty and budget_for_stocks > 0: final_stock_positions = calculate_positions_from_list(selected_stocks_df, budget_for_stocks) # Возвращаем только позиции по акциям return final_stock_positions def calculate_positions_from_list(securities_list: pd.DataFrame, budget: float) -> pd.DataFrame: """ Рассчитывает количество бумаг для покупки на основе бюджета, распределяя средства равномерно между всеми бумагами в списке. """ if securities_list.empty or budget <= 0: return pd.DataFrame() df = securities_list.copy() # Проверяем наличие столбца 'Цена' и его числовое значение if 'Цена' not in df.columns or df['Цена'].isna().any(): # Попробуем принудительно преобразовать один из столбцов к 'Цена' possible_price_cols = ['LAST', 'Цена', 'Price'] found_price_col = next((col for col in possible_price_cols if col in df.columns), None) if found_price_col: df['Цена'] = pd.to_numeric(df[found_price_col], errors='coerce') else: # Если цены нет, невозможно рассчитать количество return pd.DataFrame() # Удаляем строки с NaN в цене df = df.dropna(subset=['Цена']) if df.empty: return pd.DataFrame() # Убедимся, что все цены положительны df = df[df['Цена'] > 0] if df.empty: return pd.DataFrame() # Переименовываем тикер в 'Бумаги' если нужно if 'Бумаги' not in df.columns: if 'Тикер' in df.columns: df.rename(columns={'Тикер': 'Бумаги'}, inplace=True) elif 'SECID' in df.columns: df.rename(columns={'SECID': 'Бумаги'}, inplace=True) else: # Если столбец с тикером не найден, используем индекс или другой столбец df['Бумаги'] = df.index.astype(str) # или используйте другой подходящий столбец num_securities = len(df) budget_per_security = budget / num_securities # Рассчитываем количество, округляем вниз до целого числа лотов df['Количество бумаг'] = (budget_per_security / df['Цена']).apply(lambda x: max(0, int(x))) # Округление вниз # Возвращаем только нужные столбцы required_cols = ['Бумаги', 'Цена', 'Количество бумаг'] available_cols = [col for col in required_cols if col in df.columns] return df[available_cols].copy() # --- Копии и адаптации функций из предыдущего скрипта для акций --- def get_stock_recommendations(stock_df: pd.DataFrame, user_prompt: str) -> pd.DataFrame: """ Отправляет DataFrame с акциями и пользовательский промпт модели OpenRouter. Модель выбирает и возвращает наиболее подходящие. """ if stock_df.empty: return pd.DataFrame() stocks_json_str = stock_df.to_json(orient='records', ensure_ascii=False, indent=2) system_message = f""" Ты - опытный финансовый аналитик. Пользователь предоставил тебе таблицу с акциями и описание своих инвестиционных целей и предпочтений. Твоя задача - внимательно проанализировать данные об акциях и рекомендовать те, которые лучше всего соответствуют целям пользователя. Обоснуй свой выбор, опираясь на предоставленные данные (доходность, название, цена и т.д.). Верни только список рекомендованных акций в формате JSON: [{{"Тикер": "...", "Краткое название": "...", "Полное название": "...", "Цена": "...", "Цена закрытия": "...", "Дивидендная доходность": "...", "BOARD": "..."}}]. Если подходящих акций нет, верни пустой список []. """ user_message = f""" Вот таблица с акциями: {stocks_json_str} Вот цели и предпочтения пользователя: {user_prompt} """ payload = { "model": MODEL_NAME, "messages": [ {"role": "system", "content": system_message}, {"role": "user", "content": user_message} ], "response_format": { "type": "json_object" } } api_response = call_openrouter_api(payload) try: raw_content = api_response['choices'][0]['message']['content'] recommended_stocks_data = json.loads(raw_content) recommended_df = pd.DataFrame(recommended_stocks_data) return recommended_df except (KeyError, json.JSONDecodeError): return pd.DataFrame() # Возвращаем пустой DataFrame в случае ошибки # --- Функции для API --- def call_openrouter_api(payload: Dict[str, Any]) -> Dict[str, Any]: """ Вызывает OpenRouter API с заданным payload. """ url = "https://openrouter.ai/api/v1/chat/completions" response = requests.post(url, headers=HEADERS, data=json.dumps(payload)) response.raise_for_status() return response.json()