├── .DS_Store ├── .gitignore ├── .ipynb_checkpoints ├── Model-checkpoint.ipynb ├── TheNotebook-checkpoint.ipynb └── ToyModel-checkpoint.ipynb ├── Model.ipynb ├── README.md ├── config.py ├── csvdata.py ├── main.py ├── setup.py ├── sqldata.py ├── src ├── agent.py ├── constants.py ├── data │ ├── __init__.py │ ├── coinlist.py │ ├── datamatrices.py │ ├── globaldatamatrix.py │ ├── poloniex.py │ └── replayBuffer.py ├── network.py ├── tools │ ├── __init__.py │ ├── configprocess.py │ ├── data.py │ ├── indicator.py │ ├── shortcut.py │ └── trade.py ├── trader.py └── trainer.py ├── test.py └── train_package ├── model ├── programlog └── tensorboard ├── graph └── events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.2 ├── test └── events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.1 └── train └── events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.0 /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreaslillevangbech/PortfolioManager-pytorch/2980cfb2a2097b1fc61cecc9d989559717989184/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore pycache 3 | __pycache__/ 4 | # ignore logger 5 | /app.log 6 | # sqlite database 7 | /data.db 8 | # vs code metadata 9 | .vscode/ 10 | # virtualenv 11 | .venv 12 | venv/ 13 | ENV/ 14 | 15 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/Model-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import torch\n", 10 | "import torch.nn as nn\n", 11 | "import torch.nn.functional as F" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "class CNN(nn.Module):\n", 21 | " def __init__(self, in_features, rows, cols, device = torch.device(\"cpu\")):\n", 22 | " super(CNN, self).__init__()\n", 23 | "\n", 24 | " out1 = 2\n", 25 | " out2 = 20\n", 26 | " kernel1 = (1,3)\n", 27 | " kernel2 = (1,cols-2) # cols - (kernel1[1] - 1)\n", 28 | "\n", 29 | " self.conv1 = nn.Conv2d(in_features, out1, kernel1)\n", 30 | " self.conv2 = nn.Conv2d(out1, out2, kernel2)\n", 31 | " self.votes = nn.Conv2d(out2+1, 1, (1,1)) # input features is out2 plus the appended last_weights\n", 32 | " \n", 33 | " # BTC bias\n", 34 | " b = torch.zeros((1,1)) #requires_grad=True)\n", 35 | " self.b = nn.Parameter(b)\n", 36 | "\n", 37 | " def forward(self, x, w):\n", 38 | " x = self.conv1(x)\n", 39 | " x = F.relu(x)\n", 40 | " x = self.conv2(x)\n", 41 | " x = F.relu(x)\n", 42 | " x = torch.cat((x,w),dim=1)\n", 43 | " x = self.votes(x)\n", 44 | " x = torch.squeeze(x)\n", 45 | " \n", 46 | " cash = self.b.repeat(x.size()[0], 1)\n", 47 | " \n", 48 | " x = torch.cat((cash, x), dim=1)\n", 49 | " x = F.softmax(x, dim=1)\n", 50 | " \n", 51 | " return x" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 3, 57 | "metadata": {}, 58 | "outputs": [ 59 | { 60 | "data": { 61 | "text/plain": [ 62 | "torch.Size([16, 6])" 63 | ] 64 | }, 65 | "execution_count": 3, 66 | "metadata": {}, 67 | "output_type": "execute_result" 68 | } 69 | ], 70 | "source": [ 71 | "feat = 2\n", 72 | "window = 10\n", 73 | "coins = 5\n", 74 | "x = torch.rand(16, feat, coins, window)\n", 75 | "w = torch.rand(16, coins)\n", 76 | "w = w[:,None, : , None]\n", 77 | "model = CNN(feat,coins,window)\n", 78 | "out = model(x, w)\n", 79 | "out.shape" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 9, 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "name": "stdout", 89 | "output_type": "stream", 90 | "text": [ 91 | "torch.Size([1, 1])\n", 92 | "torch.Size([2, 2, 1, 3])\n", 93 | "torch.Size([2])\n", 94 | "torch.Size([20, 2, 1, 8])\n", 95 | "torch.Size([20])\n", 96 | "torch.Size([1, 21, 1, 1])\n", 97 | "torch.Size([1])\n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "for p in model.parameters():\n", 103 | " print(p.size())" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 5, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "learning_rate = 1e-4\n", 113 | "# optimizer = torch.optim.Adam([\n", 114 | "# {'params': model.parameters()},\n", 115 | "# {'params': model.b}\n", 116 | "# ], lr=learning_rate)\n", 117 | "\n", 118 | "optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n", 119 | "\n", 120 | "for t in range(10):\n", 121 | " y = model(x,w)\n", 122 | " loss = y.pow(2).sum()\n", 123 | " optimizer.zero_grad()\n", 124 | " loss.backward()\n", 125 | " optimizer.step()" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 6, 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "data": { 135 | "text/plain": [ 136 | "Parameter containing:\n", 137 | "tensor([[0.0010]], requires_grad=True)" 138 | ] 139 | }, 140 | "execution_count": 6, 141 | "metadata": {}, 142 | "output_type": "execute_result" 143 | } 144 | ], 145 | "source": [ 146 | "model.b" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "w = torch.rand(2,2)\n", 156 | "w[:,None,:, None].shape" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": null, 162 | "metadata": {}, 163 | "outputs": [], 164 | "source": [ 165 | "b = torch.zeros((1,1), requires_grad=True)\n", 166 | "b.repeat(5,1).dtype" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "w.size()[0]" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [] 184 | } 185 | ], 186 | "metadata": { 187 | "kernelspec": { 188 | "display_name": "Python 3", 189 | "language": "python", 190 | "name": "python3" 191 | }, 192 | "language_info": { 193 | "codemirror_mode": { 194 | "name": "ipython", 195 | "version": 3 196 | }, 197 | "file_extension": ".py", 198 | "mimetype": "text/x-python", 199 | "name": "python", 200 | "nbconvert_exporter": "python", 201 | "pygments_lexer": "ipython3", 202 | "version": "3.8.5" 203 | } 204 | }, 205 | "nbformat": 4, 206 | "nbformat_minor": 4 207 | } 208 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/TheNotebook-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import tensorflow as tf\n", 10 | "from tensorflow import keras\n", 11 | "\n", 12 | "import numpy as np\n", 13 | "import pandas as pd" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 2, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "config = {\n", 23 | " 'batch_size': 16, \n", 24 | " 'coin_no': 11, \n", 25 | " 'window_size': 50, \n", 26 | " 'feature_no': 3,\n", 27 | " \"test_portion\": 0.08,\n", 28 | " \"global_period\": 1800,\n", 29 | " \"trading_consumption\": 0.0025\n", 30 | " }\n" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 3, 36 | "metadata": {}, 37 | "outputs": [ 38 | { 39 | "name": "stdout", 40 | "output_type": "stream", 41 | "text": [ 42 | "WARNING:tensorflow:Layer cnn is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2. The layer has dtype float32 because it's dtype defaults to floatx.\n", 43 | "\n", 44 | "If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.\n", 45 | "\n", 46 | "To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.\n", 47 | "\n", 48 | "['Variable:0', 'cnn/conv1/kernel:0', 'cnn/conv1/bias:0', 'cnn/conv2/kernel:0', 'cnn/conv2/bias:0', 'cnn/votes/kernel:0', 'cnn/votes/bias:0']\n" 49 | ] 50 | } 51 | ], 52 | "source": [ 53 | "# input size is 11x50x3\n", 54 | "# Remember: Channels last\n", 55 | "\n", 56 | "class CNN(tf.keras.Model):\n", 57 | " \n", 58 | " def __init__(self, rows = 11, cols = 50, features = 3, batch_size=None):\n", 59 | " super(CNN, self).__init__()\n", 60 | " \n", 61 | " self.tensor_shape = (rows, cols, features)\n", 62 | " self.batch_size = batch_size\n", 63 | " \n", 64 | " self.conv1 = tf.keras.layers.Conv2D(\n", 65 | " filters = 2, \n", 66 | " kernel_size = (1,3), \n", 67 | " padding='valid', \n", 68 | " activation='relu',\n", 69 | " name = 'conv1'\n", 70 | " ) \n", 71 | " self.conv2 = keras.layers.Conv2D(\n", 72 | " filters = 20, \n", 73 | " kernel_size = (1, cols - 2), \n", 74 | " activation=\"relu\", \n", 75 | " name = 'conv2'\n", 76 | " )\n", 77 | " self.votes = keras.layers.Conv2D(1, (1,1), name = 'votes')\n", 78 | " self.b = tf.Variable(tf.zeros((1, 1), dtype=tf.float32), trainable=True)\n", 79 | " self.softmax = tf.keras.layers.Activation('softmax')\n", 80 | "\n", 81 | " def call(self, inputs):\n", 82 | " x = self.conv1(inputs[0])\n", 83 | " x = self.conv2(x)\n", 84 | " x = tf.concat((x, inputs[1]), axis=3)\n", 85 | " #x = keras.layers.Concatenate(axis=3)([x, inputs[1]])\n", 86 | " x = self.votes(x)\n", 87 | " x = tf.squeeze(x)\n", 88 | " cash_bias = tf.tile(self.b, [tf.shape(x)[0], 1])\n", 89 | " x = tf.concat((cash_bias, x), axis = -1)\n", 90 | " x = self.softmax(x)\n", 91 | " return x\n", 92 | "\n", 93 | "\n", 94 | "model = CNN()\n", 95 | "\n", 96 | "X = np.random.randn(100, 11,50,3)\n", 97 | "w = np.random.randn(100, 11, 1, 1)\n", 98 | "with tf.GradientTape() as tape:\n", 99 | " y = model([X,w])\n", 100 | "\n", 101 | "print([var.name for var in tape.watched_variables()])\n", 102 | "grads = tape.gradient(y, model.trainable_variables)" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 4, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "from __future__ import division,absolute_import,print_function\n", 112 | "import logging\n", 113 | "\n", 114 | "class ReplayBuffer:\n", 115 | " def __init__(self, start_index, end_index, batch_size, coin_no, sample_bias=1.0):\n", 116 | " \"\"\"\n", 117 | " :param start_index: start index of the training set on the global data matrices\n", 118 | " :param end_index: end index of the training set on the global data matrices\n", 119 | " \"\"\"\n", 120 | " self.__coin_no = coin_no\n", 121 | " self.__experiences = [Experience(i) for i in range(start_index, end_index)]\n", 122 | " # NOTE: in order to achieve the previous w feature\n", 123 | " self.__batch_size = batch_size\n", 124 | " self.__sample_bias = sample_bias\n", 125 | " logging.debug(\"buffer_bias is %f\" % sample_bias)\n", 126 | "\n", 127 | " def append_experience(self, state_index):\n", 128 | " self.__experiences.append(Experience(state_index))\n", 129 | " logging.debug(\"a new experience, indexed by %d, was appended\" % state_index)\n", 130 | "\n", 131 | " def __sample(self, start, end, bias):\n", 132 | " \"\"\"\n", 133 | " @:param end: is excluded\n", 134 | " @:param bias: value in (0, 1)\n", 135 | " \"\"\"\n", 136 | " # TODO: deal with the case when bias is 0\n", 137 | " ran = np.random.geometric(bias)\n", 138 | " while ran > end - start:\n", 139 | " ran = np.random.geometric(bias)\n", 140 | " result = end - ran\n", 141 | " return result\n", 142 | "\n", 143 | " def next_experience_batch(self):\n", 144 | " # First get a start point randomly\n", 145 | " batch_start = self.__sample(0, len(self.__experiences) - self.__batch_size,\n", 146 | " self.__sample_bias)\n", 147 | " batch = self.__experiences[batch_start:batch_start+self.__batch_size]\n", 148 | " return batch\n", 149 | "\n", 150 | "\n", 151 | "class Experience:\n", 152 | " def __init__(self, state_index):\n", 153 | " self.state_index = int(state_index)\n" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 11, 159 | "metadata": {}, 160 | "outputs": [], 161 | "source": [ 162 | "class Agent:\n", 163 | " \n", 164 | " def __init__(self, config):\n", 165 | " self.model = CNN(\n", 166 | " config['coin_no'], \n", 167 | " config['window_size'], \n", 168 | " config['feature_no'], \n", 169 | " config['batch_size']\n", 170 | " )\n", 171 | " \n", 172 | " self.batch_size = config['batch_size']\n", 173 | " self.coin_no = config['coin_no']\n", 174 | " self.window_size = config['window_size']\n", 175 | " self.global_period = config[\"global_period\"]\n", 176 | " self.feature_no = config['feature_no']\n", 177 | " \n", 178 | " self.no_periods = 150\n", 179 | " \n", 180 | " self.commission_ratio = config[\"trading_consumption\"]\n", 181 | " \n", 182 | " #Just make something random\n", 183 | " self.global_data = np.random.rand(self.feature_no, self.coin_no, self.no_periods)\n", 184 | " \n", 185 | " PVM = np.ones((self.global_data.shape[2], self.global_data.shape[1]))/self.coin_no\n", 186 | " self.PVM = pd.DataFrame(PVM)\n", 187 | " \n", 188 | " # Notice this part is made with pandas.panel\n", 189 | "# # portfolio vector memory, [time, assets]\n", 190 | "# self.__PVM = pd.DataFrame(index=self.__global_data.minor_axis,\n", 191 | "# columns=self.__global_data.major_axis)\n", 192 | "# self.__PVM = self.__PVM.fillna(1.0 / self.__coin_no)\n", 193 | "\n", 194 | " \n", 195 | " self.pv_vector = None\n", 196 | " \n", 197 | " self.divide_data(config['test_portion']) # This gives the indekses of the training and test data\n", 198 | " \n", 199 | " # This needs to be written such that it gets arguments from config, like sample bias (geo dist)\n", 200 | " end_index = self._train_ind[-1]\n", 201 | " self.__replay_buffer = ReplayBuffer(start_index=self._train_ind[0],\n", 202 | " end_index=end_index,\n", 203 | " sample_bias=5e-5,\n", 204 | " batch_size=self.batch_size,\n", 205 | " coin_no=self.coin_no)\n", 206 | " \n", 207 | " def train(self):\n", 208 | " optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)\n", 209 | " #loss_metric = -tf.keras.metrics.Mean()\n", 210 | " \n", 211 | " for step in range(50):\n", 212 | " \n", 213 | " batch = self.next_batch()\n", 214 | " w = batch['last_w']\n", 215 | " w = tf.reshape(w, [w.shape[0], w.shape[1], 1, 1] )\n", 216 | " X = tf.transpose(batch['X'], [0, 2, 3, 1])\n", 217 | " \n", 218 | " with tf.GradientTape() as tape:\n", 219 | " output = self.model([X, w])\n", 220 | " \n", 221 | " # Compute negative reward\n", 222 | " loss = self.loss(batch['y'], output)\n", 223 | "\n", 224 | " grads = tape.gradient(loss, self.model.trainable_weights)\n", 225 | " print(len(grads))\n", 226 | " optimizer.apply_gradients(zip(grads, self.model.trainable_weights))\n", 227 | " \n", 228 | " \n", 229 | " # Save the model output in PVM\n", 230 | " #batch['setw'](w[:, 1:])\n", 231 | " self.PVM.iloc[self.indexs, :] = output[:, 1:].numpy()\n", 232 | " \n", 233 | " \n", 234 | " # You can write a custom metric here. See tf.org Keras -> Train and Evaluate\n", 235 | " #loss_metric(loss)\n", 236 | " \n", 237 | " if step % 10 == 0:\n", 238 | " print(\"10 steps\")\n", 239 | " # print(\"step %d: mean loss = %.4f\" % (step, loss_metric.result()))\n", 240 | " \n", 241 | " def next_batch(self):\n", 242 | " \"\"\"\n", 243 | " @:return: the next batch of training sample. The sample is a dictionary\n", 244 | " with key \"X\"(input data); \"y\"(future relative price); \"last_w\" a numpy array\n", 245 | " with shape [batch_size, assets]; \"w\" a list of numpy arrays list length is\n", 246 | " batch_size\n", 247 | " \"\"\"\n", 248 | " batch = self.__pack_samples([exp.state_index for exp in self.__replay_buffer.next_experience_batch()])\n", 249 | " return batch\n", 250 | "\n", 251 | " \n", 252 | " def __pack_samples(self, indexs):\n", 253 | " self.indexs = indexs\n", 254 | " indexs = np.array(indexs)\n", 255 | " last_w = self.PVM.values[indexs-1, :]\n", 256 | "\n", 257 | " def setw(w): # Notice that this function is defined in terms of the specifik indexs\n", 258 | " self.PVM.iloc[indexs, :] = w \n", 259 | " M = [self.get_submatrix(index) for index in indexs] # For each state_index in the batch, get a input tensor\n", 260 | " M = np.array(M)\n", 261 | " X = M[:, :, :, :-1] # X_t tensor\n", 262 | " y = M[:, :, :, -1] / M[:, 0, None, :, -2] # y_{t+1} obtained by dividing all features by prev close price\n", 263 | " return {\"X\": X, \"y\": y, \"last_w\": last_w, \"setw\": setw}\n", 264 | " \n", 265 | "\n", 266 | " # volume in y is the volume in next access period\n", 267 | " def get_submatrix(self, ind):\n", 268 | " return self.global_data[:, :, ind-(self.window_size):ind+1]\n", 269 | " \n", 270 | " \n", 271 | " def divide_data(self, test_portion, portion_reversed = False):\n", 272 | " train_portion = 1 - test_portion\n", 273 | " s = float(train_portion + test_portion)\n", 274 | " if portion_reversed:\n", 275 | " portions = np.array([test_portion]) / s\n", 276 | " portion_split = (portions * self.no_periods).astype(int)\n", 277 | " indices = np.arange(self.no_periods)\n", 278 | " self._test_ind, self._train_ind = np.split(indices, portion_split)\n", 279 | " else:\n", 280 | " portions = np.array([train_portion]) / s\n", 281 | " portion_split = (portions * self.no_periods).astype(int)\n", 282 | " indices = np.arange(self.no_periods)\n", 283 | " self._train_ind, self._test_ind = np.split(indices, portion_split)\n", 284 | "\n", 285 | " self._train_ind = self._train_ind[(self.window_size):-1]\n", 286 | " # NOTE(zhengyao): change the logic here in order to fit both\n", 287 | " # reversed and normal version\n", 288 | " self._train_ind = list(self._train_ind)\n", 289 | " self._num_train_samples = len(self._train_ind)\n", 290 | " self._num_test_samples = len(self._test_ind)\n", 291 | "\n", 292 | " \n", 293 | " #get a loss function, which is minus the reward function\n", 294 | " def loss(self, y, output):\n", 295 | " #r_t = log(mu_t * y_t dot w_{t-1})\n", 296 | " \n", 297 | " self.future_price = tf.concat([tf.ones([16, 1]), y[:, 0, :]], 1)\n", 298 | " self.future_w = (self.future_price * output) / tf.reduce_sum(self.future_price * output, axis=1)[:, None]\n", 299 | " self.pv_vector = tf.reduce_sum(output * self.future_price, axis=1) *\\\n", 300 | " (tf.concat([tf.ones(1), self.__pure_pc(output)], axis=0))\n", 301 | " \n", 302 | " \n", 303 | " return -tf.reduce_mean(tf.math.log(self.pv_vector))\n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " # consumption vector (on each periods)\n", 309 | " def __pure_pc(self, output):\n", 310 | " c = self.commission_ratio\n", 311 | " w_t = self.future_w[:self.batch_size-1] # rebalanced\n", 312 | " w_t1 = output[1:self.batch_size]\n", 313 | " mu = 1 - np.sum(np.abs(w_t1[:, 1:]-w_t[:, 1:]), axis=1)*c\n", 314 | " \"\"\"\n", 315 | " mu = 1-3*c+c**2\n", 316 | "\n", 317 | " def recurse(mu0):\n", 318 | " factor1 = 1/(1 - c*w_t1[:, 0])\n", 319 | " if isinstance(mu0, float):\n", 320 | " mu0 = mu0\n", 321 | " else:\n", 322 | " mu0 = mu0[:, None]\n", 323 | " factor2 = 1 - c*w_t[:, 0] - (2*c - c**2)*tf.reduce_sum(\n", 324 | " tf.nn.relu(w_t[:, 1:] - mu0 * w_t1[:, 1:]), axis=1)\n", 325 | " return factor1*factor2\n", 326 | "\n", 327 | " for i in range(20):\n", 328 | " mu = recurse(mu)\n", 329 | " \"\"\"\n", 330 | " return mu" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": 12, 336 | "metadata": {}, 337 | "outputs": [], 338 | "source": [ 339 | "agent = Agent(config)" 340 | ] 341 | }, 342 | { 343 | "cell_type": "code", 344 | "execution_count": 13, 345 | "metadata": {}, 346 | "outputs": [ 347 | { 348 | "name": "stdout", 349 | "output_type": "stream", 350 | "text": [ 351 | "WARNING:tensorflow:Layer cnn_3 is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2. The layer has dtype float32 because it's dtype defaults to floatx.\n", 352 | "\n", 353 | "If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.\n", 354 | "\n", 355 | "To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.\n", 356 | "\n" 357 | ] 358 | }, 359 | { 360 | "name": "stderr", 361 | "output_type": "stream", 362 | "text": [ 363 | "WARNING:tensorflow:Layer cnn_3 is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2. The layer has dtype float32 because it's dtype defaults to floatx.\n", 364 | "\n", 365 | "If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.\n", 366 | "\n", 367 | "To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.\n", 368 | "\n" 369 | ] 370 | }, 371 | { 372 | "name": "stdout", 373 | "output_type": "stream", 374 | "text": [ 375 | "7\n", 376 | "10 steps\n", 377 | "7\n", 378 | "7\n", 379 | "7\n", 380 | "7\n", 381 | "7\n", 382 | "7\n", 383 | "7\n", 384 | "7\n", 385 | "7\n", 386 | "7\n", 387 | "10 steps\n", 388 | "7\n", 389 | "7\n", 390 | "7\n", 391 | "7\n", 392 | "7\n", 393 | "7\n", 394 | "7\n", 395 | "7\n", 396 | "7\n", 397 | "7\n", 398 | "10 steps\n", 399 | "7\n", 400 | "7\n", 401 | "7\n", 402 | "7\n", 403 | "7\n", 404 | "7\n", 405 | "7\n", 406 | "7\n", 407 | "7\n", 408 | "7\n", 409 | "10 steps\n", 410 | "7\n", 411 | "7\n", 412 | "7\n", 413 | "7\n", 414 | "7\n", 415 | "7\n", 416 | "7\n", 417 | "7\n", 418 | "7\n", 419 | "7\n", 420 | "10 steps\n", 421 | "7\n", 422 | "7\n", 423 | "7\n", 424 | "7\n", 425 | "7\n", 426 | "7\n", 427 | "7\n", 428 | "7\n", 429 | "7\n" 430 | ] 431 | } 432 | ], 433 | "source": [ 434 | "agent.train()" 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": null, 440 | "metadata": {}, 441 | "outputs": [], 442 | "source": [ 443 | "agent.next_batch()['X'].shape" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": null, 449 | "metadata": {}, 450 | "outputs": [], 451 | "source": [ 452 | "w = agent.next_batch()['last_w']\n", 453 | "w = tf.reshape(w, [w.shape[0], w.shape[1], 1, 1] )\n", 454 | "tensor = tf.transpose(agent.next_batch()['X'], [0, 2, 3, 1])" 455 | ] 456 | }, 457 | { 458 | "cell_type": "code", 459 | "execution_count": null, 460 | "metadata": {}, 461 | "outputs": [], 462 | "source": [ 463 | "future = tf.concat([tf.ones([16, 1]), agent.next_batch()['y'][:, 0, :]], 1)" 464 | ] 465 | }, 466 | { 467 | "cell_type": "code", 468 | "execution_count": null, 469 | "metadata": {}, 470 | "outputs": [], 471 | "source": [ 472 | "output = agent.model([tensor, w])" 473 | ] 474 | }, 475 | { 476 | "cell_type": "code", 477 | "execution_count": null, 478 | "metadata": {}, 479 | "outputs": [], 480 | "source": [ 481 | "output.shape" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": null, 487 | "metadata": {}, 488 | "outputs": [], 489 | "source": [ 490 | "(output * future) / np.sum(future * output, axis=1)[:,None]" 491 | ] 492 | }, 493 | { 494 | "cell_type": "code", 495 | "execution_count": null, 496 | "metadata": {}, 497 | "outputs": [], 498 | "source": [ 499 | "b_init = tf.zeros_initializer()\n", 500 | "b = tf.Variable(\n", 501 | " initial_value=b_init(shape=(1, 1), dtype=\"float32\"),\n", 502 | " trainable=True\n", 503 | " )\n" 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": null, 509 | "metadata": {}, 510 | "outputs": [], 511 | "source": [] 512 | } 513 | ], 514 | "metadata": { 515 | "kernelspec": { 516 | "display_name": "Python 3", 517 | "language": "python", 518 | "name": "python3" 519 | }, 520 | "language_info": { 521 | "codemirror_mode": { 522 | "name": "ipython", 523 | "version": 3 524 | }, 525 | "file_extension": ".py", 526 | "mimetype": "text/x-python", 527 | "name": "python", 528 | "nbconvert_exporter": "python", 529 | "pygments_lexer": "ipython3", 530 | "version": "3.8.4" 531 | } 532 | }, 533 | "nbformat": 4, 534 | "nbformat_minor": 4 535 | } 536 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/ToyModel-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import pandas as pd\n", 10 | "import numpy as np\n", 11 | "import xarray as xr" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from datetime import datetime\n", 21 | "import time, calendar\n", 22 | "import pandas as pd\n", 23 | "\n", 24 | "from src.config import *\n", 25 | "from src.constants import *\n", 26 | "from src.data.poloniex import Poloniex" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 3, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "polo = Poloniex()\n", 36 | "\n", 37 | "start = datetime(2020, 1, 20, 3, 0)\n", 38 | "end= datetime(2020, 1, 20, 4, 20)\n", 39 | "\n", 40 | "start = int(time.mktime(start.timetuple()) - time.timezone)\n", 41 | "end= int(calendar.timegm(end.timetuple()))\n", 42 | "\n", 43 | "period = FIFTEEN_MINUTES" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 4, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "from src.data.globaldatamatrix import HistoryManager\n", 53 | "mgr = HistoryManager(coin_number=4, end = end)" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 5, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "panel = mgr.get_global_panel(start=start, end=end, period=period, features=['close', 'high', 'low'])" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 6, 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "data": { 72 | "text/plain": [ 73 | "'ETH'" 74 | ] 75 | }, 76 | "execution_count": 6, 77 | "metadata": {}, 78 | "output_type": "execute_result" 79 | } 80 | ], 81 | "source": [ 82 | "coin = panel.coins[3].values\n", 83 | "str(coin)" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 7, 89 | "metadata": {}, 90 | "outputs": [ 91 | { 92 | "data": { 93 | "text/html": [ 94 | "
\n", 95 | "\n", 108 | "\n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | "
datehighlowopenclosevolumequoteVolumeweightedAverage
02020-01-20 03:00:000.0191320.0191100.0191100.0191270.29298915.3218450.019122
12020-01-20 03:15:000.0191350.0191190.0191270.0191191.42591774.5523500.019126
22020-01-20 03:30:000.0191310.0191190.0191230.0191281.39273272.8189790.019126
32020-01-20 03:45:000.0191350.0191030.0191300.0191111.78153793.1593410.019124
42020-01-20 04:00:000.0191480.0190890.0191130.0191113.112476162.7205790.019128
52020-01-20 04:15:000.0191130.0190750.0191090.0190780.32244016.8832590.019098
\n", 191 | "
" 192 | ], 193 | "text/plain": [ 194 | " date high low open close volume \\\n", 195 | "0 2020-01-20 03:00:00 0.019132 0.019110 0.019110 0.019127 0.292989 \n", 196 | "1 2020-01-20 03:15:00 0.019135 0.019119 0.019127 0.019119 1.425917 \n", 197 | "2 2020-01-20 03:30:00 0.019131 0.019119 0.019123 0.019128 1.392732 \n", 198 | "3 2020-01-20 03:45:00 0.019135 0.019103 0.019130 0.019111 1.781537 \n", 199 | "4 2020-01-20 04:00:00 0.019148 0.019089 0.019113 0.019111 3.112476 \n", 200 | "5 2020-01-20 04:15:00 0.019113 0.019075 0.019109 0.019078 0.322440 \n", 201 | "\n", 202 | " quoteVolume weightedAverage \n", 203 | "0 15.321845 0.019122 \n", 204 | "1 74.552350 0.019126 \n", 205 | "2 72.818979 0.019126 \n", 206 | "3 93.159341 0.019124 \n", 207 | "4 162.720579 0.019128 \n", 208 | "5 16.883259 0.019098 " 209 | ] 210 | }, 211 | "execution_count": 7, 212 | "metadata": {}, 213 | "output_type": "execute_result" 214 | } 215 | ], 216 | "source": [ 217 | "chart = polo.marketChart(period=period, start=start, end=end, pair = 'BTC_' + str(coin))\n", 218 | "chart = pd.DataFrame(chart)\n", 219 | "chart.date = pd.to_datetime(chart.date, unit = 's')\n", 220 | "chart" 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": 43, 226 | "metadata": {}, 227 | "outputs": [], 228 | "source": [ 229 | "db = panel.sel(coins='ETH', features='low').values[1:]" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 44, 235 | "metadata": {}, 236 | "outputs": [], 237 | "source": [ 238 | "ch = chart.low.values[:-1]" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 45, 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "data": { 248 | "text/plain": [ 249 | "array([ True, True, True, True, True])" 250 | ] 251 | }, 252 | "execution_count": 45, 253 | "metadata": {}, 254 | "output_type": "execute_result" 255 | } 256 | ], 257 | "source": [ 258 | "ch == db" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": null, 264 | "metadata": {}, 265 | "outputs": [], 266 | "source": [] 267 | }, 268 | { 269 | "cell_type": "code", 270 | "execution_count": null, 271 | "metadata": {}, 272 | "outputs": [], 273 | "source": [] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": null, 278 | "metadata": {}, 279 | "outputs": [], 280 | "source": [ 281 | "import tensorflow as tf\n", 282 | "import numpy as np" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": null, 288 | "metadata": {}, 289 | "outputs": [], 290 | "source": [ 291 | "class Cash(tf.keras.Model):\n", 292 | "\n", 293 | " def __init__(self):\n", 294 | " super(Cash, self).__init__()\n", 295 | "\n", 296 | " self.dense = tf.keras.layers.Dense(5, activation='relu')\n", 297 | " \n", 298 | " self.b = tf.Variable(tf.zeros((1, 1), dtype=tf.float32), trainable=True)\n", 299 | " self.out = tf.keras.layers.Activation('softmax')\n", 300 | "\n", 301 | " def call(self, x):\n", 302 | " x = self.dense(x)\n", 303 | " bias = tf.tile(self.b, [tf.shape(x)[0], 1])\n", 304 | " x = tf.concat((bias, x), axis = -1)\n", 305 | " x = self.out(x)\n", 306 | " \n", 307 | " return x\n", 308 | " \n", 309 | " \n", 310 | "model = Cash()\n", 311 | "\n", 312 | "X = np.random.randn(100, 3)\n", 313 | "with tf.GradientTape() as tape:\n", 314 | " y = model(X)\n", 315 | "\n", 316 | "print([var.name for var in tape.watched_variables()])\n", 317 | "grads = tape.gradient(y, model.trainable_variables)" 318 | ] 319 | }, 320 | { 321 | "cell_type": "code", 322 | "execution_count": null, 323 | "metadata": {}, 324 | "outputs": [], 325 | "source": [ 326 | "class Cash(tf.keras.Model):\n", 327 | "\n", 328 | " def __init__(self, rows = 11, cols = 50, features = 3):\n", 329 | " super(Cash, self).__init__()\n", 330 | " \n", 331 | " input_shape = (rows, cols, features)\n", 332 | " inputs = tf.keras.Input(shape=input_shape)\n", 333 | " x = tf.keras.layers.Conv2D(\n", 334 | " filters = 2, \n", 335 | " kernel_size = (1,3), \n", 336 | " padding='valid', \n", 337 | " activation='relu',\n", 338 | " name = 'conv1'\n", 339 | " )(inputs)\n", 340 | " x = tf.keras.layers.Conv2D(1, \n", 341 | " (1, x.shape[2]), \n", 342 | " activation=\"relu\", \n", 343 | " name = 'conv2')(x)\n", 344 | " x = tf.squeeze(x)\n", 345 | " \n", 346 | " self.model = tf.keras.Model(inputs = inputs, outputs = x)\n", 347 | " \n", 348 | " self.b = tf.Variable(tf.zeros((1, 1), dtype=tf.float32), trainable=True)\n", 349 | " self.out = tf.keras.layers.Activation('softmax')\n", 350 | "\n", 351 | " def call(self, x):\n", 352 | " x = self.model(x)\n", 353 | " print(x.shape)\n", 354 | " bias = tf.tile(self.b, [tf.shape(x)[0], 1])\n", 355 | " x = tf.concat((bias, x), axis = -1)\n", 356 | " x = self.out(x)\n", 357 | " \n", 358 | " return x\n", 359 | " \n", 360 | " \n", 361 | "model = Cash()\n", 362 | "\n", 363 | "X = np.random.randn(100, 11, 50, 3)\n", 364 | "with tf.GradientTape() as tape:\n", 365 | " y = model(X)\n", 366 | "\n", 367 | "print([var.name for var in tape.watched_variables()])\n", 368 | "grads = tape.gradient(y, model.trainable_variables)" 369 | ] 370 | }, 371 | { 372 | "cell_type": "code", 373 | "execution_count": null, 374 | "metadata": {}, 375 | "outputs": [], 376 | "source": [ 377 | "batch = 100\n", 378 | "dense = tf.keras.layers.Dense(5, activation='relu')\n", 379 | "b = tf.Variable(tf.zeros((1,1), dtype=tf.float32), name='b', trainable=True)\n", 380 | "\n", 381 | "x = tf.random.normal((batch, 3))\n", 382 | "with tf.GradientTape() as tape:\n", 383 | " y = dense(x)\n", 384 | " cash = tf.tile(b, [y.shape[0], 1])\n", 385 | " y = tf.concat((cash, y), axis = -1)\n", 386 | " y = tf.keras.layers.Activation('softmax')(y)\n", 387 | "\n", 388 | "print([var.name for var in tape.watched_variables()])\n", 389 | "grads = tape.gradient(y, [b, dense.trainable_variables])\n", 390 | "grads" 391 | ] 392 | }, 393 | { 394 | "cell_type": "code", 395 | "execution_count": null, 396 | "metadata": {}, 397 | "outputs": [], 398 | "source": [] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": null, 403 | "metadata": {}, 404 | "outputs": [], 405 | "source": [ 406 | "def CNN(rows, cols, features, batch_size):\n", 407 | " input_shape = (rows, cols, features)\n", 408 | " X = keras.Input(shape= input_shape, batch_size=batch_size)\n", 409 | " w = keras.Input(shape = (rows, 1, 1), batch_size=batch_size)\n", 410 | "\n", 411 | " x = tf.keras.layers.Conv2D(\n", 412 | " filters = 2, \n", 413 | " kernel_size = (1,3), \n", 414 | " padding='valid', \n", 415 | " activation='relu'\n", 416 | " )(X)\n", 417 | " \n", 418 | " x = keras.layers.Conv2D(20, \n", 419 | " (1, x.shape[2]), \n", 420 | " activation=\"relu\", \n", 421 | " name = 'conv2')(x) \n", 422 | " \n", 423 | " con = keras.layers.Concatenate(axis=3)([x, w])\n", 424 | "\n", 425 | " x = keras.layers.Conv2D(1, (1,1), name = 'votes')(con)\n", 426 | " x = tf.squeeze(x)\n", 427 | " \n", 428 | " b = tf.tile(b, [x.shape[0], 1])\n", 429 | " with_bias = keras.layers.Concatenate(axis=1)([b, x])\n", 430 | " #with_bias = CashBias()(x)\n", 431 | "\n", 432 | " outputs = keras.layers.Activation('softmax')(with_bias)\n", 433 | " return keras.Model(inputs = [X, w], outputs = outputs, name = \"Policy\")" 434 | ] 435 | }, 436 | { 437 | "cell_type": "code", 438 | "execution_count": null, 439 | "metadata": {}, 440 | "outputs": [], 441 | "source": [] 442 | }, 443 | { 444 | "cell_type": "code", 445 | "execution_count": null, 446 | "metadata": {}, 447 | "outputs": [], 448 | "source": [] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "execution_count": null, 453 | "metadata": {}, 454 | "outputs": [], 455 | "source": [ 456 | "import tensorflow as tf\n", 457 | "from tensorflow import keras\n", 458 | "from tensorflow.keras import layers\n", 459 | "import numpy as np" 460 | ] 461 | }, 462 | { 463 | "cell_type": "code", 464 | "execution_count": null, 465 | "metadata": {}, 466 | "outputs": [], 467 | "source": [ 468 | "inputs = keras.Input(shape=(784,), name=\"digits\")\n", 469 | "x1 = layers.Dense(64, activation=\"relu\")(inputs)\n", 470 | "x2 = layers.Dense(64, activation=\"relu\")(x1)\n", 471 | "outputs = layers.Dense(10, name=\"predictions\")(x2)\n", 472 | "model = keras.Model(inputs=inputs, outputs=outputs)" 473 | ] 474 | }, 475 | { 476 | "cell_type": "code", 477 | "execution_count": null, 478 | "metadata": {}, 479 | "outputs": [], 480 | "source": [ 481 | "# Instantiate an optimizer.\n", 482 | "optimizer = keras.optimizers.SGD(learning_rate=1e-3)\n", 483 | "# Instantiate a loss function.\n", 484 | "loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)" 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": null, 490 | "metadata": {}, 491 | "outputs": [], 492 | "source": [ 493 | "# Prepare the training dataset.\n", 494 | "batch_size = 64\n", 495 | "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n", 496 | "x_train = np.reshape(x_train, (-1, 784))\n", 497 | "x_test = np.reshape(x_train, (-1, 784))\n", 498 | "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n", 499 | "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": null, 505 | "metadata": {}, 506 | "outputs": [], 507 | "source": [ 508 | "for (x, y) in train_dataset:\n", 509 | "\n", 510 | " with tf.GradientTape() as tape:\n", 511 | "\n", 512 | " # Run the forward pass of the layer.\n", 513 | " # The operations that the layer applies\n", 514 | " # to its inputs are going to be recorded\n", 515 | " # on the GradientTape.\n", 516 | " logits = model(x, training=True) # Logits for this minibatch\n", 517 | "\n", 518 | " # Compute the loss value for this minibatch.\n", 519 | " loss_value = loss_fn(y, logits)\n", 520 | "\n", 521 | " # Use the gradient tape to automatically retrieve\n", 522 | " # the gradients of the trainable variables with respect to the loss.\n", 523 | " grads = tape.gradient(loss_value, model.trainable_weights)\n", 524 | "\n", 525 | " # Run one step of gradient descent by updating\n", 526 | " # the value of the variables to minimize the loss.\n", 527 | " # optimizer.apply_gradients(zip(grads, model.trainable_weights))\n", 528 | " shapes = [grad.shape for grad in grads]\n", 529 | " print(shapes)\n", 530 | " print(model.trainable_variables)\n", 531 | " break" 532 | ] 533 | }, 534 | { 535 | "cell_type": "code", 536 | "execution_count": null, 537 | "metadata": {}, 538 | "outputs": [], 539 | "source": [ 540 | "a = [np.random.rand(2,3) for i in range(10)]" 541 | ] 542 | }, 543 | { 544 | "cell_type": "code", 545 | "execution_count": null, 546 | "metadata": {}, 547 | "outputs": [], 548 | "source": [ 549 | "np.array(a, dtype='float32').dtype" 550 | ] 551 | }, 552 | { 553 | "cell_type": "code", 554 | "execution_count": null, 555 | "metadata": {}, 556 | "outputs": [], 557 | "source": [] 558 | } 559 | ], 560 | "metadata": { 561 | "kernelspec": { 562 | "display_name": "Python 3", 563 | "language": "python", 564 | "name": "python3" 565 | }, 566 | "language_info": { 567 | "codemirror_mode": { 568 | "name": "ipython", 569 | "version": 3 570 | }, 571 | "file_extension": ".py", 572 | "mimetype": "text/x-python", 573 | "name": "python", 574 | "nbconvert_exporter": "python", 575 | "pygments_lexer": "ipython3", 576 | "version": "3.8.5" 577 | } 578 | }, 579 | "nbformat": 4, 580 | "nbformat_minor": 4 581 | } 582 | -------------------------------------------------------------------------------- /Model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Populating the interactive namespace from numpy and matplotlib\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "import torch\n", 18 | "import torch.nn as nn\n", 19 | "import torch.nn.functional as F\n", 20 | "from tqdm import trange\n", 21 | "import numpy as np\n", 22 | "%pylab inline" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 2, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "from src.data.datamatrices import DataMatrices\n", 32 | "from config import config\n", 33 | "_matrix = DataMatrices.create_from_config(config)" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 3, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "class CNN(nn.Module):\n", 43 | " def __init__(self, in_features, rows, cols, device = torch.device(\"cpu\")):\n", 44 | " super(CNN, self).__init__()\n", 45 | "\n", 46 | " out1 = 2\n", 47 | " out2 = 20\n", 48 | " kernel1 = (1,3)\n", 49 | " kernel2 = (1,cols-2) # cols - (kernel1[1] - 1)\n", 50 | "\n", 51 | " self.conv1 = nn.Conv2d(in_features, out1, kernel1)\n", 52 | " self.conv2 = nn.Conv2d(out1, out2, kernel2)\n", 53 | " self.votes = nn.Conv2d(out2+1, 1, (1,1)) # input features is out2 plus the appended last_weights\n", 54 | " \n", 55 | " # BTC bias\n", 56 | " b = torch.zeros((1,1))\n", 57 | " self.b = nn.Parameter(b)\n", 58 | "\n", 59 | " def forward(self, x, w):\n", 60 | " x = self.conv1(x)\n", 61 | " x = F.relu(x)\n", 62 | " x = self.conv2(x)\n", 63 | " x = F.relu(x)\n", 64 | " x = torch.cat((x,w),dim=1)\n", 65 | " x = self.votes(x)\n", 66 | " x = torch.squeeze(x)\n", 67 | " \n", 68 | " cash = self.b.repeat(x.size()[0], 1)\n", 69 | " \n", 70 | " x = torch.cat((cash, x), dim=1)\n", 71 | " x = F.softmax(x, dim=1)\n", 72 | " \n", 73 | " return x" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 21, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "def loss_func(output, future_price, input_no):\n", 83 | " #r_t = log(mu_t * y_t dot w_{t-1})\n", 84 | " future_w = (future_price * output) / torch.sum(future_price * output, dim=1)[:, None]\n", 85 | " pv_vector = torch.sum(output * future_price, dim=1) * (torch.cat([torch.ones(1), pure_pc(output, input_no, future_w)], dim=0))\n", 86 | " return -torch.mean(torch.log(pv_vector))\n", 87 | "\n", 88 | "def pure_pc(output, input_no, future_w):\n", 89 | " c = config[\"trading\"][\"trading_consumption\"]\n", 90 | " w_t = future_w[:input_no-1] # rebalanced\n", 91 | " w_t1 = output[1:input_no]\n", 92 | " mu = 1 - torch.sum(torch.abs(w_t1[:, 1:]-w_t[:, 1:]), dim=1)*c\n", 93 | " return mu" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 22, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "def batch():\n", 103 | " batch = _matrix.next_batch()\n", 104 | " w = torch.tensor(batch['last_w'])\n", 105 | " w = w[:, None, : , None] # Concat along dim=1, the features dim)\n", 106 | " X = torch.tensor(batch['X'])\n", 107 | " y = torch.tensor(batch['y'])\n", 108 | " return X, w, y, batch['setw']" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 23, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [ 117 | "model = CNN(config[\"input\"][\"feature_no\"],\n", 118 | " config[\"input\"][\"coin_no\"],\n", 119 | " config[\"input\"][\"window_size\"])\n", 120 | "training = _matrix.get_training_set()\n", 121 | "w = torch.tensor(training['last_w'])\n", 122 | "w_train = w[:, None, : , None] # Concat along dim=1, the features dim)\n", 123 | "X_train = torch.tensor(training['X'])\n", 124 | "y_train = torch.tensor(training['y'])\n", 125 | "input_no_train = y_train.shape[0]\n", 126 | "future_price_train = torch.cat([torch.ones((input_no_train, 1)), y_train[:, 0, :]], dim=1)" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 24, 132 | "metadata": {}, 133 | "outputs": [ 134 | { 135 | "name": "stderr", 136 | "output_type": "stream", 137 | "text": [ 138 | "100%|██████████| 100000/100000 [14:17<00:00, 116.58it/s]\n" 139 | ] 140 | }, 141 | { 142 | "data": { 143 | "text/plain": [ 144 | "[]" 145 | ] 146 | }, 147 | "execution_count": 24, 148 | "metadata": {}, 149 | "output_type": "execute_result" 150 | }, 151 | { 152 | "data": { 153 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZMAAAD4CAYAAAApWAtMAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAAvNElEQVR4nO3deXxU1dnA8d+Tyc4SSIAAgZAIQQiiCGFT3BAV3LCWV3GpaLG8dalLaxW7qLW+rXZxt1YqVsVqVNziigguKLKEHQKBELYECFlICEsSJnneP+ZCQ0zIhCyTzDzfz4cPM+eee85z5ybz5J5zF1FVjDHGmMYI8nUAxhhj2j5LJsYYYxrNkokxxphGs2RijDGm0SyZGGOMabRgXwfgK126dNGEhARfh2GMMW3GsmXLClS1a23LAjaZJCQkkJ6e7uswjDGmzRCRbXUts2EuY4wxjWbJxBhjTKNZMjHGGNNolkyMMcY0miUTY4wxjWbJxBhjTKNZMjHGGNNolkyMMSZALN++lxe+3twsbVsyMcaYAPDOshwmv7CIN5Zs50C5u8nbD9gr4I0xJhBUVil/+WwDL3yTzRl9Y3ju2qG0C2v6r35LJsYY48fuf3c1b6Xn8JNRfXjgsmRCXM0zIGXJxBhj/NQHK3N5Kz2HW8/ty73jBzRrXzZnYowxfmh74UF++95aUvp05pcX9G/2/iyZGGOMnzlcWcWdb65ABJ6cPITgZhraqs6GuYwxxs88M28TK7YX88w1p9Orc2SL9GlHJsYY40fW5pbw3FebuXJoHJed1rPF+vUqmYjIeBHJFJEsEZley/IwEXnTWb5YRBKqLbvfKc8UkYvqa1NEbnfKVES6VCs/V0RKRGSl8+8Bb+MzxphAUOGu4p63VxHdLpQHLx3Uon3XO8wlIi7gOeACIAdYKiJpqppRrdpUYK+q9hORycBjwNUikgxMBgYBPYEvROTITFBdbX4HfAR8VUs4C1T10hOIzxhj/N4/v97Mht2lzPjJMKIiQ1q0b2+OTEYAWaqaraoVQCowsUadicArzuvZwPkiIk55qqqWq+oWIMtpr842VXWFqm5twDZ4E58xxvi1Dbv38cz8TVx+Wk8uHNS9xfv3JpnEATuqvc9xymqto6puoASIOc663rRZm9EiskpEPhWRI8dwXrclItNEJF1E0vPz873ozhhj2oY/fbKBDuEhPHhZsk/6b0sT8MuBPqp6GvAM8H5DG1DVGaqaoqopXbt2ber4jDHGJwr3l/PtpnyuGdGbmPZhPonBm2SSC/Su9r6XU1ZrHREJBqKAwuOs602bx1DVfaq633n9CRDiTNA3uC1jjPEnn2fkUaVw8eAePovBm2SyFEgSkUQRCcUzoZ5Wo04aMMV5PQmYr6rqlE92zvZKBJKAJV62eQwR6e7MwyAiI5zYC0+kLWOM8SefrNlFn5hIknt09FkM9Z7NpapuEbkdmAO4gJdUdZ2IPAykq2oaMBOYJSJZQBGeL3Scem8BGYAbuE1VK8FzCnDNNp3yO4B7ge7AahH5RFVvxpOkbhERN3AImOwkrFrja5JPxxhjWrmiAxUs3FzItLNPwvl72yfE830ceFJSUjQ9Pd3XYRhjTKOkLtnO9HfX8NEvxnBKXFSz9iUiy1Q1pbZlbWkC3hhjTA2frN1NfHQkg3r6bogLLJkYY0ybkrm7lPW79gFQfLCChVkFXDy4h0+HuMBu9GiMMW3GrpJDTHp+IaXlbs5K6kLfru1xVymX+PAsriPsyMQYY9oAVWX6O2twVyl3jO3Hht2lvLxwK706R3BKnG+HuMCOTIwxpk14Oz2Hrzfm89Blydx4ZiK3ntePj1bvonfnCJ8PcYElE2OMaZUe/zyTfWVuzh/YjT7R7fjjRxmMTIzmhtEJAISHuJg0rJdvg6zGkokxxrQym/JKeXp+FiLw8sKtAESEuPjrpNMICvL9UUhtLJkYY0wr85/F2wlxCV/ecy6Zu0v5KjOfM/rGEB/TMk9NPBGWTIwxphU5VFHJO8tzmHBKD3p1jqRX50jOHxjr67DqZWdzGWNMK/Lhqp2Ulrm5bmS8r0NpEEsmxhjTivxn8TaSurVnRGK0r0NpEEsmxhjTSqzJKWFVTgnXjYxvFaf7NoQlE2OMaSX+s3gbESEufjS09Zzy6y1LJsYY0wrM35DHO8tzmDikJ1ERIb4Op8EsmRhjjI99vTGfn89azoDuHbn/4oG+DueEWDIxxhgf+i6rgGmvptOvW3tmTR3RJo9KwJKJMcb4zPebC5n6ylISu7TjtZtH0iky1NchnTBLJsYY4wOLsgv56ctL6d05ktduHkl0u7abSMCSiTHGtLjF2YXc9O+lxHWO4PWfjaJL+zBfh9RolkyMMaYFfbupgJteXkrPTuG8/rORdO3Q9hMJWDIxxphmUVWlvLl0O6tzio+WfbpmFz99eSnx0ZG88bNRdOsQ7rsAm5hXN3oUkfHAU4ALeFFVH62xPAx4FRgGFAJXq+pWZ9n9wFSgErhDVeccr00RuR24C+gLdFXVAqf8OuA+QIBS4BZVXeUs2+qUVQJuVU1p+EdhjDFNJ3XpDn7z3hoATonryIiEGF5euIUhvTvx7xtHEBXZNs/aqku9RyYi4gKeAyYAycA1IpJco9pUYK+q9gOeAB5z1k0GJgODgPHAP0TEVU+b3wHjgG01+tgCnKOqg4E/AjNqLD9PVYdYIjHG+FrB/nIe/XQ9IxOj+eMVp1BZBS99t4Wzkrry2s0j/S6RgHdHJiOALFXNBhCRVGAikFGtzkTgIef1bOBZ8dxYZiKQqqrlwBYRyXLao642VXWFU3ZMEKq6sNrbRUDbu9+AMSYg/Onj9Rw6XMn//Wgw/bq15/qR8WwpOEB8dCTBLv+cXfBmq+KAHdXe5zhltdZRVTdQAsQcZ11v2jyeqcCn1d4r8LmILBORaXWtJCLTRCRdRNLz8/Mb0J0xxnhn4eYC3l2Ry8/P6Uu/bu0Bzx/HJ3Vt77eJBNrgw7FE5Dw8yWRMteIxqporIt2AuSKyQVW/qbmuqs7AGR5LSUnRFgnYGBMwKtxV/O79tcRHR3Lbef18HU6L8iZN5gK9q73v5ZTVWkdEgoEoPBPxda3rTZs/ICKnAi8CE1W18Ei5quY6/+8B3uO/Q2nGGNNiPl27i+z8A/z+0mTCQ1y+DqdFeZNMlgJJIpIoIqF4JtTTatRJA6Y4rycB81VVnfLJIhImIolAErDEyzaPISLxwLvAT1R1Y7XydiLS4chr4EJgrRfbZYwxDeL5WqvbfxZvp09MJOcP6NZCEbUe9SYTZw7kdmAOsB54S1XXicjDInK5U20mEONMsP8SmO6suw54C89k/WfAbapaWVebACJyh4jk4DlaWS0iLzp9PIBnHuYfIrJSRNKd8ljgWxFZhSdRfayqnzXiMzHGmGOszS1h6stL6f+7T3n00w1UuKt+UGdjXilLthRx7Yh4goLa1oOtmoLUl2n9VUpKiqanp9df0RgTsJZv38s/v9rM5xl5REWEkNKnM/M27CG5R0eenDyE/rEdjtZ9KG0dry/ezvf3jyXGD26PUhsRWVbX5RdtbgLeGGOaU4W7ijnrdjPz2y2s3FFMh/Bg7h7Xn5vGJNAxPIS5GXlMf2c1lz7zLU9dPYQJg3twsMLNO8tzmDC4u98mkvpYMjHGGDxDWbOX5ZC2aidFBypIiInkD5cP4sfDetE+7L9flRckxzKk99n876x0bnt9OX+ZdBqVVVWUlrm5bmQfH26Bb1kyMcYELFXl+82FPDlvE0u2FBHqCuKC5FgmDevFOf271jn30bVDGLOmjmTarHTueXsVMe1C6R/bnuEJnVt4C1oPSybGmIC0o+ggd7+5kvRte4ntGMYDlyZz5dA4rx9Q1S4smJlThnP76yv4Yn0ed5yf9IM7dwQSSybGmID01LxNrNu5j4cnDuKqlN4ndF1IeIiL568fyoJN+Zyd1LUZomw7LJkYYwJO2eFKPlu7m0tP7cENoxMa1VaIK4ixA2KbJrA2zH9vFGOMMXWYv2EP+8vdXHF6Q24JaI7HkokxJuB8sDKXrh3CGHVSjK9D8RuWTIwxAaXk0GG+3JDPZaf2xBWAV6o3F0smxpiAMmftbioqq5g4pKevQ/ErlkyMMQHlg1W5JMREcmqvKF+H4lcsmRhjAsaefWUs3FzI5UPiAvqakOZgpwYbY/xa0YEK3liynU15pazOLUEVLj/NhriamiUTY4xfm/7Oaj7PyCOuUwR9u7XnxjMSjj5O1zQdSybGGL+1u6SMeRv28L/nnMT9Ewb6Ohy/ZnMmxpg25dn5m3jqi01e1X1z6Q4qq5RrR8Q3c1TGkokxps3YlFfK43M38sI3myk7XHnMsrW5JXy2dtfR9+7KKlKXbuespC70iWnX0qEGHEsmxphW6fHPM/nXN9nHlD32WSZVCgcrKlm8peiYZb95bw0/f205H6zMBeDLzHx2lZQF9DNGWpIlE2NMq7NqRzFPz8/i/z5ZT+qS7QAs3VrkudX72H6EhwQxb33e0fpbCw6wOqeE9mHB/Hr2alZs38vri7cR2zGM8wd289VmBBRLJsaYVudvn2cS3S6UMf268Lv317Iwq4A/fbKe2I5h3HJuP8b068q89XtQVQA+XuMZ3kqdNorYjmHc/Eo6X23M5+rh8YS47GuuJXj1KYvIeBHJFJEsEZley/IwEXnTWb5YRBKqLbvfKc8UkYvqa1NEbnfKVES6VCsXEXnaWbZaRIZWWzZFRDY5/6acwOdgjGklvt9cyIJNBdx6bl/+cf1QEru048aXl7JiezF3j+tPRKiL8wd2I7f4EJl5pQB8uGonw/p05pS4KGZOGU65uwoBJg/v7duNCSD1JhMRcQHPAROAZOAaEUmuUW0qsFdV+wFPAI856yYDk4FBwHjgHyLiqqfN74BxwLYafUwAkpx/04DnnT6igQeBkcAI4EERCdxnZxrThqkqf/s8k+4dw7l+VB86hofw0o3D6RAWTFK39kwa1guA8wd4hq7mrd/DprxSNuwu5dJTewDQP7YDs6aO4Imrh9CzU4TPtiXQeHOdyQggS1WzAUQkFZgIZFSrMxF4yHk9G3hWPPcqmAikqmo5sEVEspz2qKtNVV3hlNWMYyLwqnqOaxeJSCcR6QGcC8xV1SJnvbl4EtcbXn0CxphW46vMfJZt28v//eiUo08+7B0dyZy7z8YlQrAzZNWtYzin9opi3vo8z1GIwCWDexxt5/T4zpweb39TtiRvhrnigB3V3uc4ZbXWUVU3UALEHGddb9r0Ng6v2xKRaSKSLiLp+fn59XRnjGlJZYcrefTTDcRHR3JVyrHDU13ah9G53bHPZh87oBsrdhQzO30HIxOj6dYxvCXDNTUE1MyUqs5Q1RRVTenaNbCf12xMa/PIxxlk5pXyh8sHeTVpPm5gLKqws6SMy+xeWz7nTTLJBar/mdDLKau1jogEA1FA4XHW9aZNb+M4kbaMMa3IR6t38tqi7Uw7+yTOG+DdqbyDenYktmMYriBhwik96l/BNCtvkslSIElEEkUkFM+EelqNOmnAkbOoJgHznbmNNGCyc7ZXIp7J8yVetllTGnCDc1bXKKBEVXcBc4ALRaSzM/F+oVNmjGkDthYcYPo7azg9vhO/vuhkr9cTEW45py83n5VIdI0hMNPy6p2AV1W3iNyO5wvaBbykqutE5GEgXVXTgJnALGeCvQhPcsCp9xaeyXo3cJuqVoLnFOCabTrldwD3At2B1SLyiareDHwCXAxkAQeBm5w+ikTkj3gSFMDDRybjjTGt2/5yN7f+ZzmuIOGZa05v8DUhN56Z2EyRmYaSIxf9BJqUlBRNT0/3dRjGBKxydyU3/Xspi7cU8eKUFM472a5Ub+1EZJmqptS2LKAm4I0xvrOj6CB79pUBUFml3P3mShZuLuSvk061ROIH7HkmxphmV7C/nAue+Jqyw1XEdYqgS4cwVu0o5neXDOTKob18HZ5pAnZkYoxpdm8s3k7Z4SruGpfEkPhOlB46zD0X9ufms07ydWimidiRiTGmSWXtKaVPTLujk+mHK6t4bfE2zkrqwl3j+vs4OtNc7MjEGNNkPlu7m3GPf8Ov31519I6+n63dTd6+cm46M8G3wZlmZcnEGNMkthQc4Ndvr6JDeDDvr9zJO8s91w6/vHArfWIiObe/TbL7M0smxphGO1RRyS2vLcPlEj654yxGJkbzwAdr+WBlLsu27eWG0QkEBf3g5q3Gj1gyMcY0iqry+w/WkplXypNXD6F3dCRPTh5CWHAQd6auJDLUxf+k2Blb/s6SiTHmhB2qqOTO1JXMXpbDL8Ymca5zvUiPqAj+Ouk0ACYN60XH8BBfhmlagJ3NZYw5ITl7D/K/s5aRsWsfv77oZG49t+8xy8clx/Lh7WNIim3vowhNS7JkYoxpsJ3Fh5j47HdUuKuYOSWFsQNia603uFdUC0dmfMWSiTGmwR7+MIMDFW7nyKODr8MxrYDNmRhjGuSrzD18tm43vxibZInEHGXJxBjjtbLDlTyYto6TurTj5rPs9u/mv2yYyxjjtRnfZLOt8CCvTR1JWLDL1+GYVsSOTIwxXlmbW8JzX2Zxyak9GJPUxdfhmFbGkokxpl7rdpZw/czFxLQL5YFLk30djmmFLJkYY44rY+c+rntxMREhLlKnjSa2Y7ivQzKtkCUTY0ydthce5LoXFxEe7CJ12ijiYyJ9HZJppWwC3pgAc7iyilU7ivk2q4CcvYe4ZkRvhvWJ/kG9qirl3ndW4a5U3r11FH1i2vkgWtNWeJVMRGQ88BTgAl5U1UdrLA8DXgWGAYXA1aq61Vl2PzAVqATuUNU5x2tTRBKBVCAGWAb8RFUrROQJ4Dyny0igm6p2ctapBNY4y7ar6uUN+xiM8V/7y928tzyHDbtL2ZhXSsbOfRyoqEQE2oUGM3tZDmP6deGucUmkJPw3qby+ZDuLsot49MrBJHaxRGKOr95kIiIu4DngAiAHWCoiaaqaUa3aVGCvqvYTkcnAY8DVIpIMTAYGAT2BL0TkyKPW6mrzMeAJVU0VkX86bT+vqndXi+kXwOnV+j+kqkNOYPuN8Wuqyt1vrmRuRh4dw4MZ0L0jk4b1YnTfGEaf1IWQYOG1RduY8U02k/75PVen9Oa3lw6ktMzNnz9Zz5n9Yrh6eG9fb4ZpA7w5MhkBZKlqNoCIpAITgerJZCLwkPN6NvCsiIhTnqqq5cAWEcly2qO2NkVkPTAWuNap84rT7vM1YroGeNDLbTQmYH22djdzM/K4d/zJ3HJOXzy/lseadnZffjIqgafnb+KFrzezYFM+3TqGo8CjV55a6zrG1OTNBHwcsKPa+xynrNY6quoGSvAMU9W1bl3lMUCx00atfYlIHyARmF+tOFxE0kVkkYhcUdeGiMg0p156fn5+nRtsjD8oOXSYB9PWMahnR6adddJxk0JEqIv7xg/gnVvOICLUxcodxdw3fgC9o23C3XinLU7ATwZmq2pltbI+qporIicB80Vkjapurrmiqs4AZgCkpKRoy4RrjG889tkGCvaXM3PKcIJd3p24eXp8Zz6+4yyWb9/LqMSYZo7Q+BNvfsJygeqDpr2cslrriEgwEIVnIr6udesqLwQ6OW3U1ddk4I3qBaqa6/yfDXzFsfMpxgScpVuLeH3xdqaOSWzwbeDDQ1yc0beLPWbXNIg3yWQpkCQiiSISiufLPK1GnTRgivN6EjBfVdUpnywiYc5ZWknAkrradNb50mkDp80PjnQiIgOAzsD31co6O2eTISJdgDM5dj7HmICiqjzy8Xp6RoVz9wX961/BmCZQ7zCXqrpF5HZgDp7TeF9S1XUi8jCQrqppwExgljPBXoQnOeDUewvPl7sbuO3I8FRtbTpd3gekisgjwAqn7SMm45nQrz5ENRB4QUSq8CTHR2ucaWZMQJm3fg+rdhTz2I8HExnaFkeyTVskx34vB46UlBRNT0/3dRjGNKmqKuXipxdQdriSub88hxAv50qM8YaILFPVlNqW2U+aMX7kk7W72LC7lLvG9bdEYlqU/bQZ4yfclVU8Pncj/WPbc9lpPX0djgkwlkyM8ROvfr+N7PwD/PKC/rjsTCzTwiyZGNPGfJm5h798toHdJWWAZ57ksc828PBHGZzdvysXDeru4whNILJTPYxpQ9yVVfzuvbXkFh/ixQVbuHp4b3aVlPHF+jyuHRnPHy4fZLc/MT5hycSYNmTehj3kFh/igUuT2bRnP6lLt1NZpTx0WTJTzkiwRGJ8xpKJMW3IKwu30jMqnBtG9yHYFcQd5/ej+OBhBvbo6OvQTICzORNj2ohNeaUs3FzIdaP6HL3XVo+oCEskplWwZGJMG/Hq99sIDQ5isj1fxLRClkyMaSXclVXc/MpSrpmxiG2FB45Ztq/sMO8sz+GyU3sS0z7MRxEaUzdLJsa0Ek/Pz+KL9XtYuaOYCU8t4PXF26lwV7E2t4S/zcnkYEUlN56R4OswjamVTcAb0woszCrgmfmb+PHQXvzqwv78evYqfvPeGh74YC3uKs/988YO6Nbg28kb01IsmRjjYwX7y7nzzZUkdmnHwxMH0S4smFk/Hclb6TvYUnCAQXFRnBoXRbw99dC0YpZMjGlhVVXKF+vzWLZtL1sKDrA2t4SSQ4d59acjaBfm+ZUMChImj4j3caTGeM+SiTEtRFVZsKmAv8zZwNrcfYS6goiPiSS5Z0euGRFvp/iaNs2SiTEtYE9pGfe8vZpvNuYT1ymCx686jctP6+n1s9mNae0smRjTzBZlF/KLN1ZQWnaY31+azPWj4gkLdvk6LGOalCUTY5rRiwuy+dMn60mIacesqSMY0N2Gsox/smRiTDNZuLmARz5ez0WDYvn7VUNoH2a/bsZ/2YCtMY1UdriSKS8t4a2lO44p+827a+gTE8lTk0+3RGL8nv2EG9NIaat28vXGfL7emE9QkDBpWC+emb+JrYUHeW3qSMJDbH7E+D+vjkxEZLyIZIpIlohMr2V5mIi86SxfLCIJ1Zbd75RnishF9bUpIolOG1lOm6FO+Y0iki8iK51/N1dbZ4qIbHL+TTnBz8KYBlNVXv5uK/1j23NWUhfunb2Kp+dt4oWvs/nx0F6MSeri6xCNaRH1JhMRcQHPAROAZOAaEUmuUW0qsFdV+wFPAI856yYDk4FBwHjgHyLiqqfNx4AnnLb2Om0f8aaqDnH+vej0EQ08CIwERgAPikjnBn4OxpyQpVv3krFrHzedmcgLPxnGsD6deXzuRqIiQvjdJQN9HZ4xLcabI5MRQJaqZqtqBZAKTKxRZyLwivN6NnC+eB75NhFIVdVyVd0CZDnt1dqms85Ypw2cNq+oJ76LgLmqWqSqe4G5eBKXMc3u5YVbiIoI4YohcUSGBvPSjcP50elx/P2q0+jcLtTX4RnTYrxJJnHAjmrvc5yyWuuoqhsoAWKOs25d5TFAsdNGbX39WERWi8hsETnyUAdv4gNARKaJSLqIpOfn59e9xcZ4Ibf4EHPW5TF5eG8iQj3zIh3CQ3ji6iGce3I3H0dnTMtqS2dzfQgkqOqpeI4+Xqmn/g+o6gxVTVHVlK5duzZ5gCawvLZoG6rK9aP6+DoUY3zOm2SSC1R/tFsvp6zWOiISDEQBhcdZt67yQqCT08YxfalqoaqWO+UvAsMaEJ8xTUJV2bB7H8/O38Rri7ZxQXIsve1uvsZ4lUyWAknOWVaheCbU02rUSQOOnEU1CZivquqUT3bO9koEkoAldbXprPOl0wZOmx8AiEiPav1dDqx3Xs8BLhSRzs7E+4VOmTFNan+5m0ue/pbxTy7gb59v5KSu7fn1RSf7OixjWoV6rzNRVbeI3I7nC9oFvKSq60TkYSBdVdOAmcAsEckCivAkB5x6bwEZgBu4TVUrAWpr0+nyPiBVRB4BVjhtA9whIpc77RQBNzp9FInIH/EkKICHVbXohD8RY+rw7aYCMnbt454L+3NVSm+6dQz3dUjGtBriORgIPCkpKZqenu7rMEwb8sAHa3k7PYdVD15IaHBbmm40pmmIyDJVTaltmf1GGOOl77IKGJEYbYnEmFrYb4UxXthdUsbm/AOM6WdXtBtTG0smxnjhu6wCAM7oF+PjSIxpnSyZGOOF7zYXEN0ulIH2PBJjamXJxJh6qCoLswoZfVIMQUHi63CMaZUsmRhTj+yCA+zeV2ZDXMYchyUTY+pxZL7EJt+NqZslE2Pq8V1WAXGdIoi326YYUydLJsYcR9nhSr7fXMiZ/WLwPCHBGFMbe2yvMTVUVilfZe7h49W7mJuRR2m5m/MHxvo6LGNaNUsmxjgOV1bx/opcnv9qM9kFB4iKCGH8Kd257LSenN3fHllgzPFYMjEBa3dJGU9+sZH80nL2l7vZVniQ3fvKGNijI89dO5QLkmPt1inGeMmSiQlIhyoqufnVpWzK20+/bu1pFxbMkN6d+J+UXowd0M3mR4xpIEsmJuCoKtPfXc26nfv4109SGJds8yHGNJYdw5uA868F2Xywcif3XHiyJRJjmoglExNQlmwp4tFPN3DJ4B7cem5fX4djjN+wZGICRmWV8lDaOnpERfDX/znV5kWMaUKWTEzAmL1sBxm79nH/xQOIDLXpQmOakiUTExBKyw7z1zkbSenTmUsG9/B1OMb4HUsmJiD846vNFOwv5/eXJtvwljHNwKtkIiLjRSRTRLJEZHoty8NE5E1n+WIRSai27H6nPFNELqqvTRFJdNrIctoMdcp/KSIZIrJaROaJSJ9q61SKyErnX9oJfhbGj+TsPcifP1nPIx9l8MhHGcxcsIUrh8ZxWu9Ovg7NGL9U78CxiLiA54ALgBxgqYikqWpGtWpTgb2q2k9EJgOPAVeLSDIwGRgE9AS+EJH+zjp1tfkY8ISqporIP522nwdWACmqelBEbgH+AlzttHVIVYec+Mdg/M3DH2bwxfo8IkJcKNA7OoJ7Lxrg67CM8VvezEKOALJUNRtARFKBiUD1ZDIReMh5PRt4VjxjCROBVFUtB7aISJbTHrW1KSLrgbHAtU6dV5x2n1fVL6v1twi4vgHbaQLIqh3FfJ6Rx93j+nPnuCRfh2NMQPBmmCsO2FHtfY5TVmsdVXUDJUDMcdatqzwGKHbaqKsv8BytfFrtfbiIpIvIIhG5oq4NEZFpTr30/Pz8uqqZNu5vn2fSOTKEn45J8HUoxgSMNnd+pIhcD6QA51Qr7qOquSJyEjBfRNao6uaa66rqDGAGQEpKirZIwKZFLc4uZMGmAn5z8QA6hIf4OhxjAoY3Rya5QO9q73s5ZbXWEZFgIAooPM66dZUXAp2cNn7Ql4iMA34LXO4MnQGgqrnO/9nAV8DpXmyX8TOqyt8/30i3DmHcMDrB1+EYE1C8SSZLgSTnLKtQPBPqNc+YSgOmOK8nAfNVVZ3yyc7ZXolAErCkrjaddb502sBp8wMAETkdeAFPItlzpGMR6SwiYc7rLsCZHDufYwLEN5sKWLK1iF+M7Ud4iMvX4RgTUOod5lJVt4jcDswBXMBLqrpORB4G0lU1DZgJzHIm2IvwJAecem/h+XJ3A7epaiVAbW06Xd4HpIrII3jO4JrplP8VaA+87VwnsF1VLwcGAi+ISBWe5PhojTPNTABQVR6fu5G4ThFcPTze1+EYE3DEczAQeFJSUjQ9Pd3XYZgm8mXmHm7691L+fOVgrhlhycSY5iAiy1Q1pbZldgW8afNUlSfnbqRX5wh+PLSXr8MxJiBZMjFt3peZe1iVU8Lt5/Wzx+wa4yP2m2faNFXlyS820Ts6gh8Ps6MSY3zFkolp0z7PyGN1Tgm/OC+JEJf9OBvjK/bbZ9qsDbv3cc/bqzg5tgM/GlrbjRKMMS3FkolpVeZvyOPt9B311sstPsSUl5bQLjSYl24abkclxvhYm7udivFfefvK+MXrKzhQUUn3qHDOSupaa73igxVMeWkJB8srefuW0cR1imjhSI0xNdmfc6bV+PMn6zlcqSTERPKrt1ax90BFrfXue2c12wsPMuOGFAZ079jCURpjamPJxLQKS7YU8f7KnUw7+ySeu24oew9WMP3d1dS8qPbzdbuZsy6Puy/oz+i+MT6K1hhTkyUT43OVVcqDaevoGRXOref1ZVDPKO69aABz1uXxxpL/zp/sL3fzYNo6BnTvwM1nJfowYmNMTTZnYnziq8w9LMouwhUEO4vLWL9rH89dO5TIUM+P5NQxiXy9MZ/fvLeGdTtLmD5hAE/M3cTufWU8e+1Qm3A3ppWxZGJa3BcZeUyblY5zw04qq5RLTu3BxYO7H60TFCT864YUHp+bycxvtzBv/R72lJZx3ch4hvXp7KvQjTF1sGRiWtTy7Xu5/Y3lDI6L4o1po44eiajq0eRyRESoi99eksyEwT24d/ZqgiSce8fbc9yNaY0smZgWs6XgADe/kk5sx3Bm3jj8aCIBfpBIqhsa35k5d51NhbuKiFB7TokxrZENPJsWUe6u5Gevem75/8pNI+jSPqxB67uCxBKJMa2YHZmYFvGvb7LJ2rOff980nIQu7XwdjjGmidmRiWl22woP8Mz8LC4Z3IPzTu7m63CMMc3AkolpVqrKAx+sIzhI+P2lyb4OxxjTTCyZmGb16drdfL0xn19deDLdo8J9HY4xpplYMjHN5lBFJQ9/mEFyj47cMLqPr8MxxjQjr5KJiIwXkUwRyRKR6bUsDxORN53li0Ukodqy+53yTBG5qL42RSTRaSPLaTP0RPswTedQRSWvLdrGVS98z3NfZlFZpfWuM/PbbHbvK+OhywcRbFesG+PX6v0NFxEX8BwwAUgGrhGRmoPfU4G9qtoPeAJ4zFk3GZgMDALGA/8QEVc9bT4GPOG0tddpu8F9NPSDMLWrqlKe/GIjox+dx+/eX0vu3kP8dU4m18xYRM7eg3Wul19azvNfbebC5FhGJEa3YMTGGF/w5s/FEUCWqmaragWQCkysUWci8IrzejZwvniuQpsIpKpquapuAbKc9mpt01lnrNMGTptXnGAfpgm88E02T36xiZQ+0bz989F8e995PH7VaWTs2seEpxbwysKtlLsrf7DeU/M2Uuau4r4JdsW6MYHAm2QSB1R/9F2OU1ZrHVV1AyVAzHHWras8Bih22qjZV0P7+AERmSYi6SKSnp+ff9yNDgS7Sg5xV+oKFmYV1Lp8xfa9/P3zTC4Z3IN/3TCM4QnRiAhXDu3Fp3eexaCeHXkwbR1j//Y1qUu2c6Dcs9uy9uznjSU7uG5kPH27tm/JTTLG+EhAXbSoqjOAGQApKSn1D/rXYkfRQVxBQs82/nS/ssOV/HzWMlbllPD+yp1cMyKe+y8eQMfwEAD2lR3mjtQVxHYM509XDv7B7U56R0fyxs9GsWBTAX+fu5Hp765h+rtr6BkVjogQEeLizvOTfLFpxhgf8CaZ5AK9q73v5ZTVVidHRIKBKKCwnnVrKy8EOolIsHP0Ub3+ifTRpA6Uuxn3+NdcMyKehy4f1BxdtAhV5ffvr2VVTglPTR7Cup37eHFBNvM35HFm3y70io5kTU4xO4vLeOt/RxEVEVJrOyLC2f27clZSF77LKmTF9r1k5e9nS8EBbh/bj5gG3jLFGNN2eZNMlgJJIpKI50t6MnBtjTppwBTge2ASMF9VVUTSgNdF5HGgJ5AELAGktjaddb502kh12vzgBPtocu3Cghk3MJa0VTv57SUD2+wzNV5btI23l+Vwx9h+TBwSx8QhcVw8uAd//zyTRdmF7FqZiyrcN34Aw/rUP3kuIoxJ6sKYpC4tEL0xpjWqN5moqltEbgfmAC7gJVVdJyIPA+mqmgbMBGaJSBZQhCc54NR7C8gA3MBtqloJUFubTpf3Aaki8giwwmmbE+mjOVw5NI6P1+ziq8x8LkiOba5ums3i7EL+8GEGYwd0465x/Y+WD+ndiVlTRwKemzKWlrkbfDNGY0zgkprP2A4UKSkpmp6e3uD1DldWMepP8xiRGM3z1w9rhsiaz46ig0x87js6RYbw3q1n1jl8ZYwxtRGRZaqaUtuytjlO40MhriAmDolj3vo9FB+s8HU4Xttf7ubmV9JxV1bx4g0plkiMMU3KkskJuHJoHBWVVXy4etcJt7Gl4ABrc0u8qquq7NlXxu6SsuPWq6pSDlX8cISvqkq5K3UlWfn7ee66oZxkp+saY5pYQJ0a3FQG9ezIybEdeHd5Dj8Z1fB7TqWt2sl9s1dT5q7kpjMS+fVFJxMR6qLk4GHeSt/Byh3FVFYpVaoUHzzMxj2lFB88TJDA1DGJ3H1B/2OeUrintIzZy3J4c+kOdhQd5FcXnswt5/QlKEgoOXSYX765knkb9vDQZcmcldS1KT8KY4wBLJmcEBHhx8Pi+NMnG8jO33/MX/qqyuFKZXP+fpZv38vK7cWEBAcxMjGa4QnR/Pu7LfxrwRaGJ3Tm5O4deOm7LczbkMfok2L4YOVODh2upE9MJGHBQQSJ0D4smAmn9ODk2PZk5pXyrwVb+GTNbqaOSWRb4QFW5pSwLrcEd5UyIjGapG4d+OucTNK3FnHLuf24753V7Cg6yMMTB51Q4jPGGG/YBPwJyttXxug/zyM8xEWIK+hoEil3V1L9HojR7UI57K6itNx9tGzK6D789pJkQoODWLi5gPveWU3evnKuGNKTKWckMKhnVJ39Lt1axP3vriFrz34iQ10MjosiJaEzVw7tRd+u7VFVXlu0jT9+tJ6Kyiq6dgjjH9cNZXiC3R/LGNM4x5uAt2TSCKlLtrN+176jV4eHuITwEBehriB6RUcwNL4z8dGRVClk7NzH4i2FxEdHcuGg7se0466soqKy6pihq+OpcFexs/gQvaMjcQVJrXVW5xTzzrIcbj2vH7Ed7TkixpjGs2RSi6ZIJsYYE0js1GBjjDHNypKJMcaYRrNkYowxptEsmRhjjGk0SybGGGMazZKJMcaYRrNkYowxptEsmRhjjGm0gL1oUUTygW0nuHoXoKAJw2kLbJv9X6BtL9g2N1QfVa31brEBm0waQ0TS67oK1F/ZNvu/QNtesG1uSjbMZYwxptEsmRhjjGk0SyYnZoavA/AB22b/F2jbC7bNTcbmTIwxxjSaHZkYY4xpNEsmxhhjGs2SSQOIyHgRyRSRLBGZ7ut4moOI9BaRL0UkQ0TWicidTnm0iMwVkU3O/519HWtTExGXiKwQkY+c94kistjZ32+KSKivY2xKItJJRGaLyAYRWS8io/19P4vI3c7P9VoReUNEwv1tP4vISyKyR0TWViurdb+Kx9POtq8WkaEn2q8lEy+JiAt4DpgAJAPXiEiyb6NqFm7gV6qaDIwCbnO2czowT1WTgHnOe39zJ7C+2vvHgCdUtR+wF5jqk6iaz1PAZ6o6ADgNz7b77X4WkTjgDiBFVU8BXMBk/G8/vwyMr1FW136dACQ5/6YBz59op5ZMvDcCyFLVbFWtAFKBiT6Oqcmp6i5VXe68LsXzBROHZ1tfcaq9AlzhkwCbiYj0Ai4BXnTeCzAWmO1U8attFpEo4GxgJoCqVqhqMX6+n4FgIEJEgoFIYBd+tp9V9RugqEZxXft1IvCqeiwCOolIjxPp15KJ9+KAHdXe5zhlfktEEoDTgcVArKruchbtBmJ9FVczeRK4F6hy3scAxarqdt772/5OBPKBfztDey+KSDv8eD+rai7wN2A7niRSAizDv/fzEXXt1yb7XrNkYmolIu2Bd4C7VHVf9WXqOZ/cb84pF5FLgT2quszXsbSgYGAo8Lyqng4coMaQlh/u5854/hJPBHoC7fjhcJDfa679asnEe7lA72rvezllfkdEQvAkkv+o6rtOcd6Rw1/n/z2+iq8ZnAlcLiJb8QxfjsUzn9DJGQ4B/9vfOUCOqi523s/Gk1z8eT+PA7aoar6qHgbexbPv/Xk/H1HXfm2y7zVLJt5bCiQ5Z36E4pm4S/NxTE3OmSuYCaxX1cerLUoDpjivpwAftHRszUVV71fVXqqagGe/zlfV64AvgUlONX/b5t3ADhE52Sk6H8jAj/cznuGtUSIS6fycH9lmv93P1dS1X9OAG5yzukYBJdWGwxrEroBvABG5GM/Yugt4SVX/z7cRNT0RGQMsANbw3/mD3+CZN3kLiMdz6/6rVLXmJF+bJyLnAveo6qUichKeI5VoYAVwvaqW+zC8JiUiQ/CccBAKZAM34fkD02/3s4j8Abgaz1mLK4Cb8cwR+M1+FpE3gHPx3Go+D3gQeJ9a9quTVJ/FM9x3ELhJVdNPqF9LJsYYYxrLhrmMMcY0miUTY4wxjWbJxBhjTKNZMjHGGNNolkyMMcY0miUTY4wxjWbJxBhjTKP9PxIkD1zzrNXmAAAAAElFTkSuQmCC\n", 154 | "text/plain": [ 155 | "
" 156 | ] 157 | }, 158 | "metadata": { 159 | "needs_background": "light" 160 | }, 161 | "output_type": "display_data" 162 | } 163 | ], 164 | "source": [ 165 | "learning_rate = 0.00028\n", 166 | "# optimizer = torch.optim.Adam([\n", 167 | "# {'params': model.parameters()},\n", 168 | "# {'params': model.b}\n", 169 | "# ], lr=learning_rate)\n", 170 | "\n", 171 | "optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n", 172 | "losses = []\n", 173 | "\n", 174 | "for i in (t:=trange(100000)):\n", 175 | " X, w, y, setw = batch()\n", 176 | " X = X / X[:, 0, None, :, -1, None]\n", 177 | " input_no = y.shape[0]\n", 178 | " future_price = torch.cat([torch.ones((input_no, 1)), y[:, 0, :]], dim=1) # Add cash price (always 1)\n", 179 | " \n", 180 | " output = model(X, w)\n", 181 | " loss = loss_func(output, future_price, input_no)\n", 182 | " optimizer.zero_grad()\n", 183 | " loss.backward()\n", 184 | " optimizer.step()\n", 185 | " if i%999==0:\n", 186 | " out = model(X_train, w_train)\n", 187 | " losses.append(loss_func(out, future_price_train, input_no_train))\n", 188 | " setw(output[:, 1:].detach().numpy())\n", 189 | "\n", 190 | "plot(losses)" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "metadata": {}, 197 | "outputs": [], 198 | "source": [] 199 | } 200 | ], 201 | "metadata": { 202 | "kernelspec": { 203 | "display_name": "Python 3", 204 | "language": "python", 205 | "name": "python3" 206 | }, 207 | "language_info": { 208 | "codemirror_mode": { 209 | "name": "ipython", 210 | "version": 3 211 | }, 212 | "file_extension": ".py", 213 | "mimetype": "text/x-python", 214 | "name": "python", 215 | "nbconvert_exporter": "python", 216 | "pygments_lexer": "ipython3", 217 | "version": "3.8.5" 218 | } 219 | }, 220 | "nbformat": 4, 221 | "nbformat_minor": 4 222 | } 223 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PortfolioManager 2 | 3 | Repo from paper 4 | https://github.com/ZhengyaoJiang/PGPortfolio 5 | 6 | Papers 7 | https://arxiv.org/abs/1706.10059 8 | https://arxiv.org/pdf/1612.01277.pdf 9 | 10 | References 11 | http://rail.eecs.berkeley.edu/deeprlcourse-fa17/f17docs/lecture_5_actor_critic_pdf.pdf 12 | https://www.tensorflow.org/agents/tutorials/0_intro_rl 13 | 14 | 15 | 16 | Future options 17 | https://arxiv.org/abs/1808.09940 18 | https://arxiv.org/abs/2002.05780 19 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | config = { 2 | "layers": 3 | [ 4 | {"filter_shape": [1, 3], "filter_number": 2, "weight_decay": 0.0}, 5 | {"filter_number":20, "regularizer": "L2", "weight_decay": 5e-9}, 6 | {"type": "EIIE_Output_WithW","regularizer": "L2", "weight_decay": 5e-8} 7 | ], 8 | 'training':{ 9 | 'steps': 1000000, 10 | 'batch_size': 50, 11 | 'buffer_bias': 5e-5, 12 | 'learning_rate': 0.00028, 13 | 'fast_train': False, 14 | 'decay_rate': 1.0, 15 | 'decay_steps': 50000 16 | }, 17 | "random_seed": 0, 18 | 'input':{ 19 | 'global_period': 1800, 20 | 'coin_no': 11, 21 | 'window_size': 50, 22 | 'feature_no': 3, 23 | "start_date":"2019/12/12", 24 | "end_date":"2020/10/26", 25 | "test_portion": 0.08, 26 | "volume_average_days": 30, 27 | "market": "poloniex", 28 | "online": 1 29 | }, 30 | 'trading':{ 31 | "trading_consumption": 0.0025 32 | }, 33 | } 34 | 35 | 36 | import os 37 | 38 | DATABASE_DIR = os.getcwd() + "/" + "Data.db" 39 | REF_COIN = "BTC" 40 | -------------------------------------------------------------------------------- /csvdata.py: -------------------------------------------------------------------------------- 1 | from src.trainer import Trainer 2 | from config import config 3 | import pandas as pd 4 | 5 | tra = Trainer(config) 6 | df = tra.test_set['X'] 7 | df = df[0,0,:,:] 8 | df = pd.DataFrame(df) 9 | df.to_csv("/Users/andreasbech/torch.csv") 10 | 11 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # from src.data.datamatrices import DataMatrices 2 | from datetime import datetime 3 | import json 4 | import time 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md") as f: 4 | readme = f.read() 5 | 6 | setup( 7 | name="PortfolioManager", 8 | version="1.0.0", 9 | description="", 10 | long_description=readme, 11 | author="", 12 | author_email="", 13 | packages=find_packages(exclude=("tests", "docs"), 14 | include=("matplotlib", "tensorflow", "xarray", 15 | "pandas", "scipy"))) 16 | -------------------------------------------------------------------------------- /sqldata.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import pandas as pd 3 | 4 | conn = sqlite3.connect('data.db') 5 | sql = ("SELECT * FROM History ") 6 | q = pd.read_sql_query(sql, 7 | con=conn, 8 | parse_dates=["date"]) #, 9 | # index_col="date_norm") 10 | q.to_csv("data.csv") -------------------------------------------------------------------------------- /src/agent.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | import pandas as pd 4 | 5 | from src.network import CNN 6 | 7 | class Agent: 8 | 9 | def __init__(self, config, time_index=None, coins=None, restore_dir=None, device="cpu"): 10 | 11 | self.config = config 12 | self.train_config = config['training'] 13 | self.learning_rate = self.train_config['learning_rate'] 14 | 15 | self.input_config = config['input'] 16 | self.coin_no =self.input_config['coin_no'] 17 | self.window_size = self.input_config['window_size'] 18 | self.feature_no = self.input_config['feature_no'] 19 | 20 | self.commission_ratio = config['trading']["trading_consumption"] 21 | 22 | self.model = CNN( 23 | self.feature_no, 24 | self.coin_no, 25 | self.window_size, 26 | config["layers"], 27 | device = device 28 | ) 29 | 30 | dev = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") 31 | self.model.to(dev) 32 | 33 | if restore_dir: 34 | self.model.load_state_dict(torch.load(restore_dir)) 35 | 36 | self.optimizer = torch.optim.Adam(self.model.parameters(), lr=self.learning_rate) 37 | 38 | def train_step(self, X, w, y, setw): 39 | output = self.model(X, w) 40 | loss = self.loss(output, y) 41 | self.optimizer.zero_grad() 42 | loss.backward() 43 | self.optimizer.step() 44 | #NOTE: ADD regu loss 45 | 46 | # Update weights in PVM 47 | setw(output[:, 1:].detach().numpy()) 48 | 49 | def loss(self, output, y): 50 | #r_t = log(mu_t * y_t dot w_{t-1}) 51 | input_no = y.shape[0] 52 | future_price = torch.cat([torch.ones((input_no, 1)), y[:, 0, :]], dim=1) # Add cash price (always 1) 53 | future_w = (future_price * output) / torch.sum(future_price * output, dim=1)[:, None] 54 | pv_vector = torch.sum(output * future_price, dim=1) *\ 55 | (torch.cat([torch.ones(1), self.pure_pc(output, input_no, future_w)], dim=0)) 56 | 57 | return -torch.mean(torch.log(pv_vector)) 58 | 59 | def pure_pc(self, output, input_no, future_w): 60 | c = self.commission_ratio 61 | w_t = future_w[:input_no-1] # rebalanced 62 | w_t1 = output[1:input_no] 63 | mu = 1 - torch.sum(torch.abs(w_t1[:, 1:]-w_t[:, 1:]), dim=1)*c 64 | """ 65 | mu = 1-3*c+c**2 66 | 67 | def recurse(mu0): 68 | factor1 = 1/(1 - c*w_t1[:, 0]) 69 | if isinstance(mu0, float): 70 | mu0 = mu0 71 | else: 72 | mu0 = mu0[:, None] 73 | factor2 = 1 - c*w_t[:, 0] - (2*c - c**2)*tf.reduce_sum( 74 | tf.nn.relu(w_t[:, 1:] - mu0 * w_t1[:, 1:]), axis=1) 75 | return factor1*factor2 76 | 77 | for i in range(20): 78 | mu = recurse(mu) 79 | """ 80 | return mu 81 | 82 | 83 | def test_step(self, X, w, y): 84 | output = self.model(X, w) 85 | loss = self.loss(output, y) 86 | return loss, output 87 | 88 | def evaluate(self, X, w, y): 89 | loss, output = self.test_step(X, w, y) 90 | 91 | input_no = y.shape[0] 92 | future_price = torch.cat([torch.ones((input_no, 1)), y[:, 0, :]], dim=1) # Add cash price (always 1) 93 | future_w = (future_price * output) / torch.sum(future_price * output, dim=1)[:, None] 94 | self.pv_vector = torch.sum(output * future_price, dim=1) *\ 95 | (torch.cat([torch.ones(1), self.pure_pc(output, input_no, future_w)], dim=0)) 96 | 97 | self.portfolio_value = torch.prod(self.pv_vector) 98 | self.mean = torch.mean(self.pv_vector) 99 | self.log_mean = torch.mean(torch.log(self.pv_vector)) 100 | self.standard_deviation = torch.sqrt(torch.mean((self.pv_vector - self.mean) ** 2)) 101 | self.sharp_ratio = (self.mean - 1) / self.standard_deviation 102 | 103 | self.log_mean_free = torch.mean(torch.log(torch.sum(output * future_price, 104 | dim=1))) 105 | 106 | return self.pv_vector, loss, output 107 | 108 | 109 | def call_model(self, history, prev_w): 110 | assert isinstance(history, np.ndarray),\ 111 | "the history should be a numpy array, not %s" % type(history) 112 | assert not np.any(np.isnan(prev_w)) 113 | assert not np.any(np.isnan(history)) 114 | 115 | return self.model(history, prev_w) 116 | 117 | -------------------------------------------------------------------------------- /src/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | CONFIG_FILE_DIR = 'net_config.json' 5 | LAMBDA = 1e-4 # lambda in loss function 5 in training 6 | # About time 7 | 8 | NOW = 0 9 | FIVE_MINUTES = 60 * 5 10 | FIFTEEN_MINUTES = FIVE_MINUTES * 3 11 | HALF_HOUR = FIFTEEN_MINUTES * 2 12 | HOUR = HALF_HOUR * 2 13 | TWO_HOUR = HOUR * 2 14 | FOUR_HOUR = HOUR * 4 15 | DAY = HOUR * 24 16 | YEAR = DAY * 365 17 | # trading table name 18 | TABLE_NAME = 'test' 19 | -------------------------------------------------------------------------------- /src/data/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/data/coinlist.py: -------------------------------------------------------------------------------- 1 | from src.constants import * 2 | from src.data.poloniex import Poloniex 3 | import pandas as pd 4 | from datetime import datetime 5 | import logging 6 | 7 | class CoinList(object): 8 | def __init__(self, end, volume_average_days=1, volume_forward=0): 9 | self._polo = Poloniex() 10 | # connect the internet to accees volumes 11 | vol = self._polo.marketVolume() 12 | ticker = self._polo.marketTicker() 13 | pairs = [] 14 | coins = [] 15 | volumes = [] 16 | prices = [] 17 | 18 | # NOTE: rewrite this such that it only includes one stablecoin 19 | logging.info("select coin online from %s to %s" % (datetime.fromtimestamp(end-(DAY*volume_average_days)- 20 | volume_forward). 21 | strftime('%Y-%m-%d %H:%M'), 22 | datetime.fromtimestamp(end-volume_forward). 23 | strftime('%Y-%m-%d %H:%M'))) 24 | 25 | for k, v in vol.items(): 26 | if k.startswith("BTC_") or k.endswith("_BTC"): 27 | pairs.append(k) 28 | for c, val in v.items(): 29 | if c != 'BTC': 30 | if k.endswith('_BTC'): 31 | coins.append('reversed_' + c) 32 | prices.append(1.0 / float(ticker[k]['last'])) 33 | else: 34 | coins.append(c) 35 | prices.append(float(ticker[k]['last'])) 36 | else: 37 | volumes.append(self.__get_total_volume(pair=k, global_end=end, 38 | days=volume_average_days, 39 | forward=volume_forward)) 40 | self._df = pd.DataFrame({'coin': coins, 'pair': pairs, 'volume': volumes, 'price':prices}) 41 | self._df = self._df.set_index('coin') 42 | 43 | # remove all but one stablecoin 44 | self._stables = self._df[self._df.pair.str.endswith("_BTC")] 45 | discard = [i for i in list(self._stables.index) if (self._stables.loc[i].volume < self._stables.volume.max())] 46 | self._df = self._df.drop(discard) 47 | logging.info("Successfully got coinlist") 48 | 49 | 50 | @property 51 | def allActiveCoins(self): 52 | return self._df 53 | 54 | @property 55 | def allCoins(self): 56 | return self._polo.marketStatus().keys() 57 | 58 | @property 59 | def polo(self): 60 | return self._polo 61 | 62 | # get several days volume 63 | def __get_total_volume(self, pair, global_end, days, forward): 64 | start = global_end-(DAY*days)-forward 65 | end = global_end-forward 66 | chart = self.get_chart_until_success(polo=self._polo, pair=pair, period=DAY, start=start, end=end) 67 | result = 0 68 | for one_day in chart: 69 | if pair.startswith("BTC_"): 70 | result += one_day['volume'] 71 | else: 72 | result += one_day["quoteVolume"] 73 | return result 74 | 75 | 76 | def topNVolume(self, n=5, order=True, minVolume=0): 77 | if minVolume == 0: 78 | r = self._df.loc[self._df['price'] > 2e-6] 79 | r = r.sort_values(by='volume', ascending=False)[:n] 80 | if order: 81 | return r 82 | else: 83 | return r.sort_index() 84 | else: 85 | return self._df[self._df.volume >= minVolume] 86 | 87 | 88 | def get_chart_until_success(self, polo, pair, start, period, end): 89 | is_connect_success = False 90 | chart = {} 91 | while not is_connect_success: 92 | try: 93 | chart = polo.marketChart(pair=pair, start=int(start), period=int(period), end=int(end)) 94 | is_connect_success = True 95 | except Exception as e: 96 | print(e) 97 | return chart 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/data/datamatrices.py: -------------------------------------------------------------------------------- 1 | import src.data.globaldatamatrix as gdm 2 | from src.data.replayBuffer import ReplayBuffer 3 | import numpy as np 4 | import pandas as pd 5 | import logging 6 | from src.tools.configprocess import parse_time 7 | from src.tools.data import get_volume_forward, get_type_list 8 | 9 | MIN_NUM_PERIOD = 3 10 | 11 | 12 | class DataMatrices: 13 | def __init__(self, start, end, period, batch_size=50, volume_average_days=30, buffer_bias=0, 14 | market="poloniex", coin_filter=1, window_size=50, feature_number=3, test_portion=0.15, 15 | portion_reversed=False, online=False ): 16 | """ 17 | :param start: Unix time 18 | :param end: Unix time 19 | :param access_period: the data access period of the input matrix. 20 | :param trade_period: the trading period of the agent. 21 | :param global_period: the data access period of the global price matrix. 22 | if it is not equal to the access period, there will be inserted observations 23 | :param coin_filter: number of coins that would be selected 24 | :param window_size: periods of input data 25 | :param train_portion: portion of training set 26 | :param is_permed: if False, the sample inside a mini-batch is in order 27 | :param validation_portion: portion of cross-validation set 28 | :param test_portion: portion of test set 29 | :param portion_reversed: if False, the order to sets are [train, validation, test] 30 | else the order is [test, validation, train] 31 | """ 32 | start = int(start) 33 | self.__end = int(end) 34 | 35 | # assert window_size >= MIN_NUM_PERIOD 36 | self.__coin_no = coin_filter 37 | type_list = get_type_list(feature_number) 38 | self.__features = type_list 39 | self.feature_number = feature_number 40 | volume_forward = get_volume_forward(self.__end-start, test_portion, portion_reversed) 41 | self.__history_manager = gdm.HistoryManager(coin_number=coin_filter, end=self.__end, 42 | volume_average_days=volume_average_days, 43 | volume_forward=volume_forward, online=online) 44 | if market == "poloniex": 45 | self.__global_data = self.__history_manager.get_global_panel(start, 46 | self.__end, 47 | period=period, 48 | features=type_list) 49 | else: 50 | raise ValueError("market {} is not valid".format(market)) 51 | self.__period_length = period 52 | 53 | # portfolio vector memory, [time, assets] 54 | PVM = pd.DataFrame(index=self.__global_data.time_index.values, 55 | columns=self.__global_data.coins.values, dtype='float32') 56 | self.__PVM = PVM.fillna(1.0 / self.__coin_no) 57 | 58 | self._window_size = window_size 59 | self._no_periods = len(self.__global_data.time_index.values) 60 | self.divide_data(test_portion) # This gives the indekses of the training and test data 61 | 62 | self._portion_reversed = portion_reversed 63 | 64 | self.__batch_size = batch_size 65 | self.__delta = 0 # the count of global increased 66 | 67 | 68 | self.__buffer_bias = buffer_bias 69 | # This needs to be written such that it gets arguments from config, like sample bias (geo dist) 70 | end_index = self._train_ind[-1] 71 | self.__replay_buffer = ReplayBuffer(start_index=self._train_ind[0], 72 | end_index=end_index, 73 | sample_bias=self.__buffer_bias, 74 | batch_size=self.__batch_size, 75 | coin_no=self.__coin_no) 76 | 77 | logging.info("the number of training examples is %s" 78 | ", of test examples is %s" % (self._num_train_samples, self._num_test_samples)) 79 | logging.debug("the training set is from %s to %s" % (min(self._train_ind), max(self._train_ind))) 80 | logging.debug("the test set is from %s to %s" % (min(self._test_ind), max(self._test_ind))) 81 | 82 | 83 | @property 84 | def global_weights(self): 85 | return self.__PVM 86 | 87 | @staticmethod 88 | def create_from_config(config): 89 | """main method to create the DataMatrices in this project 90 | @:param config: config dictionary 91 | @:return: a DataMatrices object 92 | """ 93 | config = config.copy() 94 | input_config = config["input"] 95 | train_config = config["training"] 96 | start = parse_time(input_config["start_date"]) 97 | end = parse_time(input_config["end_date"]) 98 | return DataMatrices(start=start, 99 | end=end, 100 | market=input_config["market"], 101 | feature_number=input_config["feature_no"], 102 | window_size=input_config["window_size"], 103 | online=input_config["online"], 104 | period=input_config["global_period"], 105 | coin_filter=input_config["coin_no"], 106 | buffer_bias = train_config['buffer_bias'], 107 | batch_size = train_config["batch_size"], 108 | volume_average_days=input_config["volume_average_days"], 109 | test_portion=input_config["test_portion"] 110 | ) 111 | 112 | @property 113 | def global_matrix(self): 114 | return self.__global_data 115 | 116 | @property 117 | def coin_list(self): 118 | return self.__history_manager.coins 119 | 120 | @property 121 | def num_train_samples(self): 122 | return self._num_train_samples 123 | 124 | @property 125 | def num_test_samples(self): 126 | return self._num_test_samples 127 | 128 | def append_experience(self, online_w=None): 129 | """ 130 | :param online_w: (number of assets + 1, ) numpy array 131 | Let it be None if in the backtest case. 132 | """ 133 | self.__delta += 1 134 | self._train_ind.append(self._train_ind[-1]+1) 135 | appended_index = self._train_ind[-1] 136 | self.__replay_buffer.append_experience(appended_index) 137 | 138 | def keras_batch(self, data=None): 139 | if data=="test": 140 | indexs = self._test_ind[(self._window_size):] 141 | else: 142 | indexs = [exp.state_index for exp in self.__replay_buffer.next_experience_batch()] 143 | M = [self.get_submatrix(index) for index in indexs] 144 | M = np.array(M) 145 | X = M[:, :, :, :-1] 146 | y = M[:, :, :, -1] / M[:, 0, None, :, -2] 147 | return {"X": X, "y": y, "idx": indexs} 148 | 149 | def next_batch(self): 150 | """ 151 | @:return: the next batch of training sample. The sample is a dictionary 152 | with key "X"(input data); "y"(future relative price); "last_w" a numpy array 153 | with shape [batch_size, assets]; "w" a list of numpy arrays list length is 154 | batch_size 155 | """ 156 | batch = self.pack_samples([exp.state_index for exp in self.__replay_buffer.next_experience_batch()]) 157 | return batch 158 | 159 | def pack_samples(self, indexs): 160 | indexs = np.array(indexs) 161 | last_w = self.__PVM.values[indexs-1, :] 162 | 163 | def setw(w): # Notice that this function is defined in terms of the specifik indexs 164 | self.__PVM.iloc[indexs, :] = w 165 | M = [self.get_submatrix(index) for index in indexs] 166 | M = np.array(M) 167 | # M is features, coins, time 168 | X = M[:, :, :, :-1] 169 | y = M[:, :, :, -1] / M[:, 0, None, :, -2] # y_{t+1} obtained by dividing all features by prev close price 170 | return {"X": X, "y": y, "last_w": last_w, "setw": setw} 171 | 172 | # volume in y is the volume in next access period 173 | def get_submatrix(self, ind): 174 | return self.__global_data.values[:, :, ind-(self._window_size):ind+1] 175 | 176 | def get_test_set(self): 177 | return self.pack_samples(self._test_ind[(self._window_size+1):]) # Make sure you dont use prices in the test sample that were used in training 178 | 179 | def get_training_set(self): 180 | return self.pack_samples(self._train_ind) 181 | 182 | def divide_data(self, test_portion, portion_reversed = False): 183 | train_portion = 1 - test_portion 184 | s = float(train_portion + test_portion) 185 | if portion_reversed: 186 | portions = np.array([test_portion]) / s 187 | portion_split = (portions * self._no_periods).astype(int) 188 | indices = np.arange(self._no_periods) 189 | self._test_ind, self._train_ind = np.split(indices, portion_split) 190 | else: 191 | portions = np.array([train_portion]) / s 192 | portion_split = (portions * self._no_periods).astype(int) 193 | indices = np.arange(self._no_periods) 194 | self._train_ind, self._test_ind = np.split(indices, portion_split) 195 | 196 | self._train_ind = self._train_ind[(self._window_size):] 197 | # NOTE(zhengyao): change the logic here in order to fit both 198 | # reversed and normal version 199 | self._train_ind = list(self._train_ind) 200 | self._num_train_samples = len(self._train_ind) 201 | self._num_test_samples = len(self._test_ind) 202 | -------------------------------------------------------------------------------- /src/data/globaldatamatrix.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import xarray as xr 4 | 5 | import sqlite3 6 | from datetime import datetime 7 | import logging 8 | 9 | from src.tools.data import panel_fillna, xarray_fillna # Now using xarray_fillna as pd.Panel is deprecated 10 | from config import * 11 | from src.constants import * 12 | 13 | from src.data.coinlist import CoinList 14 | 15 | 16 | class HistoryManager: 17 | # if offline ,the coin_list could be None 18 | # NOTE: return of the sqlite results is a list of tuples, each tuple is a row 19 | def __init__(self, coin_number, end, volume_average_days=1, volume_forward=0, online=True): 20 | self.initialize_db() 21 | self.__storage_period = FIVE_MINUTES # keep this as 300 22 | self._coin_number = coin_number 23 | self._online = online 24 | if self._online: 25 | self._coin_list = CoinList(end, volume_average_days, volume_forward) 26 | self.__volume_forward = volume_forward # volume forward is selecting when to evalute volume for coins to avoid forward looking bias 27 | self.__volume_average_days = volume_average_days # Average days is the period length to evalute volume for 28 | self.__coins = None 29 | 30 | @property 31 | def coins(self): 32 | return self.__coins 33 | 34 | def initialize_db(self): 35 | with sqlite3.connect(DATABASE_DIR) as connection: 36 | cursor = connection.cursor() 37 | cursor.execute('''CREATE TABLE IF NOT EXISTS History (date INTEGER, 38 | coin varchar(20), high FLOAT, low FLOAT, 39 | open FLOAT, close FLOAT, volume FLOAT, 40 | quoteVolume FLOAT, weightedAverage FLOAT, 41 | PRIMARY KEY (date, coin))''') 42 | connection.commit() 43 | 44 | def get_global_data_matrix(self, start, end, period=300, features=('close',)): 45 | """ 46 | :return a numpy ndarray whose axis is [feature, coin, time] 47 | """ 48 | return self.get_global_panel(start, end, period, features).values 49 | 50 | def get_global_panel(self, start, end, period=300, features=['close']): 51 | """ 52 | :param start/end: linux timestamp in seconds 53 | :param period: time interval of each data access point 54 | :param features: tuple or list of the feature names 55 | :return a panel, [feature, coin, time] 56 | """ 57 | start = int(start - (start%period)) 58 | end = int(end - (end%period)) 59 | coins = self.select_coins(start=end - self.__volume_forward - self.__volume_average_days * DAY, 60 | end=end-self.__volume_forward) 61 | self.__coins = coins 62 | for coin in coins: 63 | self.update_data(start, end, coin) 64 | 65 | if len(coins)!=self._coin_number: 66 | raise ValueError("the length of selected coins %d is not equal to expected %d" 67 | % (len(coins), self._coin_number)) 68 | 69 | logging.info("feature type list is %s" % str(features)) 70 | self.__checkperiod(period) 71 | 72 | time_index = pd.to_datetime(list(range(start, end+1, period)), unit='s') 73 | panel = xr.DataArray( 74 | dims=['features', 'coins', 'time_index'], 75 | coords={'features': features, 'coins': coins, 'time_index': time_index} 76 | ).astype('float32') 77 | # panel = pd.Panel(items=features, major_axis=coins, minor_axis=time_index, dtype=np.float32) 78 | # NOTE: Change this part to an pandas multiindex 79 | 80 | connection = sqlite3.connect(DATABASE_DIR) 81 | try: 82 | for row_number, coin in enumerate(coins): 83 | for feature in features: 84 | # NOTE: transform the start date to end date 85 | if feature == "close": 86 | sql = ("SELECT date + 300 AS date_norm, close FROM History WHERE" 87 | " date_norm>={start} and date_norm<={end}" 88 | " and date_norm%{period}=0 and coin=\"{coin}\"".format( # Should always give date_norm%period=0 as we subtract the residual from the input dates 89 | start=start, end=end, period=period, coin=coin)) 90 | elif feature == "open": 91 | sql = ("SELECT date+{period} AS date_norm, open FROM History WHERE" 92 | " date_norm>={start} and date_norm<={end}" 93 | " and date_norm%{period}=0 and coin=\"{coin}\"".format( 94 | start=start, end=end, period=period, coin=coin)) 95 | elif feature == "volume": 96 | sql = ("SELECT date_norm, SUM(volume)"+ 97 | " FROM (SELECT date+{period}-(date%{period}) " 98 | "AS date_norm, volume, coin FROM History)" 99 | " WHERE date_norm>={start} and date_norm<={end} and coin=\"{coin}\"" 100 | " GROUP BY date_norm".format( 101 | period=period,start=start,end=end,coin=coin)) 102 | elif feature == "high": 103 | sql = ("SELECT date_norm, MAX(high)" + 104 | " FROM (SELECT date+{period}-(date%{period})" 105 | " AS date_norm, high, coin FROM History)" 106 | " WHERE date_norm>={start} and date_norm<={end} and coin=\"{coin}\"" 107 | " GROUP BY date_norm".format( 108 | period=period,start=start,end=end,coin=coin)) 109 | elif feature == "low": 110 | sql = ("SELECT date_norm, MIN(low)" + 111 | " FROM (SELECT date+{period}-(date%{period})" 112 | " AS date_norm, low, coin FROM History)" 113 | " WHERE date_norm>={start} and date_norm<={end} and coin=\"{coin}\"" 114 | " GROUP BY date_norm".format( 115 | period=period,start=start,end=end,coin=coin)) 116 | else: 117 | msg = ("The feature %s is not supported" % feature) 118 | logging.error(msg) 119 | raise ValueError(msg) 120 | serial_data = pd.read_sql_query(sql, con=connection, 121 | parse_dates=["date_norm"], 122 | index_col="date_norm") 123 | panel.loc[feature, coin, serial_data.index] = serial_data.squeeze() 124 | panel = xarray_fillna(panel, "both") 125 | finally: 126 | connection.commit() 127 | connection.close() 128 | return panel 129 | 130 | # select top coin_number of coins by volume from start to end 131 | def select_coins(self, start, end): 132 | if not self._online: 133 | logging.info("select coins offline from %s to %s" % (datetime.fromtimestamp(start).strftime('%Y-%m-%d %H:%M'), 134 | datetime.fromtimestamp(end).strftime('%Y-%m-%d %H:%M'))) 135 | connection = sqlite3.connect(DATABASE_DIR) 136 | try: 137 | cursor=connection.cursor() 138 | cursor.execute('SELECT coin,SUM(volume) AS total_volume FROM History WHERE' 139 | ' date>=? and date<=? GROUP BY coin' 140 | ' ORDER BY total_volume DESC LIMIT ?;', 141 | (int(start), int(end), self._coin_number)) 142 | coins_tuples = cursor.fetchall() 143 | 144 | if len(coins_tuples)!=self._coin_number: 145 | logging.error("the sqlite error happend") 146 | finally: 147 | connection.commit() 148 | connection.close() 149 | coins = [] 150 | for tuple in coins_tuples: 151 | coins.append(tuple[0]) 152 | else: 153 | coins = list(self._coin_list.topNVolume(n=self._coin_number).index) 154 | logging.debug("Selected coins are: "+str(coins)) 155 | return coins 156 | 157 | def __checkperiod(self, period): 158 | if period == FIVE_MINUTES: 159 | return 160 | elif period == FIFTEEN_MINUTES: 161 | return 162 | elif period == HALF_HOUR: 163 | return 164 | elif period == TWO_HOUR: 165 | return 166 | elif period == FOUR_HOUR: 167 | return 168 | elif period == DAY: 169 | return 170 | else: 171 | raise ValueError('peroid has to be 5min, 15min, 30min, 2hr, 4hr, or a day') 172 | 173 | # add new history data into the database 174 | def update_data(self, start, end, coin): 175 | connection = sqlite3.connect(DATABASE_DIR) 176 | try: 177 | cursor = connection.cursor() 178 | min_date = cursor.execute('SELECT MIN(date) FROM History WHERE coin=?;', (coin,)).fetchall()[0][0] 179 | max_date = cursor.execute('SELECT MAX(date) FROM History WHERE coin=?;', (coin,)).fetchall()[0][0] 180 | 181 | if min_date==None or max_date==None: 182 | self.__fill_data(start, end, coin, cursor) 183 | else: 184 | if max_date+10*self.__storage_periodstart and self._online: 189 | self.__fill_data(start, min_date - self.__storage_period-1, coin, cursor) 190 | 191 | # NOTE: Check why we use storage_period here. What does Poloniex give 192 | 193 | # if there is no data 194 | finally: 195 | connection.commit() 196 | connection.close() 197 | 198 | def __fill_data(self, start, end, coin, cursor): 199 | duration = 7819200 # three months 200 | bk_start = start 201 | for bk_end in range(start+duration-1, end, duration): 202 | self.__fill_part_data(bk_start, bk_end, coin, cursor) 203 | bk_start += duration 204 | if bk_start < end: 205 | self.__fill_part_data(bk_start, end, coin, cursor) 206 | 207 | def __fill_part_data(self, start, end, coin, cursor): 208 | chart = self._coin_list.get_chart_until_success( 209 | polo = self._coin_list._polo, 210 | pair=self._coin_list.allActiveCoins.at[coin, 'pair'], 211 | start=start, 212 | end=end, 213 | period=self.__storage_period) 214 | logging.info("fill %s data from %s to %s"%(coin, datetime.fromtimestamp(start).strftime('%Y-%m-%d %H:%M'), 215 | datetime.fromtimestamp(end).strftime('%Y-%m-%d %H:%M'))) 216 | for c in chart: 217 | if c["date"] > 0: 218 | if c['weightedAverage'] == 0: 219 | weightedAverage = c['close'] 220 | else: 221 | weightedAverage = c['weightedAverage'] 222 | 223 | #NOTE here the USDT is in reversed order 224 | if 'reversed_' in coin: 225 | cursor.execute('INSERT INTO History VALUES (?,?,?,?,?,?,?,?,?)', 226 | (c['date'],coin,1.0/c['low'],1.0/c['high'],1.0/c['open'], 227 | 1.0/c['close'],c['quoteVolume'],c['volume'], 228 | 1.0/weightedAverage)) 229 | else: 230 | cursor.execute('INSERT INTO History VALUES (?,?,?,?,?,?,?,?,?)', 231 | (c['date'],coin,c['high'],c['low'],c['open'], 232 | c['close'],c['volume'],c['quoteVolume'], 233 | weightedAverage)) 234 | -------------------------------------------------------------------------------- /src/data/poloniex.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | import sys 4 | from datetime import datetime 5 | 6 | from urllib.request import Request, urlopen 7 | from urllib.parse import urlencode 8 | 9 | minute = 60 10 | hour = minute*60 11 | day = hour*24 12 | week = day*7 13 | month = day*30 14 | year = day*365 15 | 16 | 17 | # Possible Commands 18 | PUBLIC_COMMANDS = ['returnTicker', 'return24hVolume', 'returnOrderBook', 'returnTradeHistory', 'returnChartData', 'returnCurrencies', 'returnLoanOrders'] 19 | 20 | class Poloniex: 21 | def __init__(self, APIKey='', Secret=''): 22 | self.APIKey = APIKey.encode() 23 | self.Secret = Secret.encode() 24 | # Conversions 25 | self.timestamp_str = lambda timestamp=time.time(), format="%Y-%m-%d %H:%M:%S": datetime.fromtimestamp(timestamp).strftime(format) 26 | self.str_timestamp = lambda datestr=self.timestamp_str(), format="%Y-%m-%d %H:%M:%S": int(time.mktime(time.strptime(datestr, format))) 27 | self.float_roundPercent = lambda floatN, decimalP=2: str(round(float(floatN) * 100, decimalP))+"%" 28 | 29 | # PUBLIC COMMANDS 30 | self.marketTicker = lambda x=0: self.api('returnTicker') 31 | self.marketVolume = lambda x=0: self.api('return24hVolume') 32 | self.marketStatus = lambda x=0: self.api('returnCurrencies') 33 | self.marketLoans = lambda coin: self.api('returnLoanOrders',{'currency':coin}) 34 | self.marketOrders = lambda pair='all', depth=10:\ 35 | self.api('returnOrderBook', {'currencyPair':pair, 'depth':depth}) 36 | self.marketChart = lambda pair, period=day, start=time.time()-(week*1), end=time.time(): self.api('returnChartData', {'currencyPair':pair, 'period':period, 'start':start, 'end':end}) 37 | self.marketTradeHist = lambda pair: self.api('returnTradeHistory',{'currencyPair':pair}) # NEEDS TO BE FIXED ON Poloniex 38 | 39 | ##################### 40 | # Main Api Function # 41 | ##################### 42 | def api(self, command, args={}): 43 | """ 44 | returns 'False' if invalid command or if no APIKey or Secret is specified (if command is "private") 45 | returns {"error":""} if API error 46 | """ 47 | if command in PUBLIC_COMMANDS: 48 | url = 'https://poloniex.com/public?' 49 | args['command'] = command 50 | ret = urlopen(Request(url + urlencode(args))) 51 | return json.loads(ret.read().decode(encoding='UTF-8')) 52 | else: 53 | return False 54 | 55 | -------------------------------------------------------------------------------- /src/data/replayBuffer.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import numpy as np 3 | 4 | class ReplayBuffer: 5 | def __init__(self, start_index, end_index, batch_size, coin_no, sample_bias=1.0): 6 | """ 7 | :param start_index: start index of the training set on the global data matrices 8 | :param end_index: end index of the training set on the global data matrices 9 | """ 10 | self.__coin_no = coin_no 11 | self.__experiences = [Experience(i) for i in range(start_index, end_index)] 12 | # NOTE: in order to achieve the previous w feature 13 | self.__batch_size = batch_size 14 | self.__sample_bias = sample_bias 15 | logging.debug("buffer_bias is %f" % sample_bias) 16 | 17 | def append_experience(self, state_index): 18 | self.__experiences.append(Experience(state_index)) 19 | logging.debug("a new experience, indexed by %d, was appended" % state_index) 20 | 21 | def __sample(self, start, end, bias): 22 | """ 23 | @:param end: is excluded 24 | @:param bias: value in (0, 1) 25 | """ 26 | # TODO: deal with the case when bias is 0 27 | ran = np.random.geometric(bias) 28 | while ran > end - start: 29 | ran = np.random.geometric(bias) 30 | result = end - ran 31 | return result 32 | 33 | def next_experience_batch(self): 34 | # First get a start point randomly 35 | batch_start = self.__sample(0, len(self.__experiences) - self.__batch_size, 36 | self.__sample_bias) 37 | batch = self.__experiences[batch_start:batch_start+self.__batch_size] 38 | return batch 39 | 40 | 41 | class Experience: 42 | def __init__(self, state_index): 43 | self.state_index = int(state_index) 44 | 45 | -------------------------------------------------------------------------------- /src/network.py: -------------------------------------------------------------------------------- 1 | # Cols are time and rows are coins 2 | # Channels first 3 | 4 | import torch 5 | import torch.nn as nn 6 | import torch.nn.functional as F 7 | 8 | 9 | class CNN(nn.Module): 10 | def __init__(self, in_features, rows, cols, layers, device = torch.device("cpu")): 11 | super(CNN, self).__init__() 12 | 13 | out1 = 2 14 | out2 = 20 15 | kernel1 = (1,3) 16 | kernel2 = (1,cols-2) # cols - (kernel1[1] - 1) 17 | 18 | self.conv1 = nn.Conv2d(in_features, out1, kernel1) 19 | self.conv2 = nn.Conv2d(out1, out2, kernel2) 20 | self.votes = nn.Conv2d(out2+1, 1, (1,1)) # input features is out2 plus the appended last_weights 21 | # BTC bias 22 | b = torch.zeros((1,1)) 23 | self.b = nn.Parameter(b) 24 | 25 | def forward(self, x, w): 26 | x = x / x[:, 0, None, :, -1, None] 27 | x = self.conv1(x) 28 | x = F.relu(x) 29 | x = self.conv2(x) 30 | x = F.relu(x) 31 | x = torch.cat((x, w),dim=1) 32 | x = self.votes(x) 33 | x = torch.squeeze(x) 34 | 35 | cash = self.b.repeat(x.size()[0], 1) 36 | x = torch.cat((cash, x), dim=1) 37 | return F.softmax(x, dim=1) 38 | 39 | -------------------------------------------------------------------------------- /src/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreaslillevangbech/PortfolioManager-pytorch/2980cfb2a2097b1fc61cecc9d989559717989184/src/tools/__init__.py -------------------------------------------------------------------------------- /src/tools/configprocess.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import calendar 4 | from datetime import datetime 5 | import json 6 | import os 7 | rootpath = os.path.dirname(os.path.abspath(__file__)).\ 8 | replace("\\pgportfolio\\tools", "").replace("/pgportfolio/tools","") 9 | 10 | try: 11 | unicode # Python 2 12 | except NameError: 13 | unicode = str # Python 3 14 | 15 | 16 | def preprocess_config(config): 17 | fill_default(config) 18 | if sys.version_info[0] == 2: 19 | return byteify(config) 20 | else: 21 | return config 22 | 23 | 24 | def fill_default(config): 25 | set_missing(config, "random_seed", 0) 26 | set_missing(config, "agent_type", "NNAgent") 27 | fill_layers_default(config["layers"]) 28 | fill_input_default(config["input"]) 29 | fill_train_config(config["training"]) 30 | 31 | 32 | def fill_train_config(train_config): 33 | set_missing(train_config, "fast_train", True) 34 | set_missing(train_config, "decay_rate", 1.0) 35 | set_missing(train_config, "decay_steps", 50000) 36 | 37 | 38 | def fill_input_default(input_config): 39 | set_missing(input_config, "save_memory_mode", False) 40 | set_missing(input_config, "portion_reversed", False) 41 | set_missing(input_config, "market", "poloniex") 42 | set_missing(input_config, "norm_method", "absolute") 43 | set_missing(input_config, "is_permed", False) 44 | set_missing(input_config, "fake_ratio", 1) 45 | 46 | 47 | def fill_layers_default(layers): 48 | for layer in layers: 49 | if layer["type"] == "ConvLayer": 50 | set_missing(layer, "padding", "valid") 51 | set_missing(layer, "strides", [1, 1]) 52 | set_missing(layer, "activation_function", "relu") 53 | set_missing(layer, "regularizer", None) 54 | set_missing(layer, "weight_decay", 0.0) 55 | elif layer["type"] == "EIIE_Dense": 56 | set_missing(layer, "activation_function", "relu") 57 | set_missing(layer, "regularizer", None) 58 | set_missing(layer, "weight_decay", 0.0) 59 | elif layer["type"] == "DenseLayer": 60 | set_missing(layer, "activation_function", "relu") 61 | set_missing(layer, "regularizer", None) 62 | set_missing(layer, "weight_decay", 0.0) 63 | elif layer["type"] == "EIIE_LSTM" or layer["type"] == "EIIE_RNN": 64 | set_missing(layer, "dropouts", None) 65 | elif layer["type"] == "EIIE_Output" or\ 66 | layer["type"] == "Output_WithW" or\ 67 | layer["type"] == "EIIE_Output_WithW": 68 | set_missing(layer, "regularizer", None) 69 | set_missing(layer, "weight_decay", 0.0) 70 | elif layer["type"] == "DropOut": 71 | pass 72 | else: 73 | raise ValueError("layer name {} not supported".format(layer["type"])) 74 | 75 | 76 | def set_missing(config, name, value): 77 | if name not in config: 78 | config[name] = value 79 | 80 | 81 | def byteify(input): 82 | if isinstance(input, dict): 83 | return {byteify(key): byteify(value) 84 | for key, value in input.iteritems()} 85 | elif isinstance(input, list): 86 | return [byteify(element) for element in input] 87 | elif isinstance(input, unicode): 88 | return str(input) 89 | else: 90 | return input 91 | 92 | 93 | def parse_time(time_string): 94 | return calendar.timegm(datetime.strptime(time_string, "%Y/%m/%d").timetuple()) 95 | 96 | 97 | def load_config(index=None): 98 | """ 99 | @:param index: if None, load the default in pgportfolio; 100 | if a integer, load the config under train_package 101 | """ 102 | if index: 103 | with open(rootpath+"/train_package/" + str(index) + "/net_config.json") as file: 104 | config = json.load(file) 105 | else: 106 | with open(rootpath+"/pgportfolio/" + "net_config.json") as file: 107 | config = json.load(file) 108 | return preprocess_config(config) 109 | 110 | 111 | def check_input_same(config1, config2): 112 | input1 = config1["input"] 113 | input2 = config2["input"] 114 | if input1["start_date"] != input2["start_date"]: 115 | return False 116 | elif input1["end_date"] != input2["end_date"]: 117 | return False 118 | elif input1["test_portion"] != input2["test_portion"]: 119 | return False 120 | else: 121 | return True 122 | 123 | -------------------------------------------------------------------------------- /src/tools/data.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | 5 | def pricenorm3d(m, features, norm_method, fake_ratio=1.0, with_y=True): 6 | """normalize the price tensor, whose shape is [features, coins, windowsize] 7 | @:param m: input tensor, unnormalized and there could be nan in it 8 | @:param with_y: if the tensor include y (future price) 9 | logging.debug("price are %s" % (self._latest_price_matrix[0, :, -1])) 10 | """ 11 | result = m.copy() 12 | if features[0] != "close": 13 | raise ValueError("first feature must be close") 14 | for i, feature in enumerate(features): 15 | if with_y: 16 | one_position = 2 17 | else: 18 | one_position = 1 19 | pricenorm2d(result[i], m[0, :, -one_position], norm_method=norm_method, 20 | fake_ratio=fake_ratio, one_position=one_position) 21 | return result 22 | 23 | 24 | # input m is a 2d matrix, (coinnumber+1) * windowsize 25 | def pricenorm2d(m, reference_column, 26 | norm_method="absolute", fake_ratio=1.0, one_position=2): 27 | if norm_method=="absolute": 28 | output = np.zeros(m.shape) 29 | for row_number, row in enumerate(m): 30 | if np.isnan(row[-one_position]) or np.isnan(reference_column[row_number]): 31 | row[-one_position] = 1.0 32 | for index in range(row.shape[0] - one_position + 1): 33 | if index > 0: 34 | row[-one_position - index] = row[-index - one_position + 1] / fake_ratio 35 | row[-one_position] = 1.0 36 | row[-1] = fake_ratio 37 | else: 38 | row = row / reference_column[row_number] 39 | for index in range(row.shape[0] - one_position + 1): 40 | if index > 0 and np.isnan(row[-one_position - index]): 41 | row[-one_position - index] = row[-index - one_position + 1] / fake_ratio 42 | if np.isnan(row[-1]): 43 | row[-1] = fake_ratio 44 | output[row_number] = row 45 | m[:] = output[:] 46 | elif norm_method=="relative": 47 | output = m[:, 1:] 48 | divisor = m[:, :-1] 49 | output = output / divisor 50 | pad = np.empty((m.shape[0], 1,)) 51 | pad.fill(np.nan) 52 | m[:] = np.concatenate((pad, output), axis=1) 53 | m[np.isnan(m)] = fake_ratio 54 | else: 55 | raise ValueError("there is no norm morthod called %s" % norm_method) 56 | 57 | 58 | def get_chart_until_success(polo, pair, start, period, end): 59 | is_connect_success = False 60 | chart = {} 61 | while not is_connect_success: 62 | try: 63 | chart = polo.marketChart(pair=pair, start=int(start), period=int(period), end=int(end)) 64 | is_connect_success = True 65 | except Exception as e: 66 | print(e) 67 | return chart 68 | 69 | 70 | def get_type_list(feature_number): 71 | """ 72 | :param feature_number: an int indicates the number of features 73 | :return: a list of features n 74 | """ 75 | if feature_number == 1: 76 | type_list = ["close"] 77 | elif feature_number == 2: 78 | type_list = ["close", "volume"] 79 | raise NotImplementedError("the feature volume is not supported currently") 80 | elif feature_number == 3: 81 | type_list = ["close", "high", "low"] 82 | elif feature_number == 4: 83 | type_list = ["close", "high", "low", "open"] 84 | else: 85 | raise ValueError("feature number could not be %s" % feature_number) 86 | return type_list 87 | 88 | 89 | def panel2array(panel): 90 | """convert the panel to datatensor (numpy array) without btc 91 | """ 92 | without_btc = np.transpose(panel.values, axes=(2, 0, 1)) 93 | return without_btc 94 | 95 | 96 | def count_periods(start, end, period_length): 97 | """ 98 | :param start: unix time, excluded 99 | :param end: unix time, included 100 | :param period_length: length of the period 101 | :return: 102 | """ 103 | return (int(end)-int(start)) // period_length 104 | 105 | 106 | def get_volume_forward(time_span, portion, portion_reversed): 107 | volume_forward = 0 108 | if not portion_reversed: 109 | volume_forward = time_span*portion 110 | return volume_forward 111 | 112 | 113 | def panel_fillna(panel, type="bfill"): 114 | """ 115 | fill nan along the 3rd axis 116 | :param panel: the panel to be filled 117 | :param type: bfill or ffill 118 | """ 119 | frames = {} 120 | for item in panel.items: 121 | if type == "both": 122 | frames[item] = panel.loc[item].fillna(axis=1, method="bfill").\ 123 | fillna(axis=1, method="ffill") 124 | else: 125 | frames[item] = panel.loc[item].fillna(axis=1, method=type) 126 | return pd.Panel(frames) 127 | 128 | 129 | def xarray_fillna(panel, type="bfill"): 130 | if type=="both": 131 | panel = panel.bfill(dim = 'time_index').ffill(dim = 'time_index') 132 | elif type=="bfill": 133 | panel = panel.bfill(dim = 'time_index') 134 | else: 135 | panel = panel.ffill(dim = 'time_index') 136 | return panel 137 | -------------------------------------------------------------------------------- /src/tools/indicator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def max_drawdown(pc_array): 5 | """calculate the max drawdown with the portfolio changes 6 | @:param pc_array: all the portfolio changes during a trading process 7 | @:return: max drawdown 8 | """ 9 | portfolio_values = [] 10 | drawdown_list = [] 11 | max_benefit = 0 12 | for i in range(pc_array.shape[0]): 13 | if i > 0: 14 | portfolio_values.append(portfolio_values[i - 1] * pc_array[i]) 15 | else: 16 | portfolio_values.append(pc_array[i]) 17 | if portfolio_values[i] > max_benefit: 18 | max_benefit = portfolio_values[i] 19 | drawdown_list.append(0.0) 20 | else: 21 | drawdown_list.append(1.0 - portfolio_values[i] / max_benefit) 22 | return max(drawdown_list) 23 | 24 | 25 | def sharpe(pc_array): 26 | """calculate sharpe ratio with the portfolio changes 27 | @:param pc_array: all the portfolio changes during a trading process 28 | @:return: sharpe ratio 29 | """ 30 | pc_array = pc_array-1.0 31 | return np.mean(pc_array)/np.std(pc_array) 32 | 33 | 34 | def moving_accumulate(pc_array, n=48): 35 | acc = np.cumprod(pc_array) 36 | acc[n:] = acc[n:] / acc[:-n] 37 | return acc 38 | 39 | 40 | def positive_count(pc_array): 41 | return np.sum(pc_array>1) 42 | 43 | 44 | def negative_count(pc_array): 45 | return np.sum(pc_array<1) 46 | -------------------------------------------------------------------------------- /src/tools/shortcut.py: -------------------------------------------------------------------------------- 1 | from pgportfolio.trade.backtest import BackTest 2 | from pgportfolio.tdagent.algorithms import crp, ons, olmar, up, anticor1, pamr,\ 3 | best, bk, cwmr_std, eg, sp, ubah, wmamr, bcrp, cornk, m0, rmr 4 | 5 | # the dictionary of name of algorithms mapping to the constructor of tdagents 6 | ALGOS = {"crp": crp.CRP, "ons": ons.ONS, "olmar": olmar.OLMAR, "up": up.UP, 7 | "anticor": anticor1.ANTICOR1, "pamr": pamr.PAMR, 8 | "best": best.BEST, "bk": bk.BK, "bcrp": bcrp.BCRP, 9 | "corn": cornk.CORNK, "m0": m0.M0, "rmr": rmr.RMR, 10 | "cwmr": cwmr_std.CWMR_STD, "eg": eg.EG, "sp": sp.SP, "ubah": ubah.UBAH, 11 | "wmamr": wmamr.WMAMR} 12 | 13 | def execute_backtest(algo, config): 14 | """ 15 | @:param algo: string representing the name the name of algorithms 16 | @:return: numpy array of portfolio changes 17 | """ 18 | agent, agent_type, net_dir = _construct_agent(algo) 19 | backtester = BackTest(config, agent=agent, agent_type=agent_type, net_dir=net_dir) 20 | backtester.start_trading() 21 | return backtester.test_pc_vector 22 | 23 | 24 | def _construct_agent(algo): 25 | if algo.isdigit(): 26 | agent = None 27 | agent_type = "nn" 28 | net_dir = "./train_package/" + algo + "/netfile" 29 | elif algo in ALGOS: 30 | agent = ALGOS[algo]() 31 | agent_type = "traditional" 32 | net_dir = None 33 | else: 34 | message = "The algorithm name "+algo+" is not support. Supported algos " \ 35 | "are " + str(list(ALGOS.keys())) 36 | raise LookupError(message) 37 | return agent, agent_type, net_dir -------------------------------------------------------------------------------- /src/tools/trade.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pgportfolio.marketdata.datamatrices import DataMatrices 3 | from pgportfolio.marketdata.globaldatamatrix import HistoryManager 4 | from pgportfolio.tools.configprocess import parse_time 5 | from pgportfolio.constants import * 6 | from pgportfolio.tools.data import get_volume_forward 7 | from time import time 8 | 9 | 10 | def get_coin_name_list(config, online): 11 | """ 12 | :param online: boolean value to show if connected to internet, 13 | if False, load data from database. 14 | :return : list of coin names 15 | """ 16 | input_config = config["input"] 17 | if not online: 18 | start = parse_time(input_config["start_date"]) 19 | end = parse_time(input_config["end_date"]) 20 | volume_forward = get_volume_forward(end - start, 21 | input_config["test_portion"] 22 | + input_config["validation_portion"], 23 | input_config["portion_reversed"]) 24 | else: 25 | end = time() 26 | volume_forward = 0 27 | end = end - (end % input_config["trade_period"]) 28 | start = end - volume_forward - input_config["volume_average_days"] * DAY 29 | end = end - volume_forward 30 | coins = HistoryManager(input_config["coin_number"], end, 31 | volume_forward=volume_forward, 32 | volume_average_days=input_config["volume_average_days"], 33 | online=online).\ 34 | select_coins(start, end) 35 | return coins 36 | 37 | 38 | def calculate_pv_after_commission(w1, w0, commission_rate): 39 | """ 40 | @:param w1: target portfolio vector, first element is btc 41 | @:param w0: rebalanced last period portfolio vector, first element is btc 42 | @:param commission_rate: rate of commission fee, proportional to the transaction cost 43 | """ 44 | mu0 = 1 45 | mu1 = 1 - 2*commission_rate + commission_rate ** 2 46 | while abs(mu1-mu0) > 1e-10: 47 | mu0 = mu1 48 | mu1 = (1 - commission_rate * w0[0] - 49 | (2 * commission_rate - commission_rate ** 2) * 50 | np.sum(np.maximum(w0[1:] - mu1*w1[1:], 0))) / \ 51 | (1 - commission_rate * w1[0]) 52 | return mu1 53 | 54 | 55 | def get_test_data(config): 56 | """ 57 | :return : a 2d numpy array with shape(coin_number, periods), 58 | each element the relative price 59 | """ 60 | config["input"]["feature_number"] = 1 61 | config["input"]["norm_method"] = "relative" 62 | config["input"]["global_period"] = config["input"]["global_period"] 63 | price_matrix = DataMatrices.create_from_config(config) 64 | test_set = price_matrix.get_test_set()["y"][:, 0, :].T 65 | test_set = np.concatenate((np.ones((1, test_set.shape[1])), test_set), axis=0) 66 | return test_set 67 | 68 | 69 | def asset_vector_to_dict(coin_list, vector, with_BTC=True): 70 | vector = np.squeeze(vector) 71 | dict_coin = {} 72 | if with_BTC: 73 | dict_coin['BTC'] = vector[0] 74 | for i, name in enumerate(coin_list): 75 | if vector[i+1] > 0: 76 | dict_coin[name] = vector[i + 1] 77 | return dict_coin 78 | 79 | 80 | def save_test_data(config, file_name="test_data", output_format="csv"): 81 | if output_format == "csv": 82 | matrix = get_test_data(config) 83 | with open(file_name+"."+output_format, 'wb') as f: 84 | np.savetxt(f, matrix.T, delimiter=",") 85 | 86 | -------------------------------------------------------------------------------- /src/trader.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | 4 | from src.trainer import Trainer 5 | 6 | class Trader: 7 | def __init__(self, config, wait_period, total_steps, initial_BTC=1): 8 | self.steps = 0 9 | self.total_steps = total_steps 10 | self.wait_period = wait_period 11 | 12 | # the total assets is calculated with BTC 13 | self._total_capital = initial_BTC 14 | self._window_size = config["input"]["window_size"] 15 | self._coin_number = config["input"]["coin_no"] 16 | self._commission_rate = config["trading"]["trading_consumption"] 17 | self._asset_vector = np.zeros(self._coin_number+1) 18 | 19 | self._last_omega = np.zeros((self._coin_number+1,)) 20 | self._last_omega[0] = 1.0 21 | 22 | 23 | def _initialize_logging_data_frame(self, initial_BTC): 24 | logging_dict = {'Total Asset (BTC)': initial_BTC, 'BTC': 1} 25 | for coin in self._coin_name_list: 26 | logging_dict[coin] = 0 27 | self._logging_data_frame = pd.DataFrame(logging_dict, index=pd.to_datetime([time.time()], unit='s')) -------------------------------------------------------------------------------- /src/trainer.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import logging 4 | import time 5 | import numpy as np 6 | import torch 7 | from torch.utils.tensorboard import SummaryWriter 8 | from tqdm import trange 9 | 10 | from src.data.datamatrices import DataMatrices 11 | from src.agent import Agent 12 | 13 | 14 | class Trainer: 15 | def __init__(self, config, agent = None, save_path = None, restore_dir = None, device = "cpu"): 16 | self.config = config 17 | self.train_config = config["training"] 18 | self.input_config = config["input"] 19 | self.best_metric = 0 20 | self.save_path = save_path 21 | 22 | self._matrix = DataMatrices.create_from_config(config) 23 | self.time_index = self._matrix._DataMatrices__global_data.time_index.values 24 | self.coins = self._matrix._DataMatrices__global_data.coins.values 25 | self.test_set = self._matrix.get_test_set() 26 | self.training_set = self._matrix.get_training_set() 27 | 28 | torch.random.manual_seed(config["random_seed"]) 29 | self.device = device 30 | self._agent = Agent( 31 | config, 32 | time_index=self.time_index, 33 | coins=self.coins, 34 | restore_dir=restore_dir, 35 | device=device) 36 | 37 | def __init_tensorboard(self, log_file_dir = 'logs/'): 38 | # current_time = datetime.datetime.now().strftime("%Y%m%d-H%M%S") 39 | train_log_dir = os.path.join(log_file_dir, 'train') 40 | test_log_dir = os.path.join(log_file_dir, 'test') 41 | self.train_writer = SummaryWriter(train_log_dir) 42 | self.test_writer = SummaryWriter(test_log_dir) 43 | network_writer = SummaryWriter(os.path.join(log_file_dir, 'graph')) 44 | X, w, _, _ = self.next_batch() 45 | network_writer.add_graph(self._agent.model, [X, w]) 46 | network_writer.close() 47 | 48 | @staticmethod 49 | def calculate_upperbound(y): 50 | array = np.maximum.reduce(y[:, 0, :], 1) 51 | total = 1.0 52 | for i in array: 53 | total = total * i 54 | return total 55 | 56 | def __print_upperbound(self): 57 | upperbound_test = self.calculate_upperbound(self.test_set["y"]) 58 | logging.info("upper bound in test is %s" % upperbound_test) 59 | 60 | def _evaluate(self, set_name): 61 | if set_name=="test": 62 | batch = self.test_set 63 | elif set_name == "training": 64 | batch = self.training_set 65 | else: 66 | raise ValueError() 67 | w = torch.tensor(batch['last_w']) 68 | w = w[:, None, : , None] # Concat along dim=1, the features dim) 69 | X = torch.tensor(batch['X']) 70 | y = torch.tensor(batch['y']) 71 | pv_vector, loss, output = self._agent.evaluate(X, w, y) 72 | return pv_vector, loss, output 73 | 74 | def log_between_steps(self, step): 75 | fast_train = self.train_config["fast_train"] 76 | 77 | # Summary on test set. Evaluating the agent updates the agents metrics 78 | pv_vector, v_loss, v_output = self._evaluate("test") 79 | # Get some stats 80 | v_pv = self._agent.portfolio_value 81 | v_log_mean = self._agent.log_mean 82 | log_mean_free = self._agent.log_mean_free 83 | 84 | self.test_writer.add_scalar('portfolio value', self._agent.portfolio_value, global_step=step) 85 | self.test_writer.add_scalar('mean', self._agent.mean, global_step=step) 86 | self.test_writer.add_scalar('log_mean', self._agent.log_mean, global_step=step) 87 | self.test_writer.add_scalar('std', self._agent.standard_deviation, global_step=step) 88 | self.test_writer.add_scalar('loss', v_loss,global_step=step) 89 | self.test_writer.add_scalar("log_mean_free", self._agent.log_mean_free,global_step=step) 90 | for name, param in self._agent.model.named_parameters(): 91 | self.test_writer.add_histogram(name, param, global_step=step) 92 | 93 | # Save model 94 | if v_pv > self.best_metric: 95 | self.best_metric = v_pv 96 | logging.info("get better model at %s steps," 97 | " whose test portfolio value is %s" % (step, self._agent.portfolio_value)) 98 | if self.save_path: 99 | torch.save(self._agent.model.state_dict(), self.save_path) 100 | # self._agent.model.save_weights(self.save_path) 101 | 102 | if not fast_train: 103 | pv_vector, loss, output = self._evaluate("training") 104 | self.train_writer.add_scalar('portfolio value', self._agent.portfolio_value,global_step=step) 105 | self.train_writer.add_scalar('mean', self._agent.mean,global_step=step) 106 | self.train_writer.add_scalar('log_mean', self._agent.log_mean,global_step=step) 107 | self.train_writer.add_scalar('std', self._agent.standard_deviation,global_step=step) 108 | self.train_writer.add_scalar('loss', loss,global_step=step) 109 | self.train_writer.add_scalar("log_mean_free", self._agent.log_mean_free,global_step=step) 110 | for name, param in self._agent.model.named_parameters(): 111 | self.train_writer.add_histogram(name, param, global_step=step) 112 | for name, p in self._agent.model.named_parameters(): 113 | self.train_writer.add_histogram(name + '/gradient', p.grad, global_step=step) 114 | 115 | # print 'ouput is %s' % out 116 | logging.info('='*30) 117 | logging.info('step %d' % step) 118 | logging.info('-'*30) 119 | if not fast_train: 120 | logging.info('training loss is %s\n' % loss) 121 | logging.info('the portfolio value on test set is %s\nlog_mean is %s\n' 122 | 'loss_value is %3f\nlog mean without commission fee is %3f\n' % \ 123 | (v_pv, v_log_mean, v_loss, log_mean_free)) 124 | logging.info('='*30+"\n") 125 | 126 | # Dunno what this is for. 127 | self.check_abnormal(self._agent.portfolio_value, output) 128 | 129 | def check_abnormal(self, portfolio_value, weigths): 130 | if portfolio_value == 1.0: 131 | logging.info("average portfolio weights {}".format(weigths.mean(axis=0))) 132 | 133 | def next_batch(self): 134 | batch = self._matrix.next_batch() 135 | w = torch.tensor(batch['last_w']) 136 | w = w[:, None, : , None] # Concat along dim=1, the features dim) 137 | X = torch.tensor(batch['X']) 138 | y = torch.tensor(batch['y']) 139 | return X, w, y, batch['setw'] 140 | 141 | def train(self, log_file_dir = "./tensorboard", index = "0"): 142 | 143 | self.__print_upperbound() 144 | self.__init_tensorboard(log_file_dir) 145 | 146 | starttime = time.time() 147 | total_data_time = 0 148 | total_training_time = 0 149 | for i in range(int(self.train_config['steps'])): 150 | step_start = time.time() 151 | X, w, y, setw = self.next_batch() 152 | finish_data = time.time() 153 | total_data_time += (finish_data - step_start) 154 | self._agent.train_step(X, w, y, setw=setw) 155 | total_training_time += time.time() - finish_data 156 | if i % 1000 == 0 and log_file_dir: 157 | logging.info("average time for data accessing is %s"%(total_data_time/1000)) 158 | logging.info("average time for training is %s"%(total_training_time/1000)) 159 | total_training_time = 0 160 | total_data_time = 0 161 | self.log_between_steps(i) 162 | 163 | if self.save_path: 164 | best_agent = Agent(self.config, restore_dir=self.save_path) 165 | self._agent = best_agent 166 | 167 | pv_vector, loss, output = self._evaluate("test") 168 | pv = self._agent.portfolio_value 169 | log_mean = self._agent.log_mean 170 | logging.warning('the portfolio value train No.%s is %s log_mean is %s,' 171 | ' the training time is %d seconds' % (index, pv, log_mean, time.time() - starttime)) 172 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import logging 3 | from src.trainer import Trainer 4 | from config import config 5 | import os 6 | 7 | def train_one(save_path, config, log_file_dir, index, logfile_level, console_level): 8 | if log_file_dir: 9 | logging.basicConfig(filename=log_file_dir.replace("tensorboard","programlog"), 10 | level=logfile_level) 11 | console = logging.StreamHandler() 12 | console.setLevel(console_level) 13 | logging.getLogger().addHandler(console) 14 | print("training at %s started" % index) 15 | return Trainer(config, save_path=save_path).train(log_file_dir=log_file_dir) 16 | 17 | train_dir = "train_package" 18 | if not os.path.exists("./" + train_dir): 19 | os.makedirs("./" + train_dir) 20 | 21 | train_one( 22 | save_path="./" + train_dir + "/model", 23 | config=config, 24 | log_file_dir="./" + train_dir + "/tensorboard", 25 | index="0", 26 | logfile_level=logging.DEBUG, 27 | console_level=logging.INFO 28 | ) 29 | 30 | 31 | -------------------------------------------------------------------------------- /train_package/model: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreaslillevangbech/PortfolioManager-pytorch/2980cfb2a2097b1fc61cecc9d989559717989184/train_package/model -------------------------------------------------------------------------------- /train_package/tensorboard/graph/events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreaslillevangbech/PortfolioManager-pytorch/2980cfb2a2097b1fc61cecc9d989559717989184/train_package/tensorboard/graph/events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.2 -------------------------------------------------------------------------------- /train_package/tensorboard/test/events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreaslillevangbech/PortfolioManager-pytorch/2980cfb2a2097b1fc61cecc9d989559717989184/train_package/tensorboard/test/events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.1 -------------------------------------------------------------------------------- /train_package/tensorboard/train/events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreaslillevangbech/PortfolioManager-pytorch/2980cfb2a2097b1fc61cecc9d989559717989184/train_package/tensorboard/train/events.out.tfevents.1603707949.Andreass-MacBook-Pro.local.48469.0 --------------------------------------------------------------------------------