{"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":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\n#tf.config.run_functions_eagerly(True)\n\nimport math","metadata":{"papermill":{"duration":9.081078,"end_time":"2021-12-13T07:45:31.947867","exception":false,"start_time":"2021-12-13T07:45:22.866789","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2022-02-01T16:30:19.931918Z","iopub.execute_input":"2022-02-01T16:30:19.932229Z","iopub.status.idle":"2022-02-01T16:30:24.142057Z","shell.execute_reply.started":"2022-02-01T16:30:19.932149Z","shell.execute_reply":"2022-02-01T16:30:24.141143Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Library N-BEATS Keras","metadata":{"papermill":{"duration":0.049519,"end_time":"2021-12-13T07:50:24.767869","exception":false,"start_time":"2021-12-13T07:50:24.71835","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import tensorflow.experimental.numpy as tnp\nfrom tensorflow.keras import backend as K\nfrom tensorflow.keras.layers import Concatenate\nfrom tensorflow.keras.layers import Input, Dense, Lambda, Subtract, Add, Reshape\nfrom tensorflow.keras.models import Model\n\n\ndef smape_loss(y_true, y_pred):\n    \"\"\"\n    sMAPE loss as defined in \"Appendix A\" of\n    http://www.forecastingprinciples.com/files/pdf/Makridakia-The%20M3%20Competition.pdf\n    :return: Loss value\n    \"\"\"\n    # mask=tf.where(y_true,1.,0.)\n    mask = tf.cast(y_true, tf.bool)\n    mask = tf.cast(mask, tf.float32)\n    sym_sum = tf.abs(y_true) + tf.abs(y_pred)\n    condition = tf.cast(sym_sum, tf.bool)\n    weights = tf.where(condition, 1. / (sym_sum + 1e-8), 0.0)\n    return 200 * tnp.nanmean(tf.abs(y_pred - y_true) * weights * mask)\n\n\nclass NBeatsNet:\n    GENERIC_BLOCK = 'generic'\n    TREND_BLOCK = 'trend'\n    SEASONALITY_BLOCK = 'seasonality'\n\n    _BACKCAST = 'backcast'\n    _FORECAST = 'forecast'\n\n    def __init__(self,\n                 input_dim=1,\n                 output_dim=1,\n                 exo_dim=0,\n                 backcast_length=10,\n                 forecast_length=1,\n                 stack_types=(TREND_BLOCK, SEASONALITY_BLOCK),\n                 nb_blocks_per_stack=3,\n                 thetas_dim=(4, 8),\n                 share_weights_in_stack=False,\n                 hidden_layer_units=256,\n                 nb_harmonics=None):\n\n        self.stack_types = stack_types\n        self.nb_blocks_per_stack = nb_blocks_per_stack\n        self.thetas_dim = thetas_dim\n        self.units = hidden_layer_units\n        self.share_weights_in_stack = share_weights_in_stack\n        self.backcast_length = backcast_length\n        self.forecast_length = forecast_length\n        self.input_dim = input_dim\n        self.output_dim = output_dim\n        self.exo_dim = exo_dim\n        self.input_shape = (self.backcast_length, self.input_dim)\n        self.exo_shape = (self.backcast_length, self.exo_dim)\n        self.output_shape = (self.forecast_length, self.output_dim)\n        self.weights = {}\n        self.nb_harmonics = nb_harmonics\n        assert len(self.stack_types) == len(self.thetas_dim)\n\n        x = Input(shape=self.input_shape, name='input_variable')\n        x_ = {}\n        for k in range(self.input_dim):\n            x_[k] = Lambda(lambda z: z[..., k])(x)\n        e_ = {}\n        if self.has_exog():\n            e = Input(shape=self.exo_shape, name='exos_variables')\n            for k in range(self.exo_dim):\n                e_[k] = Lambda(lambda z: z[..., k])(e)\n        else:\n            e = None\n        y_ = {}\n\n        for stack_id in range(len(self.stack_types)):\n            stack_type = self.stack_types[stack_id]\n            nb_poly = self.thetas_dim[stack_id]\n            for block_id in range(self.nb_blocks_per_stack):\n                backcast, forecast = self.create_block(x_, e_, stack_id, block_id, stack_type, nb_poly)\n                for k in range(self.input_dim):\n                    x_[k] = Subtract()([x_[k], backcast[k]])\n                    if stack_id == 0 and block_id == 0:\n                        y_[k] = forecast[k]\n                    else:\n                        y_[k] = Add()([y_[k], forecast[k]])\n\n        for k in range(self.input_dim):\n            y_[k] = Reshape(target_shape=(self.forecast_length, 1))(y_[k])\n            x_[k] = Reshape(target_shape=(self.backcast_length, 1))(x_[k])\n        if self.input_dim > 1:\n            y_ = Concatenate()([y_[ll] for ll in range(self.input_dim)])\n            x_ = Concatenate()([x_[ll] for ll in range(self.input_dim)])\n        else:\n            y_ = y_[0]\n            x_ = x_[0]\n\n        if self.input_dim != self.output_dim:\n            y_ = Dense(self.output_dim, activation='linear', name='reg_y')(y_)\n            x_ = Dense(self.output_dim, activation='linear', name='reg_x')(x_)\n\n        inputs_x = [x, e] if self.has_exog() else x\n        n_beats_forecast = Model(inputs_x, y_, name=self._FORECAST)\n        n_beats_backcast = Model(inputs_x, x_, name=self._BACKCAST)\n\n        self.models = {model.name: model for model in [n_beats_backcast, n_beats_forecast]}\n        self.cast_type = self._FORECAST\n\n    def has_exog(self):\n        # exo/exog is short for 'exogenous variable', i.e. any input\n        # features other than the target time-series itself.\n        return self.exo_dim > 0\n\n    @staticmethod\n    def load(filepath, custom_objects=None, compile=True):\n        from tensorflow.keras.models import load_model\n        return load_model(filepath, custom_objects, compile)\n\n    def _r(self, layer_with_weights, stack_id):\n        # mechanism to restore weights when block share the same weights.\n        # only useful when share_weights_in_stack=True.\n        if self.share_weights_in_stack:\n            layer_name = layer_with_weights.name.split('/')[-1]\n            try:\n                reused_weights = self.weights[stack_id][layer_name]\n                return reused_weights\n            except KeyError:\n                pass\n            if stack_id not in self.weights:\n                self.weights[stack_id] = {}\n            self.weights[stack_id][layer_name] = layer_with_weights\n        return layer_with_weights\n\n    def create_block(self, x, e, stack_id, block_id, stack_type, nb_poly):\n        # register weights (useful when share_weights_in_stack=True)\n        def reg(layer):\n            return self._r(layer, stack_id)\n\n        # update name (useful when share_weights_in_stack=True)\n        def n(layer_name):\n            return '/'.join([str(stack_id), str(block_id), stack_type, layer_name])\n\n        backcast_ = {}\n        forecast_ = {}\n        d1 = reg(Dense(self.units, activation='relu', name=n('d1')))\n        d2 = reg(Dense(self.units, activation='relu', name=n('d2')))\n        d3 = reg(Dense(self.units, activation='relu', name=n('d3')))\n        d4 = reg(Dense(self.units, activation='relu', name=n('d4')))\n        if stack_type == 'generic':\n            theta_b = reg(Dense(nb_poly, activation='linear', use_bias=False, name=n('theta_b')))\n            theta_f = reg(Dense(nb_poly, activation='linear', use_bias=False, name=n('theta_f')))\n            backcast = reg(Dense(self.backcast_length, activation='linear', name=n('backcast')))\n            forecast = reg(Dense(self.forecast_length, activation='linear', name=n('forecast')))\n        elif stack_type == 'trend':\n            theta_f = theta_b = reg(Dense(nb_poly, activation='linear', use_bias=False, name=n('theta_f_b')))\n            backcast = Lambda(trend_model, arguments={'is_forecast': False, 'backcast_length': self.backcast_length,\n                                                      'forecast_length': self.forecast_length})\n            forecast = Lambda(trend_model, arguments={'is_forecast': True, 'backcast_length': self.backcast_length,\n                                                      'forecast_length': self.forecast_length})\n        else:  # 'seasonality'\n            if self.nb_harmonics:\n                theta_b = reg(Dense(self.nb_harmonics, activation='linear', use_bias=False, name=n('theta_b')))\n            else:\n                theta_b = reg(Dense(self.forecast_length, activation='linear', use_bias=False, name=n('theta_b')))\n            theta_f = reg(Dense(self.forecast_length, activation='linear', use_bias=False, name=n('theta_f')))\n            backcast = Lambda(seasonality_model,\n                              arguments={'is_forecast': False, 'backcast_length': self.backcast_length,\n                                         'forecast_length': self.forecast_length})\n            forecast = Lambda(seasonality_model,\n                              arguments={'is_forecast': True, 'backcast_length': self.backcast_length,\n                                         'forecast_length': self.forecast_length})\n        for k in range(self.input_dim):\n            if self.has_exog():\n                d0 = Concatenate()([x[k]] + [e[ll] for ll in range(self.exo_dim)])\n            else:\n                d0 = x[k]\n            d1_ = d1(d0)\n            d2_ = d2(d1_)\n            d3_ = d3(d2_)\n            d4_ = d4(d3_)\n            theta_f_ = theta_f(d4_)\n            theta_b_ = theta_b(d4_)\n            backcast_[k] = backcast(theta_b_)\n            forecast_[k] = forecast(theta_f_)\n\n        return backcast_, forecast_\n\n    def __getattr__(self, name):\n        # https://github.com/faif/python-patterns\n        # model.predict() instead of model.n_beats.predict()\n        # same for fit(), train_on_batch()...\n        attr = getattr(self.models[self._FORECAST], name)\n\n        if not callable(attr):\n            return attr\n\n        def wrapper(*args, **kwargs):\n            cast_type = self._FORECAST\n            if attr.__name__ == 'predict' and 'return_backcast' in kwargs and kwargs['return_backcast']:\n                del kwargs['return_backcast']\n                cast_type = self._BACKCAST\n            return getattr(self.models[cast_type], attr.__name__)(*args, **kwargs)\n\n        return wrapper\n\n\ndef linear_space(backcast_length, forecast_length, is_forecast=True):\n    # ls = K.arange(-float(backcast_length), float(forecast_length), 1) / forecast_length\n    # return ls[backcast_length:] if is_forecast else K.abs(K.reverse(ls[:backcast_length], axes=0))\n    horizon = forecast_length if is_forecast else backcast_length\n    return K.arange(0, horizon) / horizon\n\n\ndef seasonality_model(thetas, backcast_length, forecast_length, is_forecast):\n    p = thetas.get_shape().as_list()[-1]\n    p1, p2 = (p // 2, p // 2) if p % 2 == 0 else (p // 2, p // 2 + 1)\n    t = linear_space(backcast_length, forecast_length, is_forecast=is_forecast)\n    s1 = K.stack([K.cos(2 * np.pi * i * t) for i in range(p1)])\n    s2 = K.stack([K.sin(2 * np.pi * i * t) for i in range(p2)])\n    if p == 1:\n        s = s2\n    else:\n        s = K.concatenate([s1, s2], axis=0)\n    s = K.cast(s, np.float32)\n    return K.dot(thetas, s)\n\n\ndef trend_model(thetas, backcast_length, forecast_length, is_forecast):\n    p = thetas.shape[-1]\n    t = linear_space(backcast_length, forecast_length, is_forecast=is_forecast)\n    t = K.transpose(K.stack([t ** i for i in range(p)]))\n    t = K.cast(t, np.float32)\n    return K.dot(thetas, K.transpose(t))","metadata":{"execution":{"iopub.status.busy":"2022-02-01T16:30:24.143945Z","iopub.execute_input":"2022-02-01T16:30:24.144249Z","iopub.status.idle":"2022-02-01T16:30:25.162687Z","shell.execute_reply.started":"2022-02-01T16:30:24.144210Z","shell.execute_reply":"2022-02-01T16:30:25.161977Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Importing the model","metadata":{"papermill":{"duration":0.050298,"end_time":"2021-12-13T07:50:25.033659","exception":false,"start_time":"2021-12-13T07:50:24.983361","status":"completed"},"tags":[]}},{"cell_type":"code","source":"num_samples, time_steps, input_dim, output_dim = 2520000, 1, 7, 1","metadata":{"execution":{"iopub.status.busy":"2022-02-01T16:30:25.165117Z","iopub.execute_input":"2022-02-01T16:30:25.165545Z","iopub.status.idle":"2022-02-01T16:30:25.169781Z","shell.execute_reply.started":"2022-02-01T16:30:25.165507Z","shell.execute_reply":"2022-02-01T16:30:25.169188Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = NBeatsNet(\n    backcast_length=time_steps, forecast_length=output_dim, input_dim = input_dim,\n    stack_types=(NBeatsNet.GENERIC_BLOCK, NBeatsNet.GENERIC_BLOCK),\n    nb_blocks_per_stack=2, thetas_dim=(4, 4), share_weights_in_stack=True,\n    hidden_layer_units=64\n)\n\nmodel.compile(optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3), loss = 'mae')","metadata":{"execution":{"iopub.status.busy":"2022-02-01T16:30:25.170826Z","iopub.execute_input":"2022-02-01T16:30:25.171158Z","iopub.status.idle":"2022-02-01T16:30:28.375328Z","shell.execute_reply.started":"2022-02-01T16:30:25.171127Z","shell.execute_reply":"2022-02-01T16:30:28.374603Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model_1 = model.load('../input/pretrainedmodelnbeats/n_beats_model.h5')","metadata":{"execution":{"iopub.status.busy":"2022-02-01T16:30:28.377362Z","iopub.execute_input":"2022-02-01T16:30:28.377610Z","iopub.status.idle":"2022-02-01T16:30:29.462064Z","shell.execute_reply.started":"2022-02-01T16:30:28.377577Z","shell.execute_reply":"2022-02-01T16:30:29.459631Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Prepare test data","metadata":{}},{"cell_type":"code","source":"# calculate different KPI\ndef upper_shadow(df): return df['High'] - np.maximum(df['Close'], df['Open'])\ndef lower_shadow(df): return np.minimum(df['Close'], df['Open']) - df['Low']","metadata":{"execution":{"iopub.status.busy":"2022-02-01T16:30:29.464263Z","iopub.execute_input":"2022-02-01T16:30:29.464675Z","iopub.status.idle":"2022-02-01T16:30:29.473690Z","shell.execute_reply.started":"2022-02-01T16:30:29.464642Z","shell.execute_reply":"2022-02-01T16:30:29.472514Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# feature engineering function, input: data frame; output: data frame with added feature\ndef get_features_test(df_feat):\n    df_feat = df_feat.set_index('timestamp')\n    df_feat['Volume / Count'] = df_feat['Volume'] / df_feat['Count']\n    \n    df_feat['sma15'] = df_feat['Close'].rolling(15, min_periods=1).mean() / df_feat['Close'] - 1\n    \n    df_feat['lower_shadow'] = lower_shadow(df_feat)\n    df_feat['upper_shadow'] = upper_shadow(df_feat)\n    \n    df_feat['Candle_body'] = df_feat['Close'] - df_feat['Open']\n    \n    df_feat = df_feat.drop(columns=['Count', 'Open', 'High', 'Low', 'Volume', 'Asset_ID', 'row_id'])\n    \n    df_feat = df_feat.fillna(method='bfill')\n    df_feat = df_feat.replace(-math.inf, 0)\n    \n    return df_feat","metadata":{"execution":{"iopub.status.busy":"2022-02-01T16:30:29.477015Z","iopub.execute_input":"2022-02-01T16:30:29.478032Z","iopub.status.idle":"2022-02-01T16:30:29.493466Z","shell.execute_reply.started":"2022-02-01T16:30:29.477994Z","shell.execute_reply":"2022-02-01T16:30:29.492694Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Submit To Kaggle","metadata":{"papermill":{"duration":1.100626,"end_time":"2021-12-13T08:08:15.640462","exception":false,"start_time":"2021-12-13T08:08:14.539836","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import gresearch_crypto\nfrom datetime import datetime\n\nenv = gresearch_crypto.make_env()\niter_test = env.iter_test()","metadata":{"execution":{"iopub.status.busy":"2022-02-01T16:30:29.495100Z","iopub.execute_input":"2022-02-01T16:30:29.495407Z","iopub.status.idle":"2022-02-01T16:30:29.535450Z","shell.execute_reply.started":"2022-02-01T16:30:29.495371Z","shell.execute_reply":"2022-02-01T16:30:29.534509Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow.python.ops.numpy_ops import np_config\nnp_config.enable_numpy_behavior()\n\nfor i, (df_test, sample_prediction_df) in enumerate(iter_test):\n    features = get_features_test(df_test)\n    features = tf.convert_to_tensor(features).reshape(-1, 1, 7)\n    y_pred = model_1.predict(features)\n    sample_prediction_df['Target'] = y_pred.ravel()\n    env.predict(sample_prediction_df)","metadata":{"execution":{"iopub.status.busy":"2022-02-01T16:30:29.538952Z","iopub.execute_input":"2022-02-01T16:30:29.539242Z","iopub.status.idle":"2022-02-01T16:30:31.774862Z","shell.execute_reply.started":"2022-02-01T16:30:29.539205Z","shell.execute_reply":"2022-02-01T16:30:31.773989Z"},"trusted":true},"execution_count":null,"outputs":[]}]}