MII/mai/lab3.1.ipynb
2024-12-14 15:49:48 +04:00

130 KiB
Raw Permalink Blame History

Лабораторная работа 3

Датасет - Цены на золото https://www.kaggle.com/datasets/sid321axn/gold-price-prediction-dataset

Бизнес-цели:

  1. Прогнозирование цены золота на момент закрытия для поддержки принятия решений по инвестициям.
  2. Оценка волатильности цен золота для долгосрочных стратегий инвестирования.

Цели технического проекта:

  1. Создание модели машинного обучения для прогнозирования цены закрытия акций на золото на основе исторических данных (дат, цен открытия, максимальных и минимальных цен, объёма торгов).
  2. Разработка системы, которая вычисляет и анализирует волатильность на основе исторической ценовой информации и объёмов торгов.
In [13]:
import pandas as pd
from sklearn.model_selection import train_test_split
from imblearn.under_sampling import RandomUnderSampler

df = pd.read_csv("data/Gold.csv")
print(df)

# Преобразование даты продажи в числовой формат (кол-во дней с 01.01.1970)
df['Date'] = pd.to_datetime(df['Date'])
df['Date_numeric'] = (df['Date'] - pd.Timestamp('1970-01-01')).dt.days
print(df['Date_numeric'])
            Date        Open        High         Low       Close   Adj Close  \
0     2011-12-15  154.740005  154.949997  151.710007  152.330002  152.330002   
1     2011-12-16  154.309998  155.369995  153.899994  155.229996  155.229996   
2     2011-12-19  155.479996  155.860001  154.360001  154.869995  154.869995   
3     2011-12-20  156.820007  157.429993  156.580002  156.979996  156.979996   
4     2011-12-21  156.979996  157.529999  156.130005  157.160004  157.160004   
...          ...         ...         ...         ...         ...         ...   
1713  2018-12-24  119.570000  120.139999  119.570000  120.019997  120.019997   
1714  2018-12-26  120.620003  121.000000  119.570000  119.660004  119.660004   
1715  2018-12-27  120.570000  120.900002  120.139999  120.570000  120.570000   
1716  2018-12-28  120.800003  121.080002  120.720001  121.059998  121.059998   
1717  2018-12-31  120.980003  121.260002  120.830002  121.250000  121.250000   

        Volume     SP_open     SP_high      SP_low  ...    GDX_Low  GDX_Close  \
0     21521900  123.029999  123.199997  121.989998  ...  51.570000  51.680000   
1     18124300  122.230003  122.949997  121.300003  ...  52.040001  52.680000   
2     12547200  122.059998  122.320000  120.029999  ...  51.029999  51.169998   
3      9136300  122.180000  124.139999  120.370003  ...  52.369999  52.990002   
4     11996100  123.930000  124.360001  122.750000  ...  52.419998  52.959999   
...        ...         ...         ...         ...  ...        ...        ...   
1713   9736400  239.039993  240.839996  234.270004  ...  20.650000  21.090000   
1714  14293500  235.970001  246.179993  233.759995  ...  20.530001  20.620001   
1715  11874400  242.570007  248.289993  238.960007  ...  20.700001  20.969999   
1716   6864700  249.580002  251.399994  246.449997  ...  20.570000  20.600000   
1717   8449400  249.559998  250.190002  247.470001  ...  20.559999  21.090000   

      GDX_Adj Close  GDX_Volume   USO_Open   USO_High    USO_Low  USO_Close  \
0         48.973877    20605600  36.900002  36.939999  36.049999  36.130001   
1         49.921513    16285400  36.180000  36.500000  35.730000  36.270000   
2         48.490578    15120200  36.389999  36.450001  35.930000  36.200001   
3         50.215282    11644900  37.299999  37.610001  37.220001  37.560001   
4         50.186852     8724300  37.669998  38.240002  37.520000  38.110001   
...             ...         ...        ...        ...        ...        ...   
1713      21.090000    60507000   9.490000   9.520000   9.280000   9.290000   
1714      20.620001    76365200   9.250000   9.920000   9.230000   9.900000   
1715      20.969999    52393000   9.590000   9.650000   9.370000   9.620000   
1716      20.600000    49835000   9.540000   9.650000   9.380000   9.530000   
1717      21.090000    53866600   9.630000   9.710000   9.440000   9.660000   

      USO_Adj Close  USO_Volume  
0         36.130001    12616700  
1         36.270000    12578800  
2         36.200001     7418200  
3         37.560001    10041600  
4         38.110001    10728000  
...             ...         ...  
1713       9.290000    21598200  
1714       9.900000    40978800  
1715       9.620000    36578700  
1716       9.530000    22803400  
1717       9.660000    28417400  

[1718 rows x 81 columns]
0       15323
1       15324
2       15327
3       15328
4       15329
        ...  
1713    17889
1714    17891
1715    17892
1716    17893
1717    17896
Name: Date_numeric, Length: 1718, dtype: int64
In [14]:
# Функция для разбиения на обучающую, валидационную, тестовую выборки
def split_stratified_into_train_val_test(
    df_input,
    stratify_colname="y",
    frac_train=0.6,
    frac_val=0.15,
    frac_test=0.25,
    random_state=None,
):
    #проверка, что сумма долей выборок равна 1
    if frac_train + frac_val + frac_test != 1.0:
        raise ValueError(
            "fractions %f, %f, %f do not add up to 1.0"
            % (frac_train, frac_val, frac_test)
        )

    #проверка наличия указанного столбца для стратификации
    if stratify_colname not in df_input.columns:
        raise ValueError("%s is not a column in the dataframe" % (stratify_colname))

    #разделение на признаки х и целевую переменную у
    X = df_input 
    y = df_input[
        [stratify_colname]
    ]  

    #разделение данных на обучающую и временную выборку с учетом стратификации
    df_train, df_temp, y_train, y_temp = train_test_split(
        X, y, stratify=y, test_size=(1.0 - frac_train), random_state=random_state
    )

    #вычисление относительной доли тестовой выборки по отношению к временной
    relative_frac_test = frac_test / (frac_val + frac_test)
    #разделение временной выборки на валидационную и тестовую
    df_val, df_test, y_val, y_test = train_test_split(
        df_temp,
        y_temp,
        stratify=y_temp,
        test_size=relative_frac_test,
        random_state=random_state,
    )
    #проверка что общее кол-во данных равно сумме трех выборок
    assert len(df_input) == len(df_train) + len(df_val) + len(df_test)

    return df_train, df_val, df_test

#создаем бины (интервалы) для столбца Close и присваиваем метки в новый столбец
bins = [df['Close'].min(), df['Close'].quantile(0.33), df['Close'].quantile(0.66), df['Close'].max()]
labels = ['Low', 'Medium', 'High']
df['Close_binned'] = pd.cut(df['Close'], bins=bins, labels=labels)
#удаляем строки с пропущенными значениями
df = df.dropna()
# вызываем ф-ию для разделения данных на выборки с стратификацией по новому столбцу Close_binned
df_train, df_val, df_test = split_stratified_into_train_val_test(
    df, stratify_colname="Close_binned", frac_train=0.60, frac_val=0.20, frac_test=0.20
)

print(df_train.columns) 
   
print("Обучающая выборка: ", df_train.shape)
print(df_train.Close.value_counts()) 

print("Контрольная выборка: ", df_val.shape)
print(df_val.Close.value_counts())

print("Тестовая выборка: ", df_test.shape)
print(df_test.Close.value_counts())

print("Обучающая выборка: ", df_train.shape)
print(df_train['Close_binned'].value_counts())

print("Контрольная выборка: ", df_val.shape)
print(df_val['Close_binned'].value_counts())

print("Тестовая выборка: ", df_test.shape)
print(df_test['Close_binned'].value_counts())
Index(['Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume',
       'SP_open', 'SP_high', 'SP_low', 'SP_close', 'SP_Ajclose', 'SP_volume',
       'DJ_open', 'DJ_high', 'DJ_low', 'DJ_close', 'DJ_Ajclose', 'DJ_volume',
       'EG_open', 'EG_high', 'EG_low', 'EG_close', 'EG_Ajclose', 'EG_volume',
       'EU_Price', 'EU_open', 'EU_high', 'EU_low', 'EU_Trend', 'OF_Price',
       'OF_Open', 'OF_High', 'OF_Low', 'OF_Volume', 'OF_Trend', 'OS_Price',
       'OS_Open', 'OS_High', 'OS_Low', 'OS_Trend', 'SF_Price', 'SF_Open',
       'SF_High', 'SF_Low', 'SF_Volume', 'SF_Trend', 'USB_Price', 'USB_Open',
       'USB_High', 'USB_Low', 'USB_Trend', 'PLT_Price', 'PLT_Open', 'PLT_High',
       'PLT_Low', 'PLT_Trend', 'PLD_Price', 'PLD_Open', 'PLD_High', 'PLD_Low',
       'PLD_Trend', 'RHO_PRICE', 'USDI_Price', 'USDI_Open', 'USDI_High',
       'USDI_Low', 'USDI_Volume', 'USDI_Trend', 'GDX_Open', 'GDX_High',
       'GDX_Low', 'GDX_Close', 'GDX_Adj Close', 'GDX_Volume', 'USO_Open',
       'USO_High', 'USO_Low', 'USO_Close', 'USO_Adj Close', 'USO_Volume',
       'Date_numeric', 'Close_binned'],
      dtype='object')
Обучающая выборка:  (1030, 83)
Close
124.589996    4
126.180000    3
116.330002    3
126.449997    3
121.309998    3
             ..
115.489998    1
131.759995    1
121.169998    1
118.989998    1
124.500000    1
Name: count, Length: 900, dtype: int64
Контрольная выборка:  (343, 83)
Close
113.019997    2
112.570000    2
118.360001    2
151.619995    2
126.300003    2
             ..
170.770004    1
117.550003    1
124.279999    1
157.429993    1
121.339996    1
Name: count, Length: 329, dtype: int64
Тестовая выборка:  (344, 83)
Close
114.769997    3
117.120003    2
107.790001    2
123.209999    2
155.550003    2
             ..
160.440002    1
117.599998    1
113.419998    1
119.750000    1
114.830002    1
Name: count, Length: 331, dtype: int64
Обучающая выборка:  (1030, 83)
Close_binned
High      350
Low       340
Medium    340
Name: count, dtype: int64
Контрольная выборка:  (343, 83)
Close_binned
High      117
Low       113
Medium    113
Name: count, dtype: int64
Тестовая выборка:  (344, 83)
Close_binned
High      117
Medium    114
Low       113
Name: count, dtype: int64
In [15]:
#уменьшаем дисбаланс и количество уникальных значений в столбце Close
rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(df_train, df_train["Close_binned"])
# Создание датафрейма для результирующей выборки
df_train_rus = pd.DataFrame(X_resampled)
print("Обучающая выборка после undersampling: ", df_train_rus.shape)
print(df_train_rus.Close.value_counts())
Обучающая выборка после undersampling:  (1020, 83)
Close
124.589996    4
125.540001    3
122.209999    3
121.110001    3
121.580002    3
             ..
126.989998    1
156.279999    1
157.929993    1
127.150002    1
149.460007    1
Name: count, Length: 892, dtype: int64
In [16]:
df_train = pd.get_dummies(df_train, columns=['Close_binned'])
df_train['Volume_binned'] = pd.qcut(df_train['Volume'], q=4, labels=False)
#Создание нового столбца 'Price_change', который показывает изменение цены (разница между закрытием и открытием).
df_train['Price_change'] = df_train['Close'] - df_train['Open']
print(df_train)
           Date        Open        High         Low       Close   Adj Close  \
1540 2018-04-16  127.739998  128.050003  127.570000  127.629997  127.629997   
568  2014-05-16  124.349998  124.769997  124.290001  124.500000  124.500000   
972  2015-12-30  101.470001  101.599998  101.349998  101.419998  101.419998   
1087 2016-06-16  125.169998  125.669998  122.230003  122.379997  122.379997   
401  2013-08-22  132.559998  133.460007  132.270004  132.809998  132.809998   
...         ...         ...         ...         ...         ...         ...   
524  2014-03-12  131.559998  132.119995  131.389999  131.759995  131.759995   
1409 2017-10-04  121.209999  121.269997  120.709999  121.169998  121.169998   
672  2014-10-17  119.059998  119.230003  118.419998  118.989998  118.989998   
1333 2017-06-14  121.510002  121.879997  119.570000  119.820000  119.820000   
1695 2018-11-27  115.550003  115.629997  114.599998  114.949997  114.949997   

        Volume     SP_open     SP_high      SP_low  ...    USO_Low  USO_Close  \
1540   4600000  267.000000  268.200012  266.070007  ...  13.340000  13.380000   
568    4052100  187.509995  188.130005  186.720001  ...  37.090000  37.230000   
972    3745000  207.110001  207.210007  205.759995  ...  10.840000  10.930000   
1087  26635000  207.750000  208.570007  205.589996  ...  11.110000  11.140000   
401    5741800  164.899994  166.300003  164.889999  ...  37.090000  37.540001   
...        ...         ...         ...         ...  ...        ...        ...   
524   11094200  186.320007  187.350006  185.899994  ...  35.040001  35.349998   
1409   6475200  252.690002  253.440002  252.559998  ...  10.060000  10.080000   
672    8059000  188.419998  189.750000  187.619995  ...  31.030001  31.250000   
1333  21124800  244.860001  244.869995  243.289993  ...   9.200000   9.230000   
1695   9671100  266.339996  268.399994  265.660004  ...  10.640000  10.950000   

      USO_Adj Close  USO_Volume  Date_numeric  Close_binned_Low  \
1540      13.380000    14024600         17637             False   
568       37.230000     1574900         16206             False   
972       10.930000    24796400         16799              True   
1087      11.140000    31068700         16968             False   
401       37.540001     4511400         15939             False   
...             ...         ...           ...               ...   
524       35.349998     6572600         16141             False   
1409      10.080000    13078000         17443             False   
672       31.250000     7509000         16360             False   
1333       9.230000    60687800         17331             False   
1695      10.950000    36086100         17862              True   

      Close_binned_Medium  Close_binned_High  Volume_binned  Price_change  
1540                False               True              0     -0.110001  
568                  True              False              0      0.150002  
972                 False              False              0     -0.050003  
1087                 True              False              3     -2.790001  
401                 False               True              1      0.250000  
...                   ...                ...            ...           ...  
524                 False               True              3      0.199997  
1409                 True              False              1     -0.040001  
672                  True              False              2     -0.070000  
1333                 True              False              3     -1.690002  
1695                False              False              2     -0.600006  

[1030 rows x 87 columns]
In [17]:
from sklearn.preprocessing import StandardScaler
# Нормализация значений для указанных столбцов
# чтобы значения разных столбов в среднем были 0, а стандартное отклонение - 1
scaler = StandardScaler()
df_train[['Open', 'Close', 'High', 'Low', 'Volume']] = scaler.fit_transform(
    df_train[['Open', 'Close', 'High', 'Low', 'Volume']])
#Создание нового столбца 'Volatility', который показывает волатильность (разницу между максимальной и минимальной ценой).
df_train['Volatility'] = df_train['High'] - df_train['Low']
print(df_train)
           Date      Open      High       Low     Close   Adj Close    Volume  \
1540 2018-04-16  0.043550  0.030843  0.067156  0.038908  127.629997 -0.713195   
568  2014-05-16 -0.154408 -0.159690 -0.126032 -0.143968  124.500000 -0.814939   
972  2015-12-30 -1.490481 -1.505617 -1.477167 -1.492459  101.419998 -0.871967   
1087 2016-06-16 -0.106524 -0.107409 -0.247363 -0.267833  122.379997  3.378675   
401  2013-08-22  0.325013  0.345106  0.343980  0.341559  132.809998 -0.501164   
...         ...       ...       ...       ...       ...         ...       ...   
524  2014-03-12  0.266619  0.267266  0.292149  0.280211  131.759995  0.492769   
1409 2017-10-04 -0.337768 -0.363002 -0.336889 -0.338529  121.169998 -0.364973   
672  2014-10-17 -0.463316 -0.481504 -0.471767 -0.465900  118.989998 -0.070863   
1333 2017-06-14 -0.320249 -0.327568 -0.404033 -0.417405  119.820000  2.355439   
1695 2018-11-27 -0.668282 -0.690625 -0.696760 -0.701944  114.949997  0.228502   

         SP_open     SP_high      SP_low  ...  USO_Close  USO_Adj Close  \
1540  267.000000  268.200012  266.070007  ...  13.380000      13.380000   
568   187.509995  188.130005  186.720001  ...  37.230000      37.230000   
972   207.110001  207.210007  205.759995  ...  10.930000      10.930000   
1087  207.750000  208.570007  205.589996  ...  11.140000      11.140000   
401   164.899994  166.300003  164.889999  ...  37.540001      37.540001   
...          ...         ...         ...  ...        ...            ...   
524   186.320007  187.350006  185.899994  ...  35.349998      35.349998   
1409  252.690002  253.440002  252.559998  ...  10.080000      10.080000   
672   188.419998  189.750000  187.619995  ...  31.250000      31.250000   
1333  244.860001  244.869995  243.289993  ...   9.230000       9.230000   
1695  266.339996  268.399994  265.660004  ...  10.950000      10.950000   

      USO_Volume  Date_numeric  Close_binned_Low  Close_binned_Medium  \
1540    14024600         17637             False                False   
568      1574900         16206             False                 True   
972     24796400         16799              True                False   
1087    31068700         16968             False                 True   
401      4511400         15939             False                False   
...          ...           ...               ...                  ...   
524      6572600         16141             False                False   
1409    13078000         17443             False                 True   
672      7509000         16360             False                 True   
1333    60687800         17331             False                 True   
1695    36086100         17862              True                False   

      Close_binned_High  Volume_binned  Price_change  Volatility  
1540               True              0     -0.110001   -0.036313  
568               False              0      0.150002   -0.033658  
972               False              0     -0.050003   -0.028451  
1087              False              3     -2.790001    0.139953  
401                True              1      0.250000    0.001127  
...                 ...            ...           ...         ...  
524                True              3      0.199997   -0.024883  
1409              False              1     -0.040001   -0.026113  
672               False              2     -0.070000   -0.009737  
1333              False              3     -1.690002    0.076466  
1695              False              2     -0.600006    0.006134  

[1030 rows x 88 columns]
In [18]:
#генерация новых признаков из данных с помощью featuretools
import featuretools as ft
# Создание EntitySet для объединения разных датасетов для удобного использования
es = ft.EntitySet(id="stocks")
es = es.add_dataframe(
    dataframe_name="stock_data", 
    dataframe=df_train,                            
    index="Date")
# Генерация признаков
feature_matrix, feature_defs = ft.dfs(
    entityset=es, 
    target_dataframe_name="stock_data")

feature_defs
d:\3 kurs\МИИ\1 лаб\mai-main\.venv\Lib\site-packages\featuretools\synthesis\deep_feature_synthesis.py:169: UserWarning: Only one dataframe in entityset, changing max_depth to 1 since deeper features cannot be created
  warnings.warn(
Out[18]:
[<Feature: Open>,
 <Feature: High>,
 <Feature: Low>,
 <Feature: Close>,
 <Feature: Adj Close>,
 <Feature: Volume>,
 <Feature: SP_open>,
 <Feature: SP_high>,
 <Feature: SP_low>,
 <Feature: SP_close>,
 <Feature: SP_Ajclose>,
 <Feature: SP_volume>,
 <Feature: DJ_open>,
 <Feature: DJ_high>,
 <Feature: DJ_low>,
 <Feature: DJ_close>,
 <Feature: DJ_Ajclose>,
 <Feature: DJ_volume>,
 <Feature: EG_open>,
 <Feature: EG_high>,
 <Feature: EG_low>,
 <Feature: EG_close>,
 <Feature: EG_Ajclose>,
 <Feature: EG_volume>,
 <Feature: EU_Price>,
 <Feature: EU_open>,
 <Feature: EU_high>,
 <Feature: EU_low>,
 <Feature: EU_Trend>,
 <Feature: OF_Price>,
 <Feature: OF_Open>,
 <Feature: OF_High>,
 <Feature: OF_Low>,
 <Feature: OF_Volume>,
 <Feature: OF_Trend>,
 <Feature: OS_Price>,
 <Feature: OS_Open>,
 <Feature: OS_High>,
 <Feature: OS_Low>,
 <Feature: OS_Trend>,
 <Feature: SF_Price>,
 <Feature: SF_Open>,
 <Feature: SF_High>,
 <Feature: SF_Low>,
 <Feature: SF_Volume>,
 <Feature: SF_Trend>,
 <Feature: USB_Price>,
 <Feature: USB_Open>,
 <Feature: USB_High>,
 <Feature: USB_Low>,
 <Feature: USB_Trend>,
 <Feature: PLT_Price>,
 <Feature: PLT_Open>,
 <Feature: PLT_High>,
 <Feature: PLT_Low>,
 <Feature: PLT_Trend>,
 <Feature: PLD_Price>,
 <Feature: PLD_Open>,
 <Feature: PLD_High>,
 <Feature: PLD_Low>,
 <Feature: PLD_Trend>,
 <Feature: RHO_PRICE>,
 <Feature: USDI_Price>,
 <Feature: USDI_Open>,
 <Feature: USDI_High>,
 <Feature: USDI_Low>,
 <Feature: USDI_Volume>,
 <Feature: USDI_Trend>,
 <Feature: GDX_Open>,
 <Feature: GDX_High>,
 <Feature: GDX_Low>,
 <Feature: GDX_Close>,
 <Feature: GDX_Adj Close>,
 <Feature: GDX_Volume>,
 <Feature: USO_Open>,
 <Feature: USO_High>,
 <Feature: USO_Low>,
 <Feature: USO_Close>,
 <Feature: USO_Adj Close>,
 <Feature: USO_Volume>,
 <Feature: Date_numeric>,
 <Feature: Close_binned_Low>,
 <Feature: Close_binned_Medium>,
 <Feature: Close_binned_High>,
 <Feature: Volume_binned>,
 <Feature: Price_change>,
 <Feature: Volatility>,
 <Feature: DAY(Date)>,
 <Feature: MONTH(Date)>,
 <Feature: WEEKDAY(Date)>,
 <Feature: YEAR(Date)>]
In [19]:
# Оценка предсказательной способности
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error
# Копирование датафрейма для регрессионного анализа
df_train_regression = df_train.copy()
# Определение признаков и целевой переменной
X_train = df_train_regression.drop(['Close', 'Date'], axis=1)
y_train = df_train_regression['Close']
X_test = df_test.drop(['Close', 'Date'], axis=1)
y_test = df_test['Close']
# Преобразование категориальных признаков в дамми-переменные
# (создание столбцов со значениями 0 или 1, если это булевой столбец)
X_train_encoded = pd.get_dummies(X_train, drop_first=True)
X_test_encoded = pd.get_dummies(X_test, drop_first=True)
# Устранение различий в количестве столбцов между обучающей и тестовой выборками
X_test_encoded = X_test_encoded.reindex(columns=X_train_encoded.columns, fill_value=0)
# Проверка типов данных
print(X_train_encoded.dtypes)
Open                   float64
High                   float64
Low                    float64
Adj Close              float64
Volume                 float64
                        ...   
Close_binned_Medium       bool
Close_binned_High         bool
Volume_binned            int64
Price_change           float64
Volatility             float64
Length: 86, dtype: object
In [20]:
# Обучение модели линейной регрессии (поиск зависимостей между признаками)
model = LinearRegression()
model.fit(X_train_encoded, y_train)
# Предсказание цены на тестовой выборке
predictions = model.predict(X_test_encoded)
# Оценка качества модели
mae = mean_absolute_error(y_test, predictions)
mse = mean_squared_error(y_test, predictions)
print("Средняя абсолютная ошибка:", mae)
print("Среднеквадратичная ошибка:", mse)
Средняя абсолютная ошибка: 127.64575343475643
Среднеквадратичная ошибка: 16571.79812749788
In [21]:
# Оценка скорости вычисления
import time
start_time = time.time()
model.fit(X_train_encoded, y_train)
training_time = time.time() - start_time

start_time = time.time()
predictions = model.predict(X_test_encoded)
prediction_time = time.time() - start_time

print(f'время, затраченное на обучение модели: {training_time}. Время, затраченное на предсказание: {prediction_time}')
время, затраченное на обучение модели: 0.18988895416259766. Время, затраченное на предсказание: 0.002999544143676758
In [22]:
# Оценка корреляции
import seaborn as sns
import matplotlib.pyplot as plt

corr_matrix = df_train_regression.corr()
sns.heatmap(corr_matrix, annot=False)
plt.show()
No description has been provided for this image