{"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":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom datetime import datetime\ndata_folder = \"../input/g-research-crypto-forecasting/\"\n!ls $data_folder","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:40:03.381353Z","iopub.execute_input":"2021-11-29T11:40:03.382149Z","iopub.status.idle":"2021-11-29T11:40:04.176958Z","shell.execute_reply.started":"2021-11-29T11:40:03.382025Z","shell.execute_reply":"2021-11-29T11:40:04.175868Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"crypto_df = pd.read_csv(data_folder + 'train.csv')\nasset_details = pd.read_csv(data_folder + 'asset_details.csv')\nasset_details","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:40:04.179050Z","iopub.execute_input":"2021-11-29T11:40:04.179327Z","iopub.status.idle":"2021-11-29T11:41:07.220172Z","shell.execute_reply.started":"2021-11-29T11:40:04.179295Z","shell.execute_reply":"2021-11-29T11:41:07.219375Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"btc = crypto_df[crypto_df[\"Asset_ID\"]==1].set_index(\"timestamp\")","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:07.221801Z","iopub.execute_input":"2021-11-29T11:41:07.222805Z","iopub.status.idle":"2021-11-29T11:41:07.614512Z","shell.execute_reply.started":"2021-11-29T11:41:07.222743Z","shell.execute_reply":"2021-11-29T11:41:07.613578Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"btc.head()","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:07.615937Z","iopub.execute_input":"2021-11-29T11:41:07.616482Z","iopub.status.idle":"2021-11-29T11:41:07.632891Z","shell.execute_reply.started":"2021-11-29T11:41:07.616426Z","shell.execute_reply":"2021-11-29T11:41:07.632149Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(btc)","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:07.635193Z","iopub.execute_input":"2021-11-29T11:41:07.635422Z","iopub.status.idle":"2021-11-29T11:41:07.646210Z","shell.execute_reply.started":"2021-11-29T11:41:07.635397Z","shell.execute_reply":"2021-11-29T11:41:07.645583Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load libraries\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom pandas import read_csv, set_option\nfrom pandas.plotting import scatter_matrix\nimport seaborn as sns\nfrom sklearn.preprocessing import StandardScaler\nimport datetime\nimport math\nfrom numpy.random import choice\nimport random\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n#Import Model Packages for reinforcement learning\nfrom tensorflow.keras import layers, models, optimizers\nfrom tensorflow.keras import backend as K\nfrom collections import namedtuple, deque\nimport warnings","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:07.647642Z","iopub.execute_input":"2021-11-29T11:41:07.648596Z","iopub.status.idle":"2021-11-29T11:41:14.160825Z","shell.execute_reply.started":"2021-11-29T11:41:07.648544Z","shell.execute_reply":"2021-11-29T11:41:14.159927Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"btc.describe()","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:14.162334Z","iopub.execute_input":"2021-11-29T11:41:14.162642Z","iopub.status.idle":"2021-11-29T11:41:14.699384Z","shell.execute_reply.started":"2021-11-29T11:41:14.162566Z","shell.execute_reply":"2021-11-29T11:41:14.698827Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"btc.Close.plot()","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:14.700519Z","iopub.execute_input":"2021-11-29T11:41:14.700857Z","iopub.status.idle":"2021-11-29T11:41:15.310372Z","shell.execute_reply.started":"2021-11-29T11:41:14.700829Z","shell.execute_reply":"2021-11-29T11:41:15.309764Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Checking for any null values and removing the null values'''\nprint('Null Values =',btc.Close.isnull().values.any())","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:15.311468Z","iopub.execute_input":"2021-11-29T11:41:15.311792Z","iopub.status.idle":"2021-11-29T11:41:15.318963Z","shell.execute_reply.started":"2021-11-29T11:41:15.311764Z","shell.execute_reply":"2021-11-29T11:41:15.318147Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X = btc.Close.to_numpy()","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:15.322216Z","iopub.execute_input":"2021-11-29T11:41:15.323002Z","iopub.status.idle":"2021-11-29T11:41:15.327544Z","shell.execute_reply.started":"2021-11-29T11:41:15.322964Z","shell.execute_reply":"2021-11-29T11:41:15.326920Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X= X[::15]","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:15.328665Z","iopub.execute_input":"2021-11-29T11:41:15.329559Z","iopub.status.idle":"2021-11-29T11:41:15.340406Z","shell.execute_reply.started":"2021-11-29T11:41:15.329517Z","shell.execute_reply":"2021-11-29T11:41:15.339706Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(X)","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:15.341777Z","iopub.execute_input":"2021-11-29T11:41:15.342258Z","iopub.status.idle":"2021-11-29T11:41:15.354378Z","shell.execute_reply.started":"2021-11-29T11:41:15.342216Z","shell.execute_reply":"2021-11-29T11:41:15.353740Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"validation_size = 0.2\n#In case the data is not dependent on the time series, then train and test split should be done based on sequential sample\n#This can be done by selecting an arbitrary split point in the ordered list of observations and creating two new datasets.\ntrain_size = int(len(X) * (1-validation_size))\nX_train, X_test = X[0:train_size], X[train_size:len(X)]","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:15.355757Z","iopub.execute_input":"2021-11-29T11:41:15.356329Z","iopub.status.idle":"2021-11-29T11:41:15.366798Z","shell.execute_reply.started":"2021-11-29T11:41:15.356287Z","shell.execute_reply":"2021-11-29T11:41:15.366181Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import tensorflow\n# NEW on TPU in TensorFlow 24: shorter cross-compatible TPU/GPU/multi-GPU/cluster-GPU detection code\n\ntry: # detect TPUs\n    tpu = tensorflow.distribute.cluster_resolver.TPUClusterResolver.connect() # TPU detection\n    tpu_strategy = tensorflow.distribute.TPUStrategy(tpu)\n    print(\"rah\")\nexcept ValueError: # detect GPUs\n    tpu_strategy = tensorflow.distribute.MirroredStrategy() # for GPU or multi-GPU machines\n    #strategy = tf.distribute.get_strategy() # default strategy that works on CPU and single GPU\n    #strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy() # for clusters of multi-GPU machines\n\nprint(\"Number of accelerators: \", tpu_strategy.num_replicas_in_sync)","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:15.370710Z","iopub.execute_input":"2021-11-29T11:41:15.370959Z","iopub.status.idle":"2021-11-29T11:41:21.186327Z","shell.execute_reply.started":"2021-11-29T11:41:15.370930Z","shell.execute_reply":"2021-11-29T11:41:21.185503Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow import keras\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.models import load_model\nfrom tensorflow.keras.layers import Dense\n#from keras.optimizers import Adam\nfrom tensorflow.keras.optimizers import Adam\nfrom IPython.core.debugger import set_trace\n\nimport numpy as np\nimport random\nfrom collections import deque\n\nclass Agent:\n    def __init__(self, state_size, is_eval=False, model_name=\"\"):\n        #State size depends and is equal to the the window size, n previous days\n        self.state_size = state_size # normalized previous days, \n        self.action_size = 3 # sit, buy, sell\n        self.memory = deque(maxlen=1000)\n        self.inventory = []\n        self.model_name = model_name\n        self.is_eval = is_eval\n\n        self.gamma = 0.99\n        self.epsilon = 1.0\n        self.epsilon_min = 0.01\n        self.epsilon_decay = 0.995\n        #self.epsilon_decay = 0.9\n        \n        #self.model = self._model()\n\n        self.model = load_model(model_name) if is_eval else self._model()\n\n    #Deep Q Learning model- returns the q-value when given state as input \n    def _model(self):\n        tpu = tensorflow.distribute.cluster_resolver.TPUClusterResolver.connect() # TPU detection\n        tpu_strategy = tensorflow.distribute.TPUStrategy(tpu)\n        with tpu_strategy.scope():\n            model = Sequential()\n            #Input Layer\n            model.add(Dense(units=64, input_dim=self.state_size, activation=\"relu\"))\n            #Hidden Layers\n            model.add(Dense(units=32, activation=\"relu\"))\n            model.add(Dense(units=8, activation=\"relu\"))\n            #Output Layer \n            model.add(Dense(self.action_size, activation=\"linear\"))\n            model.compile(loss=\"mse\", optimizer=Adam(lr=0.001))\n        return model\n    \n    #Return the action on the value function\n    #With probability (1-$\\epsilon$) choose the action which has the highest Q-value.\n    #With probability ($\\epsilon$) choose any action at random.\n    #Intitially high epsilon-more random, later less\n    #The trained agents were evaluated by different initial random condition\n    #and an e-greedy policy with epsilon 0.05. This procedure is adopted to minimize the possibility of overfitting during evaluation.\n \n    def act(self, state): \n        #If it is test and self.epsilon is still very high, once the epsilon become low, there are no random\n        #actions suggested.\n        if not self.is_eval and random.random() <= self.epsilon:\n            return random.randrange(self.action_size)        \n        options = self.model.predict(state)\n        #set_trace()\n        #action is based on the action that has the highest value from the q-value function.\n        return np.argmax(options[0])\n\n    def expReplay(self, batch_size):\n        mini_batch = []\n        l = len(self.memory)\n        for i in range(l - batch_size + 1, l):\n            mini_batch.append(self.memory[i])\n        \n        # the memory during the training phase. \n        for state, action, reward, next_state, done in mini_batch:\n            target = reward # reward or Q at time t    \n            #update the Q table based on Q table equation\n            #set_trace()\n            if not done:\n                #set_trace()\n                #max of the array of the predicted. \n                target = reward + self.gamma * np.amax(self.model.predict(next_state)[0])     \n                \n            # Q-value of the state currently from the table    \n            target_f = self.model.predict(state)\n            # Update the output Q table for the given action in the table     \n            target_f[0][action] = target\n            #train and fit the model where state is X and target_f is Y, where the target is updated. \n            self.model.fit(state, target_f, epochs=1, verbose=0)\n        \n        if self.epsilon > self.epsilon_min:\n            self.epsilon *= self.epsilon_decay","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:21.188038Z","iopub.execute_input":"2021-11-29T11:41:21.188384Z","iopub.status.idle":"2021-11-29T11:41:21.207603Z","shell.execute_reply.started":"2021-11-29T11:41:21.188344Z","shell.execute_reply":"2021-11-29T11:41:21.206889Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# prints formatted price\ndef formatPrice(n):\n    return (\"-$\" if n < 0 else \"$\") + \"{0:.2f}\".format(abs(n))\n\n# # returns the vector containing stock data from a fixed file \n# def getStockData(key):\n#     vec = []\n#     lines = open(\"data/\" + key + \".csv\", \"r\").read().splitlines()\n\n#     for line in lines[1:]:\n#         vec.append(float(line.split(\",\")[4])) #Only Close column\n\n#     return vec\n\n# returns the sigmoid\ndef sigmoid(x):\n    return 1 / (1 + np.exp(-x))\n\n# returns an an n-day state representation ending at time t\n\ndef getState(data, t, n):    \n    d = t - n + 1\n    block = data[d:t + 1] if d >= 0 else -d * [data[0]] + list(data[0:t + 1]) # pad with t0\n    #block is which is the for [1283.27002, 1283.27002]\n    res = []\n    for i in range(n - 1):\n        res.append(sigmoid(block[i + 1] - block[i]))\n    return np.array([res])\n\n# Plots the behavior of the output\ndef plot_behavior(data_input, states_buy, states_sell, profit):\n    fig = plt.figure(figsize = (20,7))\n    plt.plot(data_input, color='r', lw=2.)\n    plt.plot(data_input, '^', markersize=2, color='m', label = 'Buying signal', markevery = states_buy)\n    plt.plot(data_input, 'v', markersize=2, color='k', label = 'Selling signal', markevery = states_sell)\n    plt.title('Total gains: %f'%(profit))\n    plt.legend()\n    #plt.savefig('output/'+name+'.png')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:21.209188Z","iopub.execute_input":"2021-11-29T11:41:21.209538Z","iopub.status.idle":"2021-11-29T11:41:21.229250Z","shell.execute_reply.started":"2021-11-29T11:41:21.209495Z","shell.execute_reply":"2021-11-29T11:41:21.228584Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from IPython.core.debugger import set_trace\nwindow_size = 1\nagent = Agent(window_size)\n#In this step we feed the closing value of the stock price \ndata = X_train\nl = len(data) - 1\n#\n# batch_size = 32\nbatch_size = 1024\n#An episode represents a complete pass over the data.\nepisode_count = 10\n\nfor e in range(episode_count + 1):\n    print(\"Running episode \" + str(e) + \"/\" + str(episode_count))\n    state = getState(data, 0, window_size + 1)\n    #set_trace()\n    total_profit = 0\n    agent.inventory = []\n    states_sell = []\n    states_buy = []\n    for t in range(l):\n        if t%5000==0:\n            print(\"Running episode \" + str(e) + \"/\" + str(episode_count), \"t is \", t)\n        action = agent.act(state)    \n        # sit\n        next_state = getState(data, t + 1, window_size + 1)\n        reward = 0\n\n        if action == 1: # buy\n            agent.inventory.append(data[t])\n            states_buy.append(t)\n            #print(\"Buy: \" + formatPrice(data[t]))\n\n        elif action == 2 and len(agent.inventory) > 0: # sell\n            bought_price = agent.inventory.pop(0)      \n            reward = max(data[t] - bought_price, 0)\n            total_profit += data[t] - bought_price\n            states_sell.append(t)\n            #print(\"Sell: \" + formatPrice(data[t]) + \" | Profit: \" + formatPrice(data[t] - bought_price))\n\n        done = True if t == l - 1 else False\n        #appends the details of the state action etc in the memory, which is used further by the exeReply function\n        agent.memory.append((state, action, reward, next_state, done))\n        state = next_state\n\n        if done:\n            print(\"--------------------------------\")\n            print(\"Total Profit: \" + formatPrice(total_profit))\n            print(\"--------------------------------\")\n            #set_trace()\n            #pd.DataFrame(np.array(agent.memory)).to_csv(\"Agent\"+str(e)+\".csv\")\n            #Chart to show how the model performs with the stock goin up and down for each \n            plot_behavior(data,states_buy, states_sell, total_profit)\n        if len(agent.memory) > batch_size:\n            agent.expReplay(batch_size)    \n            \n\n    if e % 2 == 0:\n        agent.model.save(\"model_ep\" + str(e)+\".h5\")","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:21.231020Z","iopub.execute_input":"2021-11-29T11:41:21.231267Z","iopub.status.idle":"2021-11-29T11:41:55.117429Z","shell.execute_reply.started":"2021-11-29T11:41:21.231238Z","shell.execute_reply":"2021-11-29T11:41:55.116505Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_behavior(data_input, states_buy, states_sell, profit):\n    fig = plt.figure(figsize = (20,7))\n    plt.plot(data_input, color='r', lw=2.)\n    plt.plot(data_input, '^', markersize=8, color='m', label = 'Buying signal', markevery = states_buy)\n    plt.plot(data_input, 'v', markersize=8, color='k', label = 'Selling signal', markevery = states_sell)\n    plt.title('Total gains: %f'%(profit))\n    plt.legend()\n    #plt.savefig('output/'+name+'.png')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:55.118943Z","iopub.execute_input":"2021-11-29T11:41:55.119280Z","iopub.status.idle":"2021-11-29T11:41:55.127221Z","shell.execute_reply.started":"2021-11-29T11:41:55.119237Z","shell.execute_reply":"2021-11-29T11:41:55.126225Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"n = len(data)","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:55.128423Z","iopub.execute_input":"2021-11-29T11:41:55.128686Z","iopub.status.idle":"2021-11-29T11:41:55.148354Z","shell.execute_reply.started":"2021-11-29T11:41:55.128657Z","shell.execute_reply":"2021-11-29T11:41:55.147695Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"res = list(map(lambda i: i> n-600, states_buy)).index(True)\nres2 = list(map(lambda i: i> n-600, states_sell)).index(True)\nprint(states_buy[res-1:res+3])\nprint(states_sell[res2-1:res2+3])","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:55.149583Z","iopub.execute_input":"2021-11-29T11:41:55.149924Z","iopub.status.idle":"2021-11-29T11:41:55.175334Z","shell.execute_reply.started":"2021-11-29T11:41:55.149893Z","shell.execute_reply":"2021-11-29T11:41:55.174538Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_behavior(data[n-600:],np.array(states_buy[res:])-(n-600), np.array(states_sell[res2:])-(n-600), total_profit)","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:41:55.176698Z","iopub.execute_input":"2021-11-29T11:41:55.176928Z","iopub.status.idle":"2021-11-29T11:41:55.444195Z","shell.execute_reply.started":"2021-11-29T11:41:55.176902Z","shell.execute_reply":"2021-11-29T11:41:55.443498Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Testing data","metadata":{}},{"cell_type":"code","source":"#agent is already defined in the training set above.\ntest_data = X_test\nl_test = len(test_data) - 1\nstate = getState(test_data, 0, window_size + 1)\ntotal_profit = 0\nis_eval = True\ndone = False\nstates_sell_test = []\nstates_buy_test = []\n#Get the trained model\n#model_name = \"model_ep\"+str(episode_count)+\".h5\"\n#agent = Agent(window_size, is_eval, model_name)\nstate = getState(data, 0, window_size + 1)\ntotal_profit = 0\nagent.inventory = []","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:43:14.139658Z","iopub.execute_input":"2021-11-29T11:43:14.139971Z","iopub.status.idle":"2021-11-29T11:43:14.147030Z","shell.execute_reply.started":"2021-11-29T11:43:14.139938Z","shell.execute_reply":"2021-11-29T11:43:14.145923Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"done = False\nfor t in range(l_test):\n    action = agent.act(state)\n    #print(\"step t,{}, action: {}\".format(t,action))\n    #set_trace()\n    next_state = getState(test_data, t + 1, window_size + 1)\n    reward = 0\n    #if t%500==0:\n        #print(\"step t\",t)\n    if action == 1:\n        agent.inventory.append(test_data[t])\n        states_buy_test.append(t)\n        #if t%30:\n            #print(\"Buy: \" + formatPrice(test_data[t]))\n\n    elif action == 2 and len(agent.inventory) > 0:\n        bought_price = agent.inventory.pop(0)\n        reward = max(test_data[t] - bought_price, 0)\n        #reward = test_data[t] - bought_price\n        total_profit += test_data[t] - bought_price\n        states_sell_test.append(t)\n        #if t%30:\n            #print(\"Sell: \" + formatPrice(test_data[t]) + \" | profit: \" + formatPrice(test_data[t] - bought_price))\n\n    if t == l_test - 1:\n        done = True\n        leftOverInv = 0\n        for p in agent.inventory:\n            leftOverInv += p-test_data[-1]\n        total_profit += leftOverInv\n    agent.memory.append((state, action, reward, next_state, done))\n    state = next_state\n\n    if done:\n        print(\"------------------------------------------\")\n        print(\"Total Profit: \" + formatPrice(total_profit))\n        print(\"------------------------------------------\")\n        \nplot_behavior(test_data,states_buy_test, states_sell_test, total_profit)","metadata":{"execution":{"iopub.status.busy":"2021-11-29T11:43:18.052518Z","iopub.execute_input":"2021-11-29T11:43:18.052827Z","iopub.status.idle":"2021-11-29T11:43:18.817924Z","shell.execute_reply.started":"2021-11-29T11:43:18.052798Z","shell.execute_reply":"2021-11-29T11:43:18.817239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}