پیش بینی قیمت در پایتون — راهنمای گام به گام

۲۵۰۹ بازدید
آخرین به‌روزرسانی: ۰۷ شهریور ۱۴۰۲
زمان مطالعه: ۸ دقیقه
پیش بینی قیمت در پایتون — راهنمای گام به گام

حوزه بازارهای مالی، به دلیل اهمیت بالا و پیچیدگی‌های موجود، نیاز به تحلیل با استفاده از علم روز دارد که بخش قابل توجهی از آن، توسط هوش مصنوعی (Artificial Intelligence) و علم داده (Data Science) تأمین می‌شود. در این آموزش، به پیش بینی قیمت در پایتون می‌پردازیم.

برای تحلیل و پیش‌بینی قیمت، نیاز به داده‌هایی داریم که به طور کلی دو دسته هستند:

  1. متغیرهای خرد (Micro) مانند قیمت یک نماد، حجم نماد و یا نمادهای دیگر
  2. متغیرهای کلان (Macro) مانند قیمت جهانی طلا، قیمت جهانی نفت، شاخص‌ها

به طور کلی، برای انجام یک پیش‌بینی مطلوب، وجود تعداد زیادی متغیر خرد و تعدادی متغیر کلان مفید است. برای مثال، انجام یک پیش‌بینی مناسب برای سهمی در بورس ایران، می‌تواند به متغیرهای زیر نیاز داشته باشد:

  1. متغیرهای مربوط به خود سهم
  2. شاخص کل و شاخص هم‌وزن
  3. نرخ سود بانکی
  4. نرخ تورم
  5. قیمت جهانی طلا
  6. برخی شاخص‌های موجود در بازار فارکس (Foreign Exchange Market)

از طرفی، داده‌های کیفی مرتبط با «تحلیل فاندامنتال» (Fundamental Analysis) نیز می‌توانند اثر فراوانی داشته باشند که بهتر است به شکلی کمی‌سازی شده و وارد مجموعه داده شوند.

برخی داده‌ها از منابعی مانند «Google Trend» که نشان‌دهنده اقبال عمومی هستند نیز می‌توانند بسیار کمک‌کننده باشند. بنابراین، هرچه دامنه اطلاعات مورد استفاده وسیع‌تر باشد، تحلیل با اعتبار و دقت بیشتری صورت می‌گیرد. البته باید توجه داشت که داده‌ها باید از اعتبار بالایی برخوردار باشند، همچنین با توجه به نظم خاصی که در داده‌های سری زمانی وجود دارد، باید داده‌ها امکان ترکیب و «همجوشی» (Data Fusion) با یکدیگر را داشته باشند.

برای یادگیری برنامه‌نویسی با زبان پایتون، پیشنهاد می‌کنیم به مجموعه آموزش‌های مقدماتی تا پیشرفته پایتون فرادرس مراجعه کنید که لینک آن در ادامه آورده شده است.

پیش بینی قیمت در پایتون

حال برای پیاده‌سازی مدل‌ها و انجام پیش بینی قیمت در پایتون ، وارد محیط برنامه‌نویسی شده و کتابخانه‌های مورد نیاز را فراخوانی می‌کنیم:

1import numpy as np
2import pandas as pd
3import datetime as dt
4import yfinance as yf
5import mplfinance as mplf
6import sklearn.metrics as met
7import matplotlib.pyplot as plt
8import pandas_datareader as pdt
9import sklearn.linear_model as lm
10import sklearn.preprocessing as pp
11import statsmodels.graphics.tsaplots as tsaplt

این کتابخانه‌ها به‌ترتیب برای موارد زیر استفاده خواهند شد:

  1. کار با آرایه‌ها و محاسبات برداری
  2. کار با دیتافریم‌ها و داده‌ها
  3. استفاده از تاریخ و زمان
  4. دریافت داده
  5. رسم نمودارهای مالی
  6. محاسبه معیارهای ارزیابی مدل‌ها
  7. رسم نمودار
  8. دریافت داده
  9. ایجاد و آموزش مدل‌های خطی
  10. پیش‌پردازش داده
  11. رسم نمودارهای تحلیل سری زمانی

حال، تنظیمات زیر را اعمال می‌کنیم:

1np.random.seed(0)
2plt.style.use('ggplot')

اکنون نماد (Ticker)، بازه زمانی بین داده‌ها (Interval)، تاریخ اولین داده و تاریخ آخرین داده را تعیین می‌کنیم:

1Ticker = 'BTC-USD'
2Interval = '1d'
3Date1 = dt.datetime(2015, 1, 1)
4Date2 = dt.datetime(2022, 1, 1)

حال می‌توانیم داده‌ها را به‌شکل زیر دریافت کنیم:

1DF = yf.download(tickers=Ticker, interval=Interval, start=Date1, end=Date2)

به جز روش فوق، می‌توان از روش زیر نیز استفاده کرد:

1Interval = '1d'
2Date1 = dt.datetime(2015, 1, 1)
3Date2 = dt.datetime(2022, 1, 1)
4
5Ticker = yf.Ticker('BTC-USD')
6DF = Ticker.history(interval=Interval, start=Date1, end=Date2)

همچنین می‌توان از کتابخانه Pandas Datareader نیز به‌شکل زیر برای این منظور استفاده کرد:

1Ticker = 'BTC-USD'
2Date1 = dt.datetime(2015, 1, 1)
3Date2 = dt.datetime(2022, 1, 1)
4
5DF = pdt.DataReader(Ticker, data_source='yahoo', start=Date1, end=Date2)

به این ترتیب، در هر سه روش، خروجی حاصل یک دیتافریم خواهد بود. توجه داشته باشید که به‌دلیل سرعت ارتباط یا قطعی ارتباط اینترنتی، ممکن است در برخی مواقع دریافت داده با مشکل مواجه شود.

برای نمایش دیتافریم حاصل، می‌نویسیم:

1print(DF.head())

و به‌صورت زیر، خواهیم داشت:

                  Open        High         Low       Close   Adj Close    Volume
Date
2014-12-31  310.914001  320.192993  310.210999  320.192993  320.192993  13942900
2015-01-01  320.434998  320.434998  314.002991  314.248993  314.248993   8036550
2015-01-02  314.079010  315.838989  313.565002  315.032013  315.032013   7860650
2015-01-03  314.846008  315.149994  281.082001  281.082001  281.082001  33054400
2015-01-04  281.145996  287.230011  257.612000  264.195007  264.195007  55629100

حال نمودار شمعی (Candle Stick Plot) را در ۲۰۰ روز اخیر برای قیمت به‌صورت زیر رسم می‌کنیم:

1mplf.plot(DF.iloc[-200:], type='candle')
2plt.show()

به این ترتیب، نمودار به‌شکل زیر خواهد بود.

پیش بینی قیمت با پایتون

به این ترتیب، نمودار شمعی رسم می‌شود. حال می‌توانیم نمودار حجم را نیز ضافه کنیم. افزون دو میانگین متحرک با پنجره ۱۳ روز و ۵۵ روز می‌تواند روند و سطوح را به خوبی نشان دهد:

1mplf.plot(DF.iloc[-200:], type='candle', mav=(13, 55), volume=True)
2plt.show()

در این صورت، نمودار زیر حاصل خواهد شد.

رسم نمودار میانگین متحرک

به این ترتیب، نمودارها نیز به‌خوبی رسم و رفتار سهم مشاهده می‌شود.

پیش‌پردازش داده

در مرحله اول از پیش‌بینی، تنها از داده‌های Close استفاده می‌کنیم.

برای این کار ابتدا ستون مربوط به قیمت بسته شدن نماد را استخراج می‌کنیم:

1C = DF['Close'].to_numpy()

سپس نمودار متغیر مورد نظر را رسم می‌کنیم:

1plt.plot(C, lw=0.7, c='crimson')
2plt.title('BTC Price')
3plt.xlabel('Time (Day)')
4plt.ylabel('Price (USD)')
5plt.show()

در خروجی کد فوق، شکل زیر را خواهیم داشت.

نمودار سری زمانی

می‌بینیم که نمودار رسم شده است. به دلیل ماهیت نمایی بازار، تغییرات قیمت در ۷۰۰ روز اول به‌خوبی قابل مشاهده نیست.

برای رفع این مشکل، از قیمت لگاریتم می‌گیریم و نمودار را دوباره رسم می‌کنیم:

1S = np.log(C)
2
3plt.plot(S, lw=0.7, c='crimson')
4plt.title('BTC Price Logrithm')
5plt.xlabel('Time (Day)')
6plt.ylabel('Log(Price (USD))')
7plt.show()

و در خروجی، شکل زیر را خواهیم داشت.

پیش بینی قیمت در پایتون

به این ترتیب، مشاهده می‌کنیم که اختلاف سطوح قیمت به‌‌خوبی رفع شده است. می‌توان برای رفع روند (Trend) سری زمانی، از روش‌هایی مانند تفاضل‌گیری، حذف روند خطی، میانگین متحرک و یا Hodrick-Prescott Filter استفاده کرد.

حال تابع Lag را برای تولید مجموعه داده پیاده‌سازی می‌کنیم:

1def Lag(S:np.ndarray, L:int):
2    nD0 = S.size
3    nD = nD0 - L
4    X = np.zeros((nD, L))
5    Y = np.zeros((nD, 1))
6    for i in range(nD):
7        X[i, :] = S[i:i + L]
8        Y[i, 0] = S[i + L]
9    return X, Y

این تابع به‌ترتیب مراحل زیر را انجام می‌دهد:

  1. اندازه سری زمانی ورودی تعیین می‌شود.
  2. اندازه داده خروجی تعیین می‌شود (با توجه به اینکه برای L داده موجود در ابتدای سری، نمی‌توان جفت‌های x و y تشکیل داد).
  3. دو آرایه خالی برای X و Y ایجاد می‌شود.
  4. به‌ازای تمامی داده‌ها، هر سطر از ماتریس‌های X و Y مقداردهی می‌شود.
  5. مجموعه داده تولید شده در خروجی برگردانده می‌شود.

با استفاده از این تابع، سری زمانی را به مجموعه داده قابل استفاده تبدیل می‌کنیم:

1nLag = 30
2X0, Y0 = Lag(S, nLag)

حال می‌توانیم درصد «داده‌های آموزش» (Train Dataset) را تعیین و سپس تعداد آن‌ها را محاسبه کنیم:

1sTrain = 0.8
2nDtr = int(sTrain * S.size)

حال می‌توانیم داده‌ها را به دو مجموعه داده آموزش و آزمایش (Test) تقسیم کنیم:

1trX0 = X0[:nDtr]
2teX0 = X0[nDtr:]
3trY0 = Y0[:nDtr]
4teY0 = Y0[nDtr:]

به این ترتیب، داده‌ها تقسیم می‌شود. حال مقیاس داده‌ها را اصلاح می‌کنیم:

1SSX = pp.StandardScaler()
2trX = SSX.fit_transform(trX0)
3teX = SSX.transform(teX0)
4
5SSY = pp.StandardScaler()
6trY = SSY.fit_transform(trY0)
7teY = SSY.transform(teY0)

با توجه به اینکه تنها یک ویژگی هدف وجود دارد، آرایه هدف را به‌شکل زیر تغییر می‌دهیم:

1trY = trY.reshape(-1)
2teY = teY.reshape(-1)

پیش بینی قیمت با مدل خودهمبسته

حال برای انجام پیش‌بینی، از یک مدل «خودهمبسته» (Autoregressive) استفاده می‌کنیم. برای مطالعه بیشتر در این موضوع، می‌توانید به مطلب «مدل خودهمبسته (Autoregressive) در پایتون – راهنمای گام به گام» مراجعه کنید.

اکنون می‌توانیم یک مدل خطی ایجاد کنیم و آن را آموزش دهیم:

1Model = lm.LinearRegression()
2Model.fit(trX, trY)

برای محاسبه معیارهای ارزیابی مدل، تابعی به‌صورت زیر تعریف می‌کنیم تا با گرفتن مدل و داده، معیارهای مورد نظر را محاسبه و در خروجی چاپ کند:

1def RegressionReport(Model, X:np.ndarray, Y:np.ndarray):
2    P = Model.predict(X)
3
4    MSE = met.mean_squared_error(Y, P)
5    RMSE = MSE**0.5
6    NRMSE = 100 * RMSE / (np.max(Y) - np.min(Y))
7    MAE = met.mean_absolute_error(Y, P)
8    MAPE = 100 * met.mean_absolute_percentage_error(Y, P)
9    R2 = 100 * met.r2_score(Y, P)
10
11    print(f'MSE:   {round(MSE, 4)}')
12    print(f'RMSE:  {round(RMSE, 4)}')
13    print(f'NRMSE: {round(NRMSE, 2)} %')
14    print(f'MAE:   {round(MAE, 4)}')
15    print(f'MAPE:  {round(MAPE, 2)} %')
16    print(f'R2:    {round(R2, 2)} %')

این تابع در ابتدا پیش‌بینی مدل برای داده‌ها را دریافت کرده، سپس معیارهای مختلف را با استفاده از sklearn.metrics و یا روابط موجود محاسبه می‌کند. در نهایت هر معیار چاپ می‌شود.

حال برای داده‌های آموزش و آزمایش تابع نوشته شده را اجرا می‌کنیم:

1print('Model Regression Report on Train Dataset:')
2RegressionReport(Model, trX, trY)
3print('_'*60)
4print('Model Regression Report on Test Dataset:')
5RegressionReport(Model, teX, teY)

که در خروجی خواهیم داشت:

Model Regression Report on Train Dataset:
MSE:   0.0007
RMSE:  0.0273
NRMSE: 0.85 %
MAE:   0.0175
MAPE:  8.95 %
R2:    99.93 %
____________________________________________________________
Model Regression Report on Test Dataset:
MSE:   0.0008
RMSE:  0.0276
NRMSE: 2.05 %
MAE:   0.0201
MAPE:  1.06 %
R2:    99.55 %

به این ترتیب، مشاهده می‌کنیم که ضریب مدل در هر دو مجموعه داده نزدیک به هم بوده و مناسب است. توجه داشته باشید که مقدار معیار MAPE در مجموعه داده آموزش ۸ برابر بیشتراز مجموعه داده آزمایش است که شاید در ابتدا عجیب به نظر بیاید. دلیل این اتفاق وجود مقادیر نزدیک به ۰ در مجموعه داده آموزش است.

با توجه به رشد زیاد قیمت نماد در سال‌های اخیر، در ٪۲۰ انتهایی مجموعه داده، اعداد بزرگ‌تر از میانگین هستند و به همین دلیل، مقادیر نزدیک به ۰ اغلب در مجموعه داده آموزش قرار دارد. علاوه بر این مورد، اختلاف زیادی نیز در مقدار معیار NRMSE وجود دارد، در حالی که بین RMSEها اختلاف زیادی وجود ندارد. این اتفاق نیز به‌دلیل تفاوت در Range داده‌ها است. ٪۸۰ ابتدایی داده، شامل بازه بزرگتری (از ۵ تا ۱۰) می‌شود، درحالی‌که ٪۲۰ انتهایی شامل بازه کوچک‌تری (از ۹ تا ۱۱) می‌شود. به این دلیل، ۲٫۵ برابر بودن RMSE در مجموعه داده آموزش طبیعی است.

برای مطالعه این معیار‌ها، می‌توانید به مطلب «بررسی معیارهای ارزیابی رگرسیون در پایتون — پیاده سازی + کدها» مراجعه کنید.

رسم نمودارها

حال نمودار سری زمانی اصلی و پیش‌بینی مدل را برای داده‌های آموزش و آزمایش رسم می‌کنیم:

1trP = Model.predict(trX)
2teP = Model.predict(teX)
3
4plt.plot(trY, lw=0.9, c='crimson', label='Target Values')
5plt.plot(trP, lw=0.9, c='teal', label='Predicted Values')
6plt.title('Model Result on Train Dataset')
7plt.xlabel('Time (Day)')
8plt.ylabel('Value')
9plt.legend()
10plt.show()
11
12plt.plot(teY, lw=0.9, c='crimson', label='Target Values')
13plt.plot(teP, lw=0.9, c='teal', label='Predicted Values')
14plt.title('Model Result on Test Dataset')
15plt.xlabel('Time (Day)')
16plt.ylabel('Value')
17plt.legend()
18plt.show()

در خروجی کد فوق دو نمودار زیر حاصل می‌شوند.

نمودار سری زمانی اصلی

نمودار دوم در تصویر زیر آورده شده است.

پیش بینی قیمت در پایتون

به این ترتیب، مشاهده می‌کنیم که پیش‌بینی مدل به داده‌های واقعی بسیار نزدیک است. اما نکته‌ای که وجود دارد، اندکی تأخیر مدل در پیش‌بینی است. در واقع، مدل سعی کرده تا قیمت آخرین روز را به‌عنوان پیش‌بینی فردا استفاده کند (با اندکی بهبود با استفاده از قیمت‌های گذشته).

برای مشاهده وزن مدل برای روزهای گذشته به‌شکل زیر عمل می‌کنیم:

1W = Model.coef_
2print(f'W:\n{W}')

که خواهیم داشت:

[+3.58563953e-02 -1.96077112e-02 -1.20056042e-02
 -1.88671111e-02 +2.53669946e-02 +5.83798116e-03
 +6.07673595e-03 -1.94466868e-02 +2.97395294e-02
 -5.50333940e-02 -2.67256590e-02 +3.38254901e-02
 -3.09856605e-02 +4.98994631e-02 -6.81061580e-03
 -8.07773671e-04 +4.84668241e-03 -8.51348645e-03
 +5.97168971e-04 -4.92384255e-02 +8.77800917e-02
 -2.10080279e-02 +2.11319993e-02 -8.05356204e-02
 +2.68525320e-02 +1.56674539e-02 +2.31580583e-03
 -1.30531651e-02 +4.58633245e-02 +9.70581002e-01]

به این ترتیب، مشاهده می‌کنیم که برای روز آخر از داده‌های ورودی، وزن ۰٫۹۷ تعیین شده است که نشان از تمایل زیاد مدل به استفاده از روز آخر دارد. در واقع، مدل سعی کرده تا تغییرات برای روز بعد را محاسبه و به قیمت روز آخر اضافه کند.

برای متعادل کردن رفتار مدل، می‌توانیم از مدل‌های Ridge و Lasso و Elastic Net استفاده کنیم. توجه داشته باشید که پیش‌بینی انجام‌شده، در مقیاس تغییر یافته بود. برای تبدیل مقیاس پیش‌بینی به مقیاس اصلی داده‌ها، باید ابتدا Inverse Transform انجام شده و سپس تابع Exponential اعمال شود:

1trP0 = np.exp( SSY.inverse_transform( trP.reshape( (-1, 1) ) ) )
2teP0 = np.exp( SSY.inverse_transform( teP.reshape( (-1, 1) ) ) )

می‌توان برای بهبود رفتار مدل، مقدار nLag را بهینه کرد. برای این منظور، می‌توانیم نمودار تابع خودهمبستگی جزئی (Partial Autocorrelation Function - PACF) را رسم کنیم:

1tsaplt.plot_pacf(S, lags=500, c='teal', lw=0.4, markersize=1)
2plt.xlabel('Lags (Days)')
3plt.ylabel('Pearson Correlation Coefficient')
4plt.show()

که نمودار زیر حاصل می‌شود.

نمودار تابع خودهمبستگی جزئی

به این ترتیب، مشاهده می‌کنیم که Lagهای زیر نیز ارتباط معناداری را نشان می‌دهند:

$$ \text{Lags}={1,4,5,14,15,29,195} $$

بنابراین، مشاهده می‌کنیم که در ۳۰ تأخیر ابتدایی، تنها ۶ تأخیر معنادار هستند. به این ترتیب، می‌توان تنها ۲۹ تأخیر ابتدایی را استفاده و دقت نسباً خوبی دریافت کرد. توجه داشته باشید که می‌توان به‌جای تمامی تأخیرهای بین ۱ تا ۲۹، می‌توان به‌صورت گزینشی تنها از تأخیرهای ۱ و ۴ و ۵ و ۱۴ و ۱۵ و ۲۹ استفاده کرد.

جمع‌بندی پیش بینی قیمت در پایتون

در این مطلب، از رگرسیون خطی برای پیش بینی قیمت در پایتون برای روز بعد استفاده کردیم.

برای مطالعه بیشتر، می‌توان موارد زیر را بررسی کرد:

  1. تابع Lag را به‌صورتی تغییر دهید که Lagها را به‌صورت گزینشی به مجموعه داده اضافه کند.
  2. وجود تأخیر‌های غیرمعنادار در ویژگی‌های ورودی، باعث چه مشکلاتی می‌شود؟
  3. آیا می‌توان به جای انتخاب ٪۸۰ داده برای آموزش از ابتدای سری زمانی، آن را به‌صورت تصادفی از کل سری زمانی انتخاب کرد؟
  4. در بخش پیش‌پردازش سری زمانی، از یکی از روش‌های گفته‌شده برای حذف روند استفاده و نتایج را مقایسه کنید.
  5. مدل‌های KNN و SVM و MLP را روی مجموعه داده آموزش داده و نتایج را مقایسه کنید.
  6. اگر بخواهیم به‌جای پیش‌بینی مقدار قیمت در روز بعد، جهت تغییرات را در سه دسته افزایش، کاهش و خنثی پیش‌بینی کنیم، کد را باید به چه صورتی تغییر دهیم؟
بر اساس رای ۲۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
مجله فرادرس
۳ دیدگاه برای «پیش بینی قیمت در پایتون — راهنمای گام به گام»

pandas_datareader قابل دسترسی نیست. درسته؟

سلام،
ممکن است به دلیل شرایط ااتصال و زمان پاسخ‌گویی Server، کد به درستی کار نکند، یا از هر چند بار اجرا، یک بار به درست کار کند. پیشنهاد می‌کنیم کتابخانه yfinance را بررسی نمایید و در صورتی که از یک مجموعه داده تکراری چندین بار استفاده می‌کنید، مجموعه داده دریافت شده را برای استفاده‌های بعدی ذخیره کنید.

تشکر منسجم خوب ساده…یک سوالی داشتم، اینکه کل داده ها رو ابتدا یکجا تغییر مقیاس بدیم نرمالسازی کنیم. بعد ترین و تست تفکیک کنیم… و یا اول ترین تست تفکیک کنیم بعد مقیاس شون رو تغییر بدیم… تا در مدلها استفاده بشن… در میزان و خطا متفاوت هستند. و روش اول خطای کمتری دارد . بنظرتون علت از چی میتونه باشه؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *