import streamlit as st import yfinance as yf import pandas as pd import numpy as np import matplotlib.pyplot as plt from datetime import date from sklearn.model_selection import train_test_split from prophet import Prophet from prophet.plot import plot_plotly from plotly import graph_objs as go from prophet.make_holidays import make_holidays_df from sklearn.metrics import mean_absolute_error, mean_squared_error st.set_page_config(layout='wide', initial_sidebar_state='expanded') st.set_option('deprecation.showPyplotGlobalUse', False) st.title('ML Wall Street') st.image('images/img.png') START = "2020-01-01" TODAY = date.today().strftime("%Y-%m-%d") stocks = ('AAPL', 'UNH', 'MSFT', 'GS', 'HD', 'AMGN', 'MCD', 'CAT', 'CRM', 'V', 'BA', 'HON', 'TRV', 'AXP', 'JPM', 'IBM', 'JNJ', 'WMT', 'PG', 'CVX', 'MRK', 'MMM', 'NKE', 'DIS', 'KO', 'DOW', 'CSCO', 'INTC', 'VZ', 'WBA') selected_stock = st.selectbox('Выберите тикер из индекса Dow Jones', stocks) period = st.slider('Количество дней прогноза:', 7, 14, 14) # @st.cache_data def load_data(ticker): data = yf.download(ticker, START, TODAY) data.reset_index(inplace=True) return data data = load_data(selected_stock) latest_date = data['Date'].iloc[-1].strftime('%Y-%m-%d') st.markdown(f"

Цены актуальны на последнюю дату закрытия торгов {latest_date}

", unsafe_allow_html=True) def evaluate_trend_first_day(predicted_value, actual_value): # Разница между первым днем прогноза и последним днем тестовых данных forecast_diff_first_last = predicted_value - actual_value # Оценка тренда на первый день: рост, падение, стабильность if forecast_diff_first_last > 0: return "Тенденция на первый день: Рост" elif forecast_diff_first_last < 0: return "Тенденция на первый день: Падение" else: return "Тенденция на первый день: Стабильность" def evaluate_trend_period(forecast_14_days): # Разница между первым и последним значением прогноза forecast_diff = forecast_14_days['yhat'].iloc[-1] - forecast_14_days['yhat'].iloc[0] # Оценка тренда на весь период прогноза: рост, падение, стабильность print("Разница между первым и последним значением прогноза:", forecast_diff) if forecast_diff > 0: return "Тенденция на период прогноза: Рост" elif forecast_diff < 0: return "Тенденция на период прогноза: Падение" else: return "Тенденция на период прогноза: Стабильность" # Формирование календаря для США year_2023 = 2023 # Создаем DataFrame с встроенными праздниками для 2023 holidays_df_2023 = make_holidays_df(year_list=[year_2023], country='US') # Преобразуем входные строки в datetime holidays_df_2023['ds'] = pd.to_datetime(holidays_df_2023['ds']) # Создаем DataFrame с кастомными праздниками для 2024 года custom_holidays_2024 = pd.DataFrame({ 'holiday': 'custom', 'ds': pd.to_datetime([ '2024-01-01', # Новый год '2024-01-15', # День Мартина Лютера Кинга младшего '2024-02-19', # День рождения Дж. Вашингтона (Washington’s Birthday) '2024-03-29', # Страстная пятница (Good Friday) '2024-05-27', # День Памяти (Memorial Day) '2024-06-19', # День национальной независимости | Juneteenth '2024-07-04', # День независимости '2024-09-02', # День труда '2024-11-28', # День Благодарения '2024-12-24', # Рождество ]), 'lower_window': 0, 'upper_window': 1, }) # Объединяем все DataFrame с праздниками all_holidays_US = pd.concat([holidays_df_2023, custom_holidays_2024]).drop_duplicates(subset=['ds']).sort_values(by=['ds']) # Создаем DataFrame с датами праздников holidays_df_US = pd.DataFrame({ 'ds': all_holidays_US['ds'], 'holiday': 'holiday', }) def ticker(data, holidays_df, text1, text2, k): data = data data = data.rename(columns={'Date': 'ds', 'Adj Close': 'y'}) # Сортируем данные по дате data = data.sort_values(by='ds') # Определяем индекс для разделения split_index = len(data) - period # Разделяем данные на обучающую и тестовую выборки full_train_data3 = data.iloc[:split_index].copy() full_test_data3 = data.iloc[split_index:].copy() # Удаляем временную зону из столбца ds full_train_data3['ds'] = full_train_data3['ds'].dt.tz_localize(None) full_test_data3['ds'] = full_test_data3['ds'].dt.tz_localize(None) # st.write(full_test_data3) # Создаем модель Prophet model = Prophet(interval_width=0.95) model.fit(full_train_data3) # Создаем фрейм для прогноза на тестовых данных, исключая даты праздников last_date = full_test_data3['ds'].max() future = model.make_future_dataframe(periods=full_test_data3.shape[0]+k, freq='B') future = future[~future['ds'].isin(holidays_df['ds'])] forecast_test = model.predict(future) # Создаем фрейм для прогноза на +14 дней после последней даты future_14_days = model.make_future_dataframe(periods=period, freq='B', include_history=False) future_14_days['ds'] = pd.date_range(start=last_date + pd.DateOffset(1), periods=period, freq='B') forecast_14_days = model.predict(future_14_days) # Отрисовка графика fig = go.Figure() fig = plot_plotly(model, forecast_test) fig.add_trace(go.Scatter(x=full_test_data3['ds'], y=full_test_data3['y'], mode='markers', marker=dict(color='orchid'), name='Факт тест')) fig.add_trace(go.Scatter(x=forecast_test['ds'].iloc[-period:], y=forecast_test['yhat'].iloc[-period:], mode='lines+markers', marker=dict(color='blue'), name='Прогноз тест')) fig.add_trace(go.Scatter(x=forecast_14_days['ds'], y=forecast_14_days['yhat'], mode='lines+markers', name='Прогноз будущее')) fig.update_layout(title_text=text1, xaxis_rangeslider_visible=True, xaxis_title='', yaxis_title='') fig.update_traces(showlegend=True) st.plotly_chart(fig, use_container_width=True, range_slider_visible=True) # Расчет метрик на тестовой выборке actual_values_test = full_test_data3['y'].values predicted_values_test = forecast_test['yhat'].iloc[-period:].values mape_test = np.mean(np.abs((actual_values_test - predicted_values_test) / actual_values_test)) * 100 rmse_test = np.sqrt(mean_squared_error(actual_values_test, predicted_values_test)) # Рассчитываем кастомную метрику точности прогноза для 1 дня с учетом весов предыдущих 7 дней forecast_values = forecast_test['yhat'].tail(period).values predicted_value = forecast_values[0] # Выбираем первый предсказанный день из последних 14 actual_values = full_test_data3['y'].tail(7).values weights = np.array([1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4]) custom_mape = np.dot(weights, np.abs((actual_values - predicted_value) / actual_values)) / np.sum(weights) * 100 check = st.checkbox(text2) if check: col1, col2 = st.columns([1, 1]) with col1: st.write(f"**Информация.** \ Горизонт планирования {period} дней. Валидация модели на тестовой выборке ({period} дней).") st.markdown("**Метрики для тестовой выборки:**") st.write(f"RMSE: {rmse_test:.2f}") st.write(f"MAPE: {mape_test:.2f}%") st.write(f"Weighted MAPE: {custom_mape:.2f}%") # Оценка тренда на первый день trend_evaluation_first_day = evaluate_trend_first_day(forecast_14_days['yhat'].iloc[0], full_test_data3['y'].iloc[-1]) st.write(trend_evaluation_first_day) # Оценка тренда на период прогноза trend_evaluation_period = evaluate_trend_period(forecast_14_days[['ds', 'yhat']]) st.write(trend_evaluation_period) st.info("📌 Кастомная метрика “weighted MAPE’’ - \ взвешенное среднее абсолютных процентных ошибок 1 дня прогноза по отношению к значениям крайних 7 дней.") with col2: forecast_results = pd.DataFrame({ 'Дата': forecast_14_days['ds'].iloc[-period:].values, 'Прогноз': forecast_14_days['yhat'].iloc[-period:].values.round(2) }) st.dataframe(forecast_results.set_index('Дата')) # fig2 = data.plot_components(future_14_days) # st.write(fig2) text1 = f'График прогноза для {period} дней по акции {selected_stock}, USD 🇺🇸' text2 = f"Результаты прогноза {selected_stock}" ticker(data, holidays_df_US, text1, text2, 1)