├── .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 | " date | \n",
113 | " high | \n",
114 | " low | \n",
115 | " open | \n",
116 | " close | \n",
117 | " volume | \n",
118 | " quoteVolume | \n",
119 | " weightedAverage | \n",
120 | "
\n",
121 | " \n",
122 | " \n",
123 | " \n",
124 | " 0 | \n",
125 | " 2020-01-20 03:00:00 | \n",
126 | " 0.019132 | \n",
127 | " 0.019110 | \n",
128 | " 0.019110 | \n",
129 | " 0.019127 | \n",
130 | " 0.292989 | \n",
131 | " 15.321845 | \n",
132 | " 0.019122 | \n",
133 | "
\n",
134 | " \n",
135 | " 1 | \n",
136 | " 2020-01-20 03:15:00 | \n",
137 | " 0.019135 | \n",
138 | " 0.019119 | \n",
139 | " 0.019127 | \n",
140 | " 0.019119 | \n",
141 | " 1.425917 | \n",
142 | " 74.552350 | \n",
143 | " 0.019126 | \n",
144 | "
\n",
145 | " \n",
146 | " 2 | \n",
147 | " 2020-01-20 03:30:00 | \n",
148 | " 0.019131 | \n",
149 | " 0.019119 | \n",
150 | " 0.019123 | \n",
151 | " 0.019128 | \n",
152 | " 1.392732 | \n",
153 | " 72.818979 | \n",
154 | " 0.019126 | \n",
155 | "
\n",
156 | " \n",
157 | " 3 | \n",
158 | " 2020-01-20 03:45:00 | \n",
159 | " 0.019135 | \n",
160 | " 0.019103 | \n",
161 | " 0.019130 | \n",
162 | " 0.019111 | \n",
163 | " 1.781537 | \n",
164 | " 93.159341 | \n",
165 | " 0.019124 | \n",
166 | "
\n",
167 | " \n",
168 | " 4 | \n",
169 | " 2020-01-20 04:00:00 | \n",
170 | " 0.019148 | \n",
171 | " 0.019089 | \n",
172 | " 0.019113 | \n",
173 | " 0.019111 | \n",
174 | " 3.112476 | \n",
175 | " 162.720579 | \n",
176 | " 0.019128 | \n",
177 | "
\n",
178 | " \n",
179 | " 5 | \n",
180 | " 2020-01-20 04:15:00 | \n",
181 | " 0.019113 | \n",
182 | " 0.019075 | \n",
183 | " 0.019109 | \n",
184 | " 0.019078 | \n",
185 | " 0.322440 | \n",
186 | " 16.883259 | \n",
187 | " 0.019098 | \n",
188 | "
\n",
189 | " \n",
190 | "
\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": "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\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
--------------------------------------------------------------------------------