{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"![gresearch.png](attachment:8f9e16fc-c54c-4f53-8a06-5e816ea1b10b.png)  <p style=\"text-align:center;\"><span style=\"font-size:80px;\"><span style=\"color:orange;\"> <i>Crypto Forecasting</i> </span></span></p>\n  \n<span style=\"font-size:18px;\"><span style=\"font-family:cursive;\">\n  <b>Author: Vincent Weimer <br>\n      Date: 2022-01-13</b>\n    </span>\n  \n<hr></hr> ","metadata":{},"attachments":{"8f9e16fc-c54c-4f53-8a06-5e816ea1b10b.png":{"image/png":"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"}}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"%%capture\n\n## Import Libraries\nimport numpy as np # linear algebra\nfrom numpy.random import seed \nimport math \n\nfrom scipy.stats import normaltest\n\nimport pandas as pd # data processing \npd.options.display.max_rows = 100\npd.options.display.max_seq_items = 2000\npd.set_option('display.max_columns', None)\npd.set_option('display.expand_frame_repr', False)\npd.set_option('max_colwidth', -1)\n\nimport datetime as dt\n\n!pip -q install mplfinance\nimport mplfinance as mpf\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as ticker\nimport matplotlib.dates as mpl_dates\nplt.rcParams.update({'font.size': 14})\nimport seaborn as sns\nplt.style.use('seaborn')\nsns.set_style('whitegrid')\n\n!pip install talib-binary # install talib for feature engineering \nimport talib\nfrom talib import RSI, BBANDS, MACD, ATR\n\nfrom sklearn.metrics import mean_squared_error\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.linear_model import LinearRegression, Ridge, Lasso\nfrom sklearn.pipeline import Pipeline\n\nimport warnings # Supress warnings \nwarnings.filterwarnings('ignore')\n\nimport statsmodels as sm\n\nimport joblib\n\n# Fix seed for reproducible results\nSEED = 42\nnp.random.seed(SEED)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Import data\ndata_folder = \"../input/g-research-crypto-forecasting/\"\n!ls $data_folder","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Example Sample Submission","metadata":{}},{"cell_type":"code","source":"example_sample_submission = pd.read_csv(data_folder + \"example_sample_submission.csv\")\nprint(example_sample_submission.shape)\nexample_sample_submission.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"### Example Test","metadata":{}},{"cell_type":"code","source":"%%time\n\ntest_df = pd.read_csv(data_folder + \"example_test.csv\")\ntest_df['timestamp'] = pd.to_datetime(test_df['timestamp'], unit='s') \nprint(test_df.shape)\ntest_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<b><i>Important!! Test data starts at 2021-06-13 and training data ends at 2021-09-21. To prevent data leakage, we need to filter train_df before the test data starts.</i></b>","metadata":{}},{"cell_type":"markdown","source":"## Preprocessing train data","metadata":{}},{"cell_type":"markdown","source":"The data contains more than 24 million rows! We need to reduce its memory using the following function.","metadata":{}},{"cell_type":"code","source":"%%time \n\ndef reduce_mem_usage(df): ## Copied directly from other Kagglers! ##\n    \"\"\" iterate through all the columns of a dataframe and modify the data type\n        to reduce memory usage.        \n    \"\"\"\n    start_mem = df.memory_usage().sum() / 1024**2\n    print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))\n    \n    for col in df.columns:\n        col_type = df[col].dtype\n        \n        if col_type != object:\n            c_min = df[col].min()\n            c_max = df[col].max()\n            if str(col_type)[:3] == 'int':\n                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:\n                    df[col] = df[col].astype(np.int8)\n                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:\n                    df[col] = df[col].astype(np.int16)\n                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:\n                    df[col] = df[col].astype(np.int32)\n                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:\n                    df[col] = df[col].astype(np.int64)  \n            else:\n                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:\n                    df[col] = df[col].astype(np.float16)\n                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:\n                    df[col] = df[col].astype(np.float32)\n                else:\n                    df[col] = df[col].astype(np.float64)\n#         else:\n#             df[col] = df[col].astype('category')\n\n    end_mem = df.memory_usage().sum() / 1024**2\n    print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))\n    print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))\n    \n    return df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time \nassets_df = pd.read_csv(data_folder + \"asset_details.csv\")\nassets_df = assets_df.sort_values(by='Asset_ID')\n\ntickers = ['BNB', 'BTC', 'BCH', 'ADA', 'DOGE', 'EOS', 'ETH', 'ETC', 'IOTA', 'LTC', 'MKR', 'XMR', 'XLM', 'TRX']\n\n# mappings\nmapping_name = dict(assets_df[['Asset_ID', 'Asset_Name']].values)\nmapping_weight = dict(assets_df[['Asset_ID', 'Weight']].values)\nmapping_tickers = dict(enumerate(tickers))\n\n# data types\ndtypes = {\n    'timestamp': str,\n    'Asset_ID': np.int8,\n    'Count': np.int32,\n    'Open': np.float64,\n    'High': np.float64,\n    'Low': np.float64,\n    'Close': np.float64,\n    'Volume': np.float64,\n    'VWAP': np.float64,\n    'Target': np.float64,\n}\n\n# formatting\ntext_formats = {\n   'PURPLE': '\\033[95m',\n   'CYAN': '\\033[96m',\n   'DARKCYAN': '\\033[36m',\n   'BLUE': '\\033[94m',\n   'GREEN': '\\033[92m',\n   'YELLOW': '\\033[93m',\n   'RED': '\\033[91m',\n   'BOLD': '\\033[1m',\n   'UNDERLINE': '\\033[4m',\n   'END': '\\033[0m'\n}\n\nRED = text_formats['RED']\nBOLD = text_formats['BOLD']\nEND = text_formats['END']\n\ndf_train_raw = pd.read_csv(data_folder + \"train.csv\", dtype=dtypes)\n# df_train_raw = reduce_mem_usage(df_train_raw) # Memory reduced by 34.8%\ndf_train_raw.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prepare first train data\nTaking in the raw train data, we wrangle the data in a first step perform the following steps:\n* Transform unix timestamp to a regular timestamp\n* Extract simple date values like date, year, and month\n* Add colomns from asset details including the ticket, which makes the Data Frames more easy to filter\n* Rename count to interval_trade_count\n","metadata":{}},{"cell_type":"code","source":"%%time\n\ndef prepare_train_data(df):\n    \"\"\"This function takes in the raw training data and wrangles the data in the shape and types \n    required for further research\"\"\"\n    \n    CUTOFF_DATE = '2021-06-13 00:00:00'\n    \n    df['timestamp'] = pd.to_datetime(df['timestamp'], unit='s')       # convert from unix to datetime timestamp\n    df = df.set_index('timestamp')                                    # set date as index column\n    \n    dates = df.index.get_level_values('timestamp')\n    df['date'] = pd.to_datetime(dates.date)\n    df['date'] = df['date'].dt.strftime('%Y%m%d').astype(int)         # SLOWS IT DOWN BY 3 MINS! NEED STH BETTER\n    df['year'] = dates.year\n    df['month'] = dates.month\n    \n    df[\"asset_name\"] = df[\"Asset_ID\"].map(mapping_name)\n    df[\"asset_weight\"] = df[\"Asset_ID\"].map(mapping_weight)\n    df[\"ticker\"] = df[\"Asset_ID\"].map(mapping_tickers)\n    \n    df = df.rename({'Count': 'interval_trade_count'}, axis=1)\n    df.columns = map(str.lower, df.columns)\n    \n    df.sort_index()\n    df = df[(df.index < CUTOFF_DATE)]\n    \n    total_num_records = df.shape[0]\n    print(f\"Total number of records in train dataset: {total_num_records}\")\n    \n    return df\n\ndf_train = prepare_train_data(df_train_raw)\nprint(df_train.dtypes)\ndf_train.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_vwap_inf = np.where(np.isinf(df_train['vwap']))\nprint(df_vwap_inf)\nprint(len(df_vwap_inf))\n\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def find_missing_rows(df, start, end, asset):\n    \"\"\"This function calculates how many missing rows each crypto asset has.\"\"\"\n    \n    num_rows = len(df.index)\n    num_missing_rows = len(pd.date_range(start=start, end=end, freq='min').difference(df.index))\n    print(f\"{asset} total number of records: \" + BOLD + f\"{num_rows}\" + END +  \\\n          \", Total number of missing minute records: \" + BOLD + f\"{num_missing_rows}\" + END)\n    \ndef populate_missing_rows(df):\n    \"\"\"This function populates missing minute data rows with Null values.\"\"\"\n    \n    asset, start_date, end_date = df.iloc[0]['asset_name'], df.index[0], df.index[-1]\n    print(f\"{asset} start date: {start_date}, end date: {end_date}\")\n    find_missing_rows(df, start_date, end_date, asset)\n    \n    df = df.asfreq(freq='T', method='ffill')  # forward fill missing time series data\n    print(f\"Populating missing {asset} data...\")\n    find_missing_rows(df, start_date, end_date, asset)\n    print('\\n')\n    \n    return df\n    \ncrypto_dfs = list()\n    \nfor i in list(range(14)):\n    symbol_df = df_train[df_train['asset_id'] == i]\n    crypto_dfs.append(populate_missing_rows(symbol_df))\n\n\ndf_train = pd.concat(crypto_dfs).sort_index()  \n# print(df_train.dtypes)\n# print(len(df_train.index))\n# df_train.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%time\n# Generate dictionary of Dataframes for all tickers and store in dict..\ndef generate_crypto_dfs():\n    \"\"\"Returns a dictionary of dataframes with key(Ticker):value(Dataframe that belongs to that ticker)\"\"\"\n    \n    return {ticker:df_train.query(\"ticker == @ticker\") for ticker in tickers}\n\nall_cryptos = generate_crypto_dfs()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Filter on a few data frames to work with (BTC, ETH, DOGE)\nbtc = all_cryptos['BTC']\neth = all_cryptos['ETH']\ndoge = all_cryptos['DOGE']\nbnb = all_cryptos['BNB']\n\nprint(f\"DOGE unique years: {doge.year.unique()}\")\nprint(f\"BTC unique years: {btc.year.unique()}\")\nprint(f\"Number of BTC records: {len(btc.index)}\")\n\n# doge_filtered = doge.query('year >= 2020')\n# btc_filtered = btc.query('year >= 2020')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# print(len(btc_filtered.index))\n# print(len(doge_filtered.index))\n\n\n# doge.index[-1]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Function that gives opportunity to resample a time series data frame\n\ndef resample_timeseries(df, offset='H'):\n    \"\"\"Resampes a timeseries dataframe given the offset forwarded in the function\"\"\"\n    \n    df = df.reset_index().groupby(['asset_id', 'ticker']).resample(offset, on='timestamp', origin='start').agg(\n        {\"open\": \"first\", \n         \"close\": \"last\", \n         \"low\": \"min\", \n         \"high\": \"max\",\n         \"volume\": \"sum\",\n         \"vwap\": \"max\"\n        }\n    ).dropna()[['open', 'high', 'low', 'close', 'volume','vwap']]\n\n    return df.reset_index().set_index('timestamp')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Statistical Analysis","metadata":{}},{"cell_type":"markdown","source":"## Correlation","metadata":{}},{"cell_type":"code","source":"def plot_rolling_correlation(df, ticker0, ticker1, window, offset, prices):\n    \"\"\"Computes and plots the rolling correlations\"\"\"\n    \n    rolling_corr = df[ticker0].rolling(window).corr(df[ticker1]).to_frame('correlation')\n    \n    plt.figure(figsize=(20,8))\n    sns.lineplot(x=\"timestamp\", y=\"correlation\", data=rolling_corr).set(title=f\"{window} {offset} Rolling Correlation between {ticker0} and {ticker1} using {prices}\")\n    \n    \ndef merge_series(series0, series1, ticker0, ticker1):\n    return pd.DataFrame({ticker0: series0, ticker1: series1})\n\ndef test_correlation(df0, df1, window0, window1, offset, use_returns=True):\n    \"\"\" Computes the correlation between 2 series for the whole series\n    and the correlation on a rolling window\"\"\"\n    \n    ticker0, ticker1 = df0.iloc[0]['ticker'], df1.iloc[0]['ticker']\n    if use_returns:\n        df0_close, df1_close = df0.close.pct_change(), df1.close.pct_change()\n        price_type = 'Returns'\n    else:\n        df0_close, df1_close = df0.close, df1.close\n        price_type = 'Absolute Prices'\n        \n    df = merge_series(df0_close, df1_close, ticker0, ticker1)\n    \n    # Compute correlations\n    corr_spearman = df[ticker0].corr(df[ticker1], method='spearman')\n    corr_pearson = df[ticker0].corr(df[ticker1], method='pearson')\n    \n    # Print correlations\n    print(f\" Correlation between {ticker0} and {ticker1} using {offset}+{price_type} with Spearman method is: \" +  (RED+BOLD) + str(corr_spearman) + END)\n    print(f\" Correlation between {ticker0} and {ticker1} using {offset}+{price_type} with Pearson method is: \" + (RED+BOLD) + str(corr_pearson) + END)\n    \n    # Compute and plot rolling correlations \n    plot_rolling_correlation(df, ticker0, ticker1, window0, offset, price_type)   \n    print('\\n')\n    plot_rolling_correlation(df, ticker0, ticker1, window1, offset, price_type)    \n    \n# Test correlation with Daily crypto data\ntest_correlation(resample_timeseries(btc, 'D'), resample_timeseries(eth, 'D'), 30, 60, 'day'.capitalize(), False)     # BTC and ETH are highly positively correlated with a value of 0.797\ntest_correlation(resample_timeseries(btc, 'D'), resample_timeseries(eth, 'D'), 30, 60, 'day'.capitalize())\n\n# Test correlation with Monthly crypto data\ntest_correlation(resample_timeseries(btc, 'M'), resample_timeseries(eth, 'M'), 3, 6, 'month'.capitalize(), False)        # BTC and ETH are highly positively correlated with a value of 0.797\ntest_correlation(resample_timeseries(btc, 'M'), resample_timeseries(eth, 'M'), 3, 6, 'month'.capitalize())\n\n# # Test correlation with Daily crypto data between BTC/DOGE to see if they are less correlated compared to BTC/ETH\n# test_correlation(resample_timeseries(btc_filtered, 'D'), resample_timeseries(doge_filtered, 'D'), 30, 60, 'day'.capitalize())\n\n# Test correlation with Daily crypto data between BTC/BNB to see if they are less correlated compared to BTC/ETH\ntest_correlation(resample_timeseries(btc, 'D'), resample_timeseries(bnb, 'D'), 30, 60, 'day'.capitalize())","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Stationarity","metadata":{}},{"cell_type":"code","source":"from statsmodels.tsa.stattools import adfuller\n\nFORMATTER = mpl_dates.DateFormatter('%Y-%m-%d')\nbtc_hourly = resample_timeseries(btc) \neth_hourly = resample_timeseries(eth)\ndoge_hourly = resample_timeseries(doge) \n\n\ndef plot_series(series, asset):\n    \n    plt.figure(figsize=(15,8))\n    plt.plot(series, linestyle='solid')\n    plt.gca().xaxis.set_major_formatter(FORMATTER) # set date format\n    \n    plt.title(f\"{asset} closing prices\", fontweight='bold')\n    plt.xlabel('Date')\n    plt.ylabel('Price')\n    plt.show()\n\ndef check_stationarity(series, asset):\n\n    result = adfuller(series.values)\n    \n    print(BOLD + f\"Stationary Test for {asset}\" + END + '\\n')\n    print('ADF Statistic: %f' % result[0])\n    print('p-value: %f' % result[1])\n    print('Critical Values:')\n    for key, value in result[4].items():\n        print('\\t%s: %.3f' % (key, value))\n\n    if (result[1] <= 0.05) & (result[4]['5%'] > result[0]):\n        print(RED + \"Stationary\" + END)\n    else:\n        print(RED + \"Non-stationary\" + END)\n    print('\\n')\n    \n    plot_series(series, asset)\n           \ncheck_stationarity(btc_hourly.close, 'BTC'), check_stationarity(eth_hourly.close, 'ETH'), check_stationarity(doge_hourly.close, 'DOGE')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Feature Engineering\n\nFeatures considered:\n\n* Price transformation features\n* RSI\n* Historical Volatility\n* Momentum features\n* Rolling Average Volume","metadata":{}},{"cell_type":"code","source":"from math import sqrt, log\n\n\ndef close_close_volatility(df,window_size, N=365): # N=365 because crypto markets don't close \n    # Compute log returns using close prices \n    df['log_returns'] = np.log(df['close'] / df['close'].shift(1))\n    \n    # Compute historical volatility\n    return df.log_returns.rolling(window_size).std() * np.sqrt(N)\n\ndef calc_daily_features(df):\n    \n    df['date'] = pd.to_datetime(df.index.date)\n    df['date'] = df['date'].dt.strftime('%Y%m%d').astype(int)\n    \n    # Momentum \n    df['rsi_14d'] = RSI(df.close, timeperiod=14)\n    df['bbands_upper'], df['bbands_middle'], df['bbands_lower'] = BBANDS(df.close, timeperiod=10, nbdevup=2, nbdevdn=2)\n    \n    # SMA\n    df['sma_10d'] = df.close.rolling(window=10).mean()\n    df['sma_20d'] = df.close.rolling(window=20).mean()\n    df['sma_30d'] = df.close.rolling(window=30).mean()\n    \n    # Volume moving average\n    df['avg_volume_10d'] = df['volume'].rolling(window=10).mean()\n    df['avg_volume_20d'] = df['volume'].rolling(window=20).mean()\n    df['avg_volume_30d'] = df['volume'].rolling(window=30).mean()\n\n    # Volatility\n    df['close_close_vol_10d'] = close_close_volatility(df,10)\n    df['close_close_vol_20d'] = close_close_volatility(df,20)\n    df['close_close_vol_30d'] = close_close_volatility(df,30)\n    df['atr_14d'] = ATR(df.high, df.low, df.close, timeperiod=14)\n    \n    return df.iloc[:, 8:] \n    \n\ndef calc_features(df, daily=True):\n    \n#     df = reduce_mem_usage(df)\n    \n    CLOSE = df.close\n    DF_DAILY = resample_timeseries(df, 'D')\n    \n       # Price transformation features\n    df['avg_price'] = (df['close'] + df['open'] + df['low'] + df['high']) / 4\n    df['weighted_close'] = ((df['close'] * 2) + df['high'] + df['low']) / 4        # extra weight on the close pri\n    df['typical_price'] = (df['high'] + df['low'] + df['close']) / 3 \n    df['median_price'] = (df['high'] + df['low']) / 2\n    \n    df['dollar_volume'] = df['close'].mul(df['volume'])\n    df['volume_per_trade'] = df['volume'].div(df['interval_trade_count'])\n    df['dollar_volume_per_trade'] = df['dollar_volume'].div(df['interval_trade_count'])\n    \n    df_features_daily = calc_daily_features(DF_DAILY) \n\n    # Join daily features back to original df which contains minute data\n    if daily:\n        return df_features_daily\n    else:\n        df['timestamp'] = df.index\n        df = df.merge(df_features_daily, on='date', how='left').set_index(df['timestamp']).drop(columns='timestamp')\n        df = df.dropna(subset=['close_close_vol_30d'])\n        return df ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"btc_feat = calc_features(btc, False) \ndoge_feat = calc_features(doge, False)\nbtc_feat.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# %%time\n# # Generate dictionary of Dataframes for all tickers and store in dict..\n# def generate_crypto_dfs_with_features():\n#     \"\"\"Returns a dictionary of dataframes with key(Ticker):value(Dataframe with generated features that belongs to that ticker)\"\"\"\n    \n#     return {ticker:generate_features(df_train.query(\"ticker == @ticker\"), False) for ticker in tickers}\n\n# all_cryptos_feat = generate_crypto_dfs_with_features()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## How many targets of BTC are null\nprint(f\"Number of BTC target values that are null: {btc_feat.target.isnull().sum()}\")\nprint(f\"Number of DOGE target values that are null: {doge_feat.target.isnull().sum()}\")\n\n## How many total null values\nbtc_feat.isnull().sum()\nbtc_feat.isna().sum()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"## Recreate target\ndef recreate_target(df):\n    price_column = df.close\n    df['p1'] = df[price_column].shift(freq='-1T')\n    df['p16'] = df[price_column].shift(freq='-16T')\n    df['r'] = np.log(df.p16/ df.p1)\n    df.drop(['p1', 'p16'], axis=1, inplace=True)\n    df.reset_index(inplace=True)\n    \n    return df\n\n\n\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b>TODO</b>","metadata":{}},{"cell_type":"markdown","source":"# Modeling ","metadata":{}},{"cell_type":"markdown","source":"## Linear Regression","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\n\nbtc_feat = btc_feat.dropna(how=\"any\")\nbtc_y = btc_feat['target']\nfeatures = ['vwap', 'weighted_close', 'avg_price', 'median_price', 'typical_price', 'sma_10d', 'sma_20d', 'rsi_14d', 'atr_14d', 'close_close_vol_10d', \\\n           'close_close_vol_20d'] \n\n\nbtc_y = btc_feat['target']\nbtc_x = pd.DataFrame(btc_feat, columns = features)\n\nprint(btc_y.shape)\nprint(btc_x.shape)\nbtc_x.head()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train, X_test, y_train, y_test = \\\n    train_test_split(btc_x.values, btc_y.values, test_size = 0.25, random_state = 1)\n\n\ny_train = y_train.reshape(-1,1)\ny_test = y_test.reshape(-1,1)\n\nprint(X_train.shape)\nprint(X_test.shape)\nprint(y_train.shape)\nprint(y_test.shape)\nprint(y_train)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"scaler = StandardScaler()\n\nX_train_scaled = scaler.fit_transform(X_train)\nX_test_scaled = scaler.transform(X_test).astype(float)\n\nprint(X_train_scaled)\nprint(X_train_scaled.shape)\nprint(X_test_scaled.shape)\nprint(y_train.shape)\nprint(y_test.shape)\n\n# X_train_scaled.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Linear Regression\n\n# from sklearn.linear_model import LinearRegression\n\n# # implement basic ML baseline (one per asset)\n# lr = LinearRegression()\n# lr.fit(X_train_scaled, y_train)\n# y_pred_lr = lr.predict(X_test_scaled)\n\n# models\nfrom xgboost import XGBRegressor\nimport lightgbm as lgb\n\n# parameters\nparams = {\n        'objective': 'regression',\n        'metric': 'rmse',\n        'boosting_type': 'gbdt',\n        'max_depth': -1,\n        'learning_rate': 0.01\n        }\n\n# train (full model)\nmodel = lgb.LGBMRegressor(**params)\nmodel.fit(X_train_scaled, y_train, verbose=-1)\n\ny_pred = model.predict(X_test_scaled)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"first_pred = y_pred[0]\nprint(first_pred)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Feature importance\n# feature importance\n\ndef feature_importance():\n    fi_df = pd.DataFrame()\n    fi_df['features'] = features\n    f_df = fi_df[:-1]\n    fi_df['importance'] = model.booster_.feature_importance(importance_type=\"gain\")\n    \n\n    # plot feature importance\n    fig, ax = plt.subplots(1, 1, figsize=(7, 15))\n    sns.barplot(\n    x='importance', \n    y='features',\n    data=fi_df.sort_values(by=['importance'], ascending=False),\n    ax=ax)\n\nfeature_importance()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## Evaluate\nfrom sklearn.metrics import r2_score\n\nfrom sklearn.metrics import mean_absolute_error, mean_squared_error\nfrom math import sqrt\n\ndef mae(observation, forecast):    \n    error = mean_absolute_error(observation, forecast)\n    print('Mean Absolute Error (MAE): {:.3g}'.format(error))\n    return error\n\ndef mape(observation, forecast): \n    observation, forecast = np.array(observation), np.array(forecast)\n    # Might encounter division by zero error when observation is zero\n    error = np.mean(np.abs((observation - forecast) / observation)) * 100\n    print('Mean Absolute Percentage Error (MAPE): {:.3g}'.format(error))\n    return error\n\ndef rmse(observation, forecast):\n    error = sqrt(mean_squared_error(observation, forecast))\n    print('Root Mean Square Error (RMSE): {:.3g}'.format(error))\n    return error\n\ndef evaluate(pd_dataframe, observation, forecast):\n    \"\"\"\n    :params\n    :pd_dataframe pandas dataframe\n    :observation column name of expected values\n    :forecast column name of forecasted values\n    :return the results of MAE, MAPE and RMSE, respectively\n    \"\"\"\n    first_valid_date = pd_dataframe[forecast].first_valid_index()\n    mae_error = mae(pd_dataframe[observation].loc[first_valid_date:, ], pd_dataframe[forecast].loc[first_valid_date:, ])\n    mape_error = mape(pd_dataframe[observation].loc[first_valid_date:, ], pd_dataframe[forecast].loc[first_valid_date:, ])\n    rmse_error = rmse(pd_dataframe[observation].loc[first_valid_date:, ], pd_dataframe[forecast].loc[first_valid_date:, ]) \n\n    ax = pd_dataframe.loc[:, [observation, forecast]].plot()\n    ax.xaxis.label.set_visible(False)\n    \n#     return mae_error, mape_error, rmse_error\n#     print()\n\n# evaluate(X_test_scaled, y_test, y_pred)\n\n\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"# Evaluation","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"# Submission","metadata":{}},{"cell_type":"code","source":"# env = gresearch_crypto.make_env()\n# iter_test = env.iter_test()\n\n# for i, (df_test, df_pred) in enumerate(iter_test):\n#     for j , row in df_test.iterrows():        \n#         if new_models[row['Asset_ID']] is not None:\n#             try:\n#                 model = new_models[row['Asset_ID']]\n#                 x_test = get_features(row)\n#                 y_pred = model.predict(pd.DataFrame([x_test]))[0]\n#                 df_pred.loc[df_pred['row_id'] == row['row_id'], 'Target'] = y_pred\n#             except:\n#                 df_pred.loc[df_pred['row_id'] == row['row_id'], 'Target'] = 0\n#                 traceback.print_exc()\n#         else: \n#             df_pred.loc[df_pred['row_id'] == row['row_id'], 'Target'] = 0  \n    \n#     env.predict(df_pred)\n\n# import gresearch_crypto\n# env = gresearch_crypto.make_env()   # initialize the environment\n# iter_test = env.iter_test()    # an iterator which loops over the test set and sample submission\n# for (test_df, sample_prediction_df) in iter_test:\n#     print(test_df)\n#     print('\\n')\n#     print(sample_prediction_df)\n#     # feature engineering\n#     test_df = calc_features(test_df, False)\n    \n#     # inference\n#     sample_prediction_df['Target'] = model.predict(test_df[features])  # make your predictions here\n    \n#     # register your predictions\n#     env.predict(sample_prediction_df)  \n\n# import gresearch_crypto\n# env = gresearch_crypto.make_env()\n# iter_test = env.iter_test()\n\n# for i, (df_test, df_pred) in enumerate(iter_test):\n#     for _, row in df_test.iterrows():\n#         asset_id = int(row['Asset_ID'])\n#         if list_models[asset_id] is not None:\n#             try:\n#                 model  = list_models[asset_id]\n#                 x_test = x_feature(row)\n#                 y_pred = model.predict(pd.DataFrame([x_test]))[0]\n#                 df_pred.loc[df_pred['row_id'] == row['row_id'], 'Target'] = y_pred\n#             except:\n#                 df_pred.loc[df_pred['row_id'] == row['row_id'], 'Target'] = 0\n#         else: \n#             df_pred.loc[df_pred['row_id'] == row['row_id'], 'Target'] = 0\n        \n#     env.predict(df_pred)","metadata":{"execution":{"iopub.status.busy":"2022-01-19T03:36:58.920635Z","iopub.execute_input":"2022-01-19T03:36:58.921317Z","iopub.status.idle":"2022-01-19T03:36:59.076316Z","shell.execute_reply.started":"2022-01-19T03:36:58.921204Z","shell.execute_reply":"2022-01-19T03:36:59.075100Z"},"trusted":true},"execution_count":null,"outputs":[]}]}