У світі фінансів, який постійно змінюється, точне прогнозування цін акцій є критично важливим для прийняття обґрунтованих інвестиційних рішень. Використовуючи машинне навчання та візуалізацію даних, я вирішив розробити зручний додаток для прогнозування цін акцій за допомогою Streamlit.
Я пройду весь процес розробки як фронтенду, так і бекенду цього веб-застосунку для прогнозування цін акцій. У цій статті ми розглянемо:
- Аналіз і візуалізацію даних про акції за допомогою Pandas і Plotly
- Попередню обробку даних і розробку моделі Long-Short Term Memory (LSTM) за допомогою sklearn і TensorFlow/Keras
- Розробку фронтенд-застосунку та інтеграцію моделі з Streamlit
Що саме являє собою цей додаток?
- Аналітичний інструмент для трейдерів і інвесторів, що генерує аналітику в режимі реального часу на основі даних про конкретні акції.
- Користувачі можуть переглядати та аналізувати історичні тренди цін акцій компаній, тренди прибутків і фінансові дані.
Крім того, користувачі можуть переглядати останні новини, пов'язані з вибраною акцією, у стрічці новин.
3. Використовує попередньо натреновану модель LSTM для прогнозування ціни акцій на наступні 30 днів.
Зміст
- Фронтенд
- Бекенд
- 2.1 Збір даних для візуалізації
- 2.2 Візуалізація даних
- 2.3 Розробка моделі LSTM
- Висновки та майбутні покращення
1. Фронтенд
Для початку давайте коротко познайомимось з Streamlit. У двох словах, Streamlit — це фреймворк на Python, який дозволяє нам створювати інтерактивні додатки для наукових даних і машинного навчання з мінімальним обсягом коду.
Більше інформації ви можете знайти в їхній документації, яка, власне, є основним джерелом для мого вивчення Streamlit.
Щоб продемонструвати простоту використання Streamlit, для створення заголовка та текстів я використав лише функції st.header() і st.markdown().
Додаток спочатку пропонує користувачеві випадаючий список для вибору символу акцій, що оновлює інші частини додатка з даними цієї акції.
Щоб створити випадаючий список для вибору символу акцій, я використав функцію st.selectbox(), де tickers — це список символів акцій S&P500. Вибір користувача зберігається в змінній ‘selectedticker’_, щоб її можна було використати пізніше.
selected_ticker = st.selectbox('Select a stock ticker', tickers)
Перший графік, який ми генеруємо, — це свічковий графік для відображення тренду ціни акцій.
Користувачеві надається 6 різних часових горизонтів для налаштування свічкового графіка: 1 день, 5 днів, 1 місяць, 6 місяців, 1 рік та 5 років. Для цього я використав функції st.columns() та st.button().
Використовуючи умови ‘if’, можна налаштувати дії, які виконуватимуться при натисканні на конкретну кнопку.
У цьому прикладі, якщо натиснути кнопку ‘5d’, користувачеві буде показано 5-денний свічковий графік.
selection_horizon = ''
st.markdown('Оберіть горизонт для свічкового графіка ціни акцій 📈')
one_day, five_day, one_month, six_month, one_year, five_year = st.columns(6)
if one_day.button("1d", use_container_width=True):
selected_horizon='1d'
candlestick_dataframe = df_for_candlestick(selected_horizon, selected_ticker)
candlestick = plot_candlestick(selected_ticker, selected_horizon, candlestick_dataframe)
st.plotly_chart(candlestick)
if five_day.button("5d", use_container_width=True):
selected_horizon='5d'
candlestick_dataframe = df_for_candlestick(selected_horizon, selected_ticker)
candlestick = plot_candlestick(selected_ticker, selected_horizon, candlestick_dataframe)
st.plotly_chart(candlestick)
...
(код обрізано, продовження)
Свічковий графік спочатку створюється за допомогою Plotly, Python-бібліотеки для створення інтерактивних та динамічних візуалізацій даних. Потім графіки Plotly завантажуються в frontend за допомогою функції st.plotlychart()_. Детальніше про створення візуалізацій ми розглянемо в частині про backend.
У наступному розділі користувач може перемикатися між вкладками, щоб переглянути різні типи статистики (ціна акцій, звіт про доходи, баланс, грошовий потік, акції та дивіденди, а також розподіли).
Як і раніше, вкладки були створені за допомогою st.tabs(), а таблиці — за допомогою st.table(), яка приймає Pandas DataFrame.
st.header("Статистика")
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs(["Статистика акцій", "Статистика звіту про доходи", "Статистика балансу", "Статистика грошових потоків", "Статистика акцій", "Статистика дивідендів та розподілів"])
with tab1:
st.table(stock_stats)
with tab2:
st.table(income_stats)
with tab3:
st.table(balance_stats)
with tab4:
st.table(cashflow_stats)
with tab5:
st.table(share_stats)
with tab6:
st.table(dividend_splits_stats)
У наступному розділі користувач може переглядати річні та квартальні звіти про доходи, баланси та звіти про грошові потоки, а також візуалізації фінансових даних, знову ж таки створені за допомогою Plotly.
Використовується st.selectbox(), щоб надати користувачу можливість вибрати між річними або квартальними фінансовими звітами. Подібно до кнопок для графіка свічок раніше, ми можемо використовувати ‘if’ оператори для генерації річних або квартальних фінансових звітів відповідно до вибору користувача. Знову ж таки, я використовував st.tabs(), щоб користувач міг переглядати різні звіти, які знову ж таки є таблицями Streamlit, створеними з Pandas DataFrame.
Останні 3 розділи — це історія доходів, прогноз та новини. Як і раніше, графіки були створені за допомогою Plotly і завантажені через st.plotlychart()_. Новинний стрічка організована в 3 стеки по 2 колонки, щоб імітувати сітку.
2.
2.1 Збір даних для візуалізації
Першим кроком у зборі даних було отримання списку символів тикерів акцій S&P500 з сторінки Вікіпедії «Список компаній S&P500».
def get_sp500_tickers():
sp500 = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')[0]
tickers = sp500['Symbol'].tolist()
return tickers
Отримання історичних даних по акціях
Далі, щоб отримати історичні дані по акціях, я використовував клас Ticker() з Python пакету yfinance (Yahoo Finance).
Інтерфейс Ticker() приймає символ тикера та зберігає різні типи даних про акції, такі як ціна акцій, загальна інформація про компанію, фінансові звіти, новини та інше.
Наприклад, щоб створити графік свічок, нам спершу потрібен DataFrame з даними про ціну акцій, такими як ціни відкриття, закриття, максимальна та мінімальна ціни, а також обсяг торгів.
def df_for_candlestick(horizon, ticker):
stock = yf.Ticker(ticker)
candle_dataframe = stock.history(period=f'{horizon}')
return candle_dataframe
У наведеній функції ми зберігаємо символ тикера або дані про акцію в змінній stock, яку потім використовуємо разом з методом Ticker.history(), щоб отримати DataFrame, що містить дані про ціну акцій з такими стовпцями: [‘Open’, ‘Close’, ‘High’, ‘Low’, ‘Volume’, ‘Dividends’ та ‘Stock Splits’]. Ticker.history() також приймає параметр ‘period’, який визначає, за який період дані будуть отримані.
У випадку графіка свічок, нас цікавили періоди 1d, 5d, 1mo, 6mo, 1y і 5y. Однак, якщо встановити параметр на ‘max’, то дані будуть доступні аж до самого початку існування акцій.
Отримання статистики акцій
Аналогічно, для отримання статистики компанії ми використовуємо ще один метод, getinfo()_. Цей метод повертає вкладений словник з різноманітною інформацією про акцію, від назви компанії та її CEO до коефіцієнта P/E акцій. Оскільки нас цікавить лише статистика, спершу потрібно визначити ключі, з яких ми хочемо витягнути інформацію.
Наприклад, для статистики акцій:
stock_keys = [
'previousClose', 'open', 'dayLow', 'dayHigh', 'volume', 'averageVolume', 'averageVolume10days',
'averageDailyVolume10Day', 'marketCap', 'fiftyTwoWeekLow', 'fiftyTwoWeekHigh',
'fiftyDayAverage', 'twoHundredDayAverage',
'52WeekChange', 'SandP52WeekChange', 'priceToSalesTrailing12Months',
]
Функція нижче зчитує вкладений словник з getinfo()_ і проходить через список ключів, витягуючи лише значення цих ключів, і створює DataFrame за допомогою Pandas.
def create_stats_dataframe(ticker, keys_to_extract, stat):
ticker = yf.Ticker(ticker)
dataframe = ticker.get_info()
dict = {key: dataframe[key] for key in keys_to_extract if key in dataframe}
df = pd.DataFrame(dict.items(), columns=['Key', 'Value'])
df.columns = [f'{stat} Statistics', 'Value']
df.index = range(1, len(df) + 1)
return df
Отримання фінансових даних
Для отримання фінансових даних ми використовуємо методи getincomestmt(), getbalancesheet() та getcashflow()_.
Ці методи приймають параметр freq, який можна встановити на 'yearly' або 'quarterly', що, в свою чергу, повертає DataFrame для відповідного фінансового звіту за рік або квартал.
У функціях нижче ми видаляємо останній стовпець або останні два стовпці, залежно від того, чи це річні, чи квартальні дані, оскільки вони не містять жодних даних.
def income_statement(ticker, timeframe):
ticker = yf.Ticker(ticker)
dataframe = ticker.get_income_stmt(freq=timeframe)
dataframe.columns = dataframe.columns.strftime('%Y-%m-%d')
if timeframe == 'yearly':
income_statement = dataframe.drop(columns=dataframe.columns[-1], axis=1)
return income_statement
elif timeframe == 'quarterly':
income_statement = dataframe.drop(columns=dataframe.columns[-2:], axis=1)
return income_statement
def balance_sheet(ticker, timeframe):
ticker = yf.Ticker(ticker)
dataframe = ticker.get_balance_sheet(freq=timeframe)
dataframe.columns = dataframe.columns.strftime('%Y-%m-%d')
if timeframe == 'yearly':
balance_sheet = dataframe.drop(columns=dataframe.columns[-1], axis=1)
return balance_sheet
elif timeframe == 'quarterly':
balance_sheet = dataframe.drop(columns=dataframe.columns[-2:], axis=1)
return balance_sheet
def cashflow(ticker, timeframe):
ticker = yf.Ticker(ticker)
dataframe = ticker.get_cashflow(freq=timeframe)
dataframe.columns = dataframe.columns.strftime('%Y-%m-%d')
if timeframe == 'yearly':
cashflow = dataframe.drop(columns=dataframe.columns[-1], axis=1)
return cashflow
elif timeframe == 'quarterly':
cashflow = dataframe.drop(columns=dataframe.columns[-2:], axis=1)
return cashflow
Аналогічно, для отримання даних про історію прибутків, ми використовуємо метод getearningshistory().
2.2 Візуалізація даних
Як уже згадувалося, я використовував Plotly для створення всіх графіків в додатку.
Plotly є відносно простим та інтуїтивно зрозумілим, тому я не буду проходити через код візуалізації.
Графік Свічок
def plot_candlestick(ticker, horizon, candle_dataframe):
fig = go.Figure(data=[go.Candlestick(x=candle_dataframe.index,
open=candle_dataframe['Open'],
high=candle_dataframe['High'],
low=candle_dataframe['Low'],
close=candle_dataframe['Close'])])
fig.update_layout(title=f'{ticker} {horizon} Chart')
return fig
Гістограма Чистого Прибутку, Валового Прибутку та Загальних Доходів
def plot_income_statement(dataframe):
filtered_df = dataframe.loc[['NetIncome', 'GrossProfit', 'TotalRevenue']]
filtered_df = filtered_df.T.reset_index()
filtered_df.rename(columns={"index": "Date"}, inplace=True)
filtered_df["Date"] = pd.to_datetime(filtered_df["Date"]).dt.date
filtered_df = filtered_df.loc[::-1]
df_melted = filtered_df.melt(id_vars="Date", var_name="Metric", value_name="Amount")
fig = px.bar(
df_melted,
title='Net Income, Gross Profit and Total Revenue',
x="Date",
y="Amount",
color="Metric",
barmode="group",
labels={"Amount": "Amount ($)", "Date": "Date", "Metric": "Financial Metric"}
)
fig.update_layout(
xaxis=dict(type="category"),
yaxis=dict(tickformat=","),
bargap=0.2
)
return fig
Гістограма Загальних Активів та Зобов'язань
def plot_assets_liabilities(dataframe):
filtered_df = dataframe.loc[['TotalAssets', 'TotalLiabilitiesNetMinorityInterest']]
filtered_df = filtered_df.T.reset_index()
filtered_df.rename(columns={"index": "Date"}, inplace=True)
filtered_df["Date"] = pd.to_datetime(filtered_df["Date"]).dt.date
filtered_df = filtered_df.loc[::-1]
filtered_df.rename(columns={"TotalLiabilitiesNetMinorityInterest": "TotalLiabilities"}, inplace=True)
df_melted = filtered_df.melt(id_vars="Date", var_name="Metric", value_name="Amount")
fig = px.bar(
df_melted,
x="Date",
y="Amount",
color="Metric",
barmode="group",
title="Assets and Liabilities Over Time",
labels={"Amount": "Amount ($)", "Date": "Date", "Metric": "Financial Metric"}
)
fig.update_layout(
xaxis=dict(type="category"),
yaxis=dict(tickformat=","),
bargap=0.2
)
return fig
Лінійний Графік Вільного Грошового Потоку
def plot_free_cashflow(dataframe):
filtered_df = dataframe.loc['FreeCashFlow']
filtered_df = filtered_df.T.reset_index()
filtered_df.rename(columns={"index": "Date"}, inplace=True)
filtered_df["Date"] = pd.to_datetime(filtered_df["Date"]).dt.date
filtered_df = filtered_df.loc[::-1]
fig = px.line(
filtered_df,
x=filtered_df['Date'],
y=filtered_df['FreeCashFlow'],
title='Free Cash Flow Over Time',
labels={"FreeCashFlow": "Free Cash Flow ($)"}
)
fig.update_layout(
xaxis=dict(type="category"),
yaxis=dict(tickformat=",")
)
return fig
Графік Історії Прибутків
Графік історії прибутків був складнішим для створення, оскільки ми будуємо два різних типи діаграм: фактичний EPS та оцінений EPS.
Крім того, точки даних фактичного EPS повинні бути пофарбовані в зелений або червоний колір в залежності від того, чи перевищили вони оцінку EPS або ж не досягли її.
def plot_earnings_history(dataframe):
dataframe = dataframe.reset_index()
dataframe.rename(columns={"index": "Date"}, inplace=True)
dataframe["Date"] = pd.to_datetime(dataframe["Date"]).dt.date
fig = go.Figure()
fig.add_trace(go.Scatter(
x=dataframe['Date'],
y=dataframe['epsEstimate'],
mode='markers',
marker=dict(size=12, symbol='circle-open', color='blue', line=dict(width=2, color='gray')),
name='EPS Estimate'
))
for i, row in dataframe.iterrows():
color = 'green' if row['epsActual'] > row['epsEstimate'] else 'red'
fig.add_trace(go.Scatter(
x=[row['Date']],
y=[row['epsActual']],
mode='markers',
marker=dict(size=12, color=color, line=dict(width=1, color='black')),
name="Actual EPS" if i == 0 else "",
hovertemplate=(
f"EPS Actual: {row['epsActual']:.2f}"
f"Surprise %: {row['surprisePercent']:.2f}%"
""
),
showlegend=(i == 0)
))
fig.update_layout(
title="Quarterly EPS Actual vs Estimate",
xaxis=dict(title="Date", tickmode='array', tickvals=dataframe['Date']),
yaxis=dict(title="EPS", zeroline=True, zerolinecolor='gray', zerolinewidth=1),
font=dict(color='black'),
legend=dict(x=1, y=1),
margin=dict(l=50, r=50, t=50, b=50),
)
return fig
2.3 Розробка Моделі LSTM
Тепер починається найцікавіша частина! Основна особливість цього додатка — його здатність прогнозувати ціни акцій, що реалізується за допомогою моделі LSTM.
Коротко кажучи, LSTM (Long Short-Term Memory) — це рекурентна нейронна мережа, яка добре справляється з обробкою часових рядів та прогнозуванням. Ось кроки, які я зробив для тренування цієї моделі LSTM.
1. Отримання Даних
aapl = yf.Ticker('AAPL')
data = aapl.history(period='max')
data.drop(columns=['Dividends', 'Stock Splits'], inplace=True)
Для розробки моделі я вибрав акції AAPL для отримання даних. Після того, як я видалив стовпці «Dividends» (Дивіденди) та «Stock Splits» (Поділ акцій), набір даних, отриманий з history(period = ‘max’) містить відкриту та закриту ціну, денний максимум, денний мінімум та обсяг торгів на кожен ринковий день.
2. Покращення Даних
Наразі дані мають лише 4 передбачувані характеристики: відкриту ціну, денний максимум, денний мінімум та обсяг торгів. Щоб побудувати більш точну модель LSTM, я вирішив додати технічні індикатори як характеристики.
Технічні індикатори — це математичні розрахунки, засновані на історичних даних про ціни, які дозволяють трейдерам прогнозувати рух цін або тренди, а також визначати точки входу та виходу при торгівлі акціями.
- Волатильність за методом Гармана-Класса
- Індекс відносної сили (RSI)
- Полоси Боллінджера
- 50-денна та 200-денна експоненційна ковзаюча середня (EMA)
- Індекс зближення/розбіжності ковзаючих середніх (MACD)
- Середній справжній діапазон (ATR)
- Індекс акумуляції/розподілу (A/D)
- Індекс напрямку руху (ADX)
- Обсяг на балансі (OBV)
Для обчислення технічних індикаторів я використовував Python пакет pandas_ta (Pandas Technical Indicator).
def obtain_dataframe(selected_ticker):
ticker = yf.Ticker(selected_ticker)
dataframe = ticker.history(period='max')
dataframe.drop(columns=['Dividends', 'Stock Splits'], inplace=True)
dataframe = dataframe.loc['2010-01-01':].copy()
dataframe['Garman_Klass_Volatility'] = ((np.log(dataframe['High'])-np.log(dataframe['Low']))**2)/2-(2*np.log(2)-1)*((np.log(dataframe['Close'])-np.log(dataframe['Open']))**2)
dataframe['RSI'] = ta.rsi(close=dataframe['Close'], length=14)
dataframe['BB_Low'] = ta.bbands(close=dataframe['Close'], length=20).iloc[:,0]
dataframe['BB_Mid'] = ta.bbands(close=dataframe['Close'], length=20).iloc[:,1]
dataframe['BB_High'] = ta.bbands(close=dataframe['Close'], length=20).iloc[:,2]
dataframe['EMA_50'] = ta.ema(dataframe['Close'], length=50)
dataframe['EMA_200'] = ta.ema(dataframe['Close'], length=200)
dataframe['MACD_12_26_9'] = ta.macd(dataframe['Close'], fast=12, slow=26, signal=9).iloc[:,0]
dataframe['ATR'] = ta.atr(dataframe['High'], dataframe['Low'], dataframe['Close'])
dataframe['ADI'] = ta.ad(dataframe["High"], dataframe["Low"], dataframe["Close"], dataframe["Volume"])
dataframe['ADX_14'] = ta.adx(dataframe["High"], dataframe["Low"], dataframe["Close"], length=14).iloc[:,2]
dataframe['OBV'] = ta.obv(dataframe["Close"], dataframe["Volume"])
return dataframe
3.
Попередня обробка Даних
Після цього я здійснив попередню обробку даних для тренування моделі.
- Видалив записи, датовані до 2010-01-01
data = data.loc['2010-01-01':].copy()
- Масштабував характеристики за допомогою MinMaxScaler()
target = pd.DataFrame(data['Close'])
features = pd.DataFrame(data.drop(columns=['Close']))
scaler = MinMaxScaler(feature_range=(0, 1))
feature_transform = scaler.fit_transform(features)
feature_transform = pd.DataFrame(data=feature_transform, index=data.index)
MinMaxScaler() нормалізує дані в діапазоні (0, 1), що означає, що максимальне значення характеристики може бути 1, а мінімальне — 0.
Масштабування є особливо важливим для LSTM, оскільки воно покращує збіжність градієнтного спуску, запобігає вибуху градієнтів, скорочує час тренування та зберігає тимчасові взаємозв'язки в даних часових рядів.
- Створення тренувальних та тестових наборів за допомогою TimeSeriesSplit()
from sklearn.model_selection import TimeSeriesSplit
timesplit = TimeSeriesSplit(n_splits=10)
for train_index, test_index in timesplit.split(feature_transform):
X_train, X_test = feature_transform[:len(train_index)], feature_transform[len(train_index): (len(train_index)+len(test_index))]
y_train, y_test = target[:len(train_index)].values.ravel(), target[len(train_index): (len(train_index)+len(test_index))].values.ravel()
TimeSeriesSplit() — це метод крос-валідації, спеціально розроблений для даних часових рядів. На відміну від стандартної крос-валідації, він враховує тимчасове впорядкування даних. Він працює шляхом поступового розширення тренувального набору при збереженні тестового набору в майбутньому.
Це гарантує, що модель не тренується на майбутніх даних та не тестується на минулих. Я налаштував кількість розділів на 10, що означає, що 10% даних йде на тестовий набір, а решта 90% використовується для тренування моделі LSTM.
- Трансформація даних для LSTM
train_X = np.array(X_train)
test_X = np.array(X_test)
X_train = train_X.reshape(X_train.shape[0], 1, X_train.shape[1])
X_test = test_X.reshape(X_test.shape[0], 1, X_test.shape[1])
LSTM вимагає, щоб вхідні дані були у 3D-форматі. Тому потрібно перетворити дані в формат (кількість зразків, 1, кількість ознак). Наприклад, у нашому випадку форма Xtrain_ виглядає як (3423, 1, 16).
Розробка моделі LSTM
- Архітектура моделі
model = Sequential()
model.add(LSTM(units = 32, activation = 'relu', return_sequences = True, input_shape=(1, train_X.shape[1])))
model.add(Dropout(0.2))
model.add(LSTM(units = 64, activation = 'relu', return_sequences = True))
model.add(Dropout(0.2))
model.add(LSTM(units = 96, activation = 'relu', return_sequences = True))
model.add(Dropout(0.2))
model.add(LSTM(units = 128, activation = 'relu'))
model.add(Dropout(0.2))
model.add(Dense(units = 1, activation='linear'))
model.summary()
Модель складається з 4 шарів LSTM та 1 вихідного шару Dense. Я додав шари Dropout з коефіцієнтом випадання 0.2 після кожного шару LSTM. Dropout — це техніка регуляризації, яка зменшує перенавчання, виключаючи частину нейронів, що робить мережу менш залежною від конкретних нейронів і покращує її здатність до узагальнення.
- Складання моделі
optimizer = Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-7)
model.compile(optimizer=optimizer, loss='mean_squared_error', metrics=['mean_absolute_error'])
Я скомпонував модель з оптимізатором Adam і швидкістю навчання 0.001. Функція втрат — це середнє квадратичне відхилення (mean squared error), а метрика для оцінки — середнє абсолютне відхилення (mean absolute error).
- Навчання моделі
history = model.fit(X_train, y_train, epochs=100, batch_size=32)
Я тренував модель на 100 епохах з розміром пакета 32.
Щоб візуалізувати втрати під час навчання, я побудував простий графік.
plt.figure(figsize=(12,6))
plt.plot(history.history['loss'], label='Втрати при навчанні')
plt.xlabel('Епохи')
plt.ylabel('Втрати')
plt.legend()
plt.show()
- Оцінка моделі
Щоб оцінити ефективність моделі на тестовому наборі, я побудував графіки з оригінальними цінами та передбаченими цінами. З графіків видно, що наша модель схильна передбачати нижчі ціни порівняно з оригінальними цінами.
Однак, також не здається, що модель страждає від переобучення.
y_pred = model.predict(X_test)
plt.figure(figsize=(12, 6))
plt.plot(data.index[-len(y_test):], y_test, label='Оригінальна ціна')
plt.plot(data.index[-len(y_test):], y_pred, label='Передбачені ціни')
plt.title('Оцінка моделі')
plt.xlabel('Дата')
plt.ylabel('Ціна')
plt.legend()
plt.show()
- Прогнозування моделі
Після побудови моделі, я використав її для прогнозування ціни акцій на наступні 30 днів. Для цього я створив forecastnextdays(), яка використовує дані з попереднього дня для прогнозування ціни акцій на наступний день.
Щоб уникнути статичних прогнозів, я додав невелику випадкову варіацію до ознак після кожного прогнозу.
def forecast_next_days(model, feature_transform, target, forecast_days=30):
# Отримуємо останню послідовність ознак
last_sequence = feature_transform.iloc[-1:].values
last_sequence = last_sequence.reshape(1, 1, -1)
# Отримуємо останню відому ціну закриття
last_close = target.iloc[-1].values[0]
# Список для збереження прогнозів
predictions = []
predictions.append(last_close)
for _ in range(forecast_days - 1):
# Прогнозуємо ціну наступного дня
next_pred = model.predict(last_sequence, verbose=0)
next_price = next_pred[0, 0]
predictions.append(next_price)
# Створюємо нову послідовність для наступного прогнозу
new_sequence = last_sequence[0, 0, :].copy()
# Додаємо невелику кількість шуму до ознак
noise = np.random.normal(0, 0.01, new_sequence.shape)
new_sequence += noise
# Переміщаємо для наступного прогнозу
last_sequence = new_sequence.reshape(1, 1, -1)
return predictions
model.save('forecast_model.h5')
**3.
Майбутні покращення
Покращення моделі та прогнозування
- Поточна модель навчена на даних акцій AAPL. Тому модель не навчається прогнозувати інші акції. Потрібно розглянути можливість попереднього навчання моделі на кількох акціях або навчати і створювати моделі прогнозування на основі вибору користувача в межах шляху використання програми.
- Якщо дозволяє обчислювальна потужність, використовувати Keras Tuner для налаштування гіперпараметрів моделі.
- Замість використання випадкового шуму для зміни значень ознак, розраховувати нові значення, використовуючи прогнозовану ціну.
- Використовувати ковзаюче вікно для навчання моделі та прогнозування.
Покращення програми
- UI для користувачів, щоб вони могли вводити гіперпараметри та ознаки, які подаються в процес навчання моделі прогнозування, що дозволяє їм персоналізувати модель.
- Додати графіки для візуалізації технічних індикаторів проти ціни.
Висновок
Створення цієї програми стало неймовірним досвідом навчання, і я зміг поєднати елементи науки про дані, машинного навчання та веб-розробки.
Використовуючи інтуїтивно зрозумілі та гнучкі можливості Streamlit, я зміг створити інтерактивний та потужний інструмент для інвесторів, який дозволяє аналізувати історичні дані акцій, фінансові дані, прогнозувати ціни акцій та приймати інвестиційні рішення на основі даних.
Я планую продовжувати покращувати цей додаток, продовжуючи свою подорож у галузі науки про дані.
Сподіваюся, що читачі зможуть використати цю статтю як посібник для створення подібних веб-додатків на основі даних, як для акцій, так і для інших даних.
Посилання
- Код: https://github.com/nelsonng2002/data-science/tree/main/stockforecastapp
- LinkedIn: https://www.linkedin.com/in/nelson-ng-2002/
Посилання на джерела
- Документація Streamlit: https://docs.streamlit.io/
- Документація Yahoo Finance API: https://pypi.org/project/yfinance/
- Документація Scikit-learn: https://scikit-learn.org/stable/documentation.html
- Документація Keras: https://keras.io/api/
- Документація Plotly: https://plotly.com/python/
Перекладено з: Stock Data and Forecast App Powered by LSTM and Streamlit