diff --git "a/fin_rl_qlearning_v1-4.ipynb" "b/fin_rl_qlearning_v1-4.ipynb" new file mode 100644--- /dev/null +++ "b/fin_rl_qlearning_v1-4.ipynb" @@ -0,0 +1,1717 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "nwaAZRu1NTiI" + }, + "source": [ + "# Q-learning \n", + "\n", + "#### This version implements q-learning using a custom enviroment 1 day, with synthetic data, this version uses numpy elements as state\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "DDf1gLC2NTiK" + }, + "outputs": [], + "source": [ + "# !pip install -r ./requirements.txt\n", + "# !pip install stable_baselines3[extra]\n", + "# !pip install yfinance\n", + "# !pip install talib-binary\n", + "# !pip install huggingface_sb3\n" + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "metadata": { + "id": "LNXxxKojNTiL" + }, + "outputs": [], + "source": [ + "import gym\n", + "from gym import spaces\n", + "from gym.utils import seeding\n", + "\n", + "import talib as ta\n", + "from tqdm.notebook import tqdm\n", + "\n", + "import yfinance as yf\n", + "import pandas as pd\n", + "import numpy as np\n", + "from matplotlib import pyplot as plt\n" + ] + }, + { + "cell_type": "code", + "execution_count": 143, + "metadata": {}, + "outputs": [], + "source": [ + "def get_syntetic_data(tf, start_date, end_date, plot=True, add_noise=None):\n", + " df = pd.date_range(start=start_date, end=end_date, freq=tf)\n", + " df = df.to_frame()\n", + "\n", + " df['v1'] = np.arange(len(df.index))\n", + " df[['Open','High','Low','Close','Volume']] = 0.0\n", + " df = df.drop([0], axis=1)\n", + "\n", + " # df[\"Close\"]=df[\"v1\"].map(lambda x: np.sin(x)+10 )\n", + " df[\"Close\"]=df[\"v1\"].map(lambda x: np.sin(x)+10 + np.sin(x/2) )\n", + " if add_noise is not None: # could be 0.5\n", + " noise = np.random.normal(0, add_noise, len(df))\n", + " df[\"Close\"] += noise\n", + "\n", + " if plot:\n", + " plt.figure(figsize=(15,6))\n", + " df['Close'].tail(30).plot()\n", + "\n", + " df[\"Open\"]=df[\"Close\"].shift(1)\n", + " df = df.dropna()\n", + " x = 1.5\n", + " df[\"High\"] = np.where( df[\"Close\"] > df['Open'], df[\"Close\"]+x, df[\"Open\"]+x )\n", + " df[\"Low\"] = np.where( df[\"Close\"] < df['Open'], df[\"Close\"]-x, df[\"Open\"]-x )\n", + " df[\"Volume\"] = 10\n", + " return df" + ] + }, + { + "cell_type": "code", + "execution_count": 144, + "metadata": { + "id": "dmAuEhZZNTiL" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3080\n", + "1931\n" + ] + } + ], + "source": [ + "# Get data\n", + "eth_usd = yf.Ticker(\"ETH-USD\")\n", + "eth = eth_usd.history(period=\"max\")\n", + "\n", + "btc_usd = yf.Ticker(\"BTC-USD\")\n", + "btc = btc_usd.history(period=\"max\")\n", + "print(len(btc))\n", + "print(len(eth))\n", + "\n", + "btc_train = eth[-3015:-200]\n", + "# btc_test = eth[-200:]\n", + "eth_train = eth[-1864:-200]\n", + "eth_test = eth[-200:]\n", + "# len(eth_train)" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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+ixdfScWi39/3nHS17dfi4tK5vYLJo3eDyEiQUlFn4iwaGIaX75yGj96bi+XTM/k5kX57/s99ceOc/9fq7lxfi5z8pIDkFJERM7E7TIkx0aSHHv2HRsiMj7oXr6IAPDZy6aydHIa//aXSg42d73v+T9+bi8W+OdrZox9OBEREREZNhV9IgIMXSH+2eoFREW4+PyDW+nznn6luLfqO3hsax13XDBpRK1IIiIiIjL2VPSJyDuyk2P44U3z2FXfwX+s33Pa875XVkVSTCT/dNnUAKYTERERkeFQ0Sci73L17Gw+dv5EfvPXal7a0/Se11/d28xr+1r4/BVTSY7TvBERERGRYKeiT0Te45vXzmJmdiL/vGY7TZ297xwf9Fm+V7abgrRYPnr+RAcTioiIiMjZUtEnIu8RE+nm3tsW0N3v5atrtuPzWQAe33qEqoYOvnbNTKIj3O/zLiIiIiISDFT0icgpTZuQyL+tmM1r+1r4zV8P0jswyI+f20NxfjIr5uY4HU9EREREzpL26ROR07ptcQGv7Wvmh+v38FZ9Bw3tvfx09XxcLm3ELiIiIhIqdKdPRE7LGMP3PzSPrMRonthWz5Wzslg6Od3pWCIiIiJyDlT0icgZJcdFcu+HF1BckMLXl89yOo6IiIiInCO1d4rI+1o0MY2/3HWh0zFEREREZBh0p09ERERERCSMqegTEREREREJYyr6REREREREwpiKPhERERERkTCmok9ERERERCSMqegTEREREREJYyr6REREREREwpiKPhERERERkTCmok9ERERERCSMqegTEREREREJYyr6REREREREwpiKPhERERERkTCmok9ERERERCSMGWut0xnewxjTCewJ4JDJQHsAx9OYGjMUxxwPP6PG1JihOOZ4+Bk1psYMtfE0psYcDTOstYmj8k7W2qB7ABUBHu9XDvyMGlNjhtSY4+Fn1JgaMxTHHA8/o8bUmKE2nsbUmKM05qjVRGrvHLJOY2pMjRl042lMjakxg3M8jakxQ3HM8fAzaszwG3PUBGt7Z4W1tsTpHCIiIiIiIk4YzZooWO/0/crpACIiIiIiIg4atZooKO/0iYiIiIiIyOgI1jt9YccYs8wYs8cYs98Y83X/sf9tjDlijNnmf1zrdE4Rpxhj7jfGNBljKk849h/GmN3GmB3GmMeNMSlOZhRx2ml+T4qNMW8YY3YaY9YZY5KczCjiNGNMgTHmJWNMlTFmlzHmiye89nn/57FdxpgfOplTJJB0py8AjDFuYC9wFVAHbAJuA24Buqy1P3IwnkhQMMZcAnQBf7DWzvEfuxrYYK31GmN+AGCtvdvBmCKOOs3vySbgn621rxhjPg4UWWv/l5M5RZxkjMkBcqy1W4wxicBm4EZgAvCvwHXW2j5jTJa1tsnJrCKBojt9gbEY2G+tPWit7QceAm5wOJNIULHWvgp4Tjr2nLXW6//PN4H8gAcTCSKn+j0BZgCv+p8/D9wU0FAiQcZa22Ct3eJ/3glUAXnAZ4HvW2v7/K+p4JNxQ0VfYOQBh0/47zr/MYDP+VvX7jfGpAY+mkjI+DhQ5nQIkSBUCVzvf74KKHAwi0hQMcZMAhYAG4HpwMXGmI3GmFeMMaVOZhMJJBV9gWFOccwCvwSmAPOBBuDHgQwlEiqMMf8KeIE/OZ1FJAh9HLjLGLMZSAT6Hc4jEhSMMQnAWuBL1toOIAJIBZYCXwPWGGNO9RlNJOxEOB1gnKjj3Vde84F6a+3Rtw8YY34NPBXoYCLBzhhzO7AC+IDVJGSR97DW7gauBjDGTAeuczaRiPOMMZEMFXx/stY+5j9cBzzm/7ek3BjjAzKAZodiigSM7vQFxiZgmjGmyBgTBdwKPOmfaPy2DzLUoiMifsaYZcDdwPXW2h6n84gEI2NMlv+rC/gWcJ+ziUSc5b9791ugylr7kxNeegK4wn/OdCAKaAl8QpHA052+APCvPPg54FnADdxvrd1ljPmjMWY+Q62eNcBnHIwp4ihjzIPAZUCGMaYO+HfgG0A08Ly/A+dNa+2djoUUcdhpfk8SjDF3+U95DPidQ/FEgsWFwEeBncaYbf5j3wTuB+73b3nSD9yuDhIZL7Rlg4iIiIiISBhTe6eIiIiIiEgYU9EnIiIiIiISxlT0iYiIiIiIhDEVfSIiIiIiImFMRZ+IiIiIiEgYU9EnIiIiIiISxlT0iYiIiIiIhDEVfSIiIiIiImFMRZ+IiIiIiEgYU9EnIiIiIiISxlT0iYiIiIiIhDEVfSIiIiIiImFMRZ+IiIiIiEgYU9EnIiIiIiISxlT0iYiIiIiIhDEVfSIiIiIiImFMRZ+IiIiIiIgDjDFdgRhHRZ+IiIiIiEgYU9EnIiIiIiLiEGNMgjHmRWPMFmPMTmPMDf7jk4wxVcaYXxtjdhljnjPGxA5rDGvt6KYWERERERGR9+Vv70wB4qy1HcaYDOBNYBowEdgPlFhrtxlj1gBPWmv/51zHiRjN0CIiIiIiInJODPBdY8wlgA/IAyb4X6u21m7zP98MTBrOACr6REREREREnPMRIBNYZK0dMMbUADH+1/pOOG8QGFZ7p+b0iYiIiIiIOCcZaPIXfJcz1NY5qnSnT0REREREJMCMMREM3cn7E7DOGFMBbAN2j/pYWshFREREREQksIwxxcCvrbWLx3ostXeKiIiIiIgEkDHmTuBB4FsBGU93+kRERERERMKX7vSJiIiIiIiMMWNMgTHmJf+G67uMMV/0H08zxjxvjNnn/5rqP36VMWazf8P2zcaYK054r/XGmO3+97nPGOM+49i60yciIiIiIjK2jDE5QI61dosxJpGhffduBP4R8Fhrv2+M+TqQaq292xizADhqra03xswBnrXW5vnfK8m/mbsBHgUesdY+dLqxdadPRERERERkjFlrG6y1W/zPO4EqhjZivwH4vf+03zNUCGKt3Wqtrfcf3wXEGGOi/a91+I9HAFHAGe/kqegTEREREREJIGPMJGABsBGYYK1tgKHCEMg6xbfcBGy11vad8B7PAk1AJ0N3+05LRZ+IiIiIiEiAGGMSgLXAl064Y3em82cDPwA+c+Jxa+01QA4QDVxxim99h4o+ERERERGRADDGRDJU8P3JWvuY//BR/3y/t+f9NZ1wfj7wOPAxa+2Bk9/PWtsLPMlQi+hpqegTEREREREZY/5FV34LVFlrf3LCS08Ct/uf3w78xX9+CvA08A1r7d9OeJ+EE4rECOBaYPcZx9bqnSIiIiIiImPLGHMR8BqwE/D5D3+ToXl9a4BC4BCwylrrMcZ8C/gGsO+Et7kaMMBTDLV1uoENwJettd7Tjq2iT0REREREJHypvVNERERERCSMqegTEREREREJYyr6REREREREwpiKPhERERERkTCmok9ERERERCSMqegTEREREREJYyr6REREREREwpiKPhERERERkTD2/wGBtDpoybn0LwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# use synthetic data\n", + "# synthetic_data = get_syntetic_data(tf=\"D\", start_date=\"2015-01-01\", end_date=\"2015-02-05\", add_noise=None)\n", + "synthetic_data = get_syntetic_data(tf=\"D\", start_date=\"2015-01-01\", end_date=\"2023-01-01\", add_noise=None)\n", + "eth_train = synthetic_data[-1864:-200]\n", + "eth_test = synthetic_data[-200:]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 146, + "metadata": {}, + "outputs": [], + "source": [ + "def initialize_q_table(state_elements):\n", + " return np.array([], dtype=[('state','u4',(state_elements,)), ('action', 'u4'), ('qvalue', 'f4')])" + ] + }, + { + "cell_type": "code", + "execution_count": 147, + "metadata": {}, + "outputs": [], + "source": [ + "def get_q_value(Qtable, state, action):\n", + " mask = (np.all(Qtable['state'] == state, axis=1)) & (np.where(Qtable['action'] == action, True, False)) \n", + "\n", + " if len(mask) > 0 and (np.argmax(mask) > 0 or mask[0] == True):\n", + " qvalue = Qtable['qvalue'][np.argmax(mask)]\n", + " return qvalue\n", + " return None\n", + "\n", + "def set_q_value(Qtable, state, action, qvalue):\n", + " if get_q_value(Qtable, state, action) is not None:\n", + " mask = (np.all(Qtable['state'] == state, axis=1)) & (np.where(Qtable['action'] == action, True, False)) \n", + " Qtable['qvalue'][np.argmax(mask)] = qvalue\n", + " else:\n", + " Qtable = np.append(Qtable, np.array([((state),action, qvalue) ], dtype=[('state','u4',(4,)), ('action', 'u4'), ('qvalue', 'f4')]))\n", + " return Qtable\n", + "\n", + "def get_max_q_value(Qtable, state):\n", + " mask = np.all(Qtable['state'] == state, axis=1)\n", + " if len(mask) > 0 and (np.argmax(mask) > 0 or mask[0] == True):\n", + " return np.max(Qtable['qvalue'][mask])\n", + " return None\n", + "\n", + "def get_max_action(Qtable, state):\n", + " mask = np.all(Qtable['state'] == state, axis=1)\n", + " if len(mask) > 0 and (np.argmax(mask) > 0 or mask[0] == True):\n", + " return np.argmax(Qtable['qvalue'][mask])\n", + " return None" + ] + }, + { + "cell_type": "code", + "execution_count": 148, + "metadata": {}, + "outputs": [], + "source": [ + "# Policy\n", + "\n", + "def greedy_policy(Qtable, state):\n", + " # Exploitation: take the action with the highest state, action value\n", + " # if we dont have a state with values return DO_NOTHING \n", + " action = get_max_action(Qtable, state)\n", + " if action is None:\n", + " action = 2\n", + " # action = np.argmax(Qtable[state])\n", + " return action\n", + "\n", + "\n", + "def epsilon_greedy_policy(Qtable, state, epsilon, env):\n", + " # Randomly generate a number between 0 and 1\n", + " random_num = np.random.uniform(size=1)\n", + " # if random_num > greater than epsilon --> exploitation\n", + " if random_num > epsilon:\n", + " # Take the action with the highest value given a state\n", + " # np.argmax can be useful here\n", + " action = greedy_policy(Qtable, state)\n", + " # else --> exploration\n", + " else:\n", + " # action = np.random.random_integers(4,size=1)[0]\n", + " action = env.action_space.sample()\n", + " \n", + " return action" + ] + }, + { + "cell_type": "code", + "execution_count": 149, + "metadata": { + "id": "wlC-EdLENTiN" + }, + "outputs": [], + "source": [ + "\n", + "def train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable, learning_rate, gamma):\n", + " state_history = []\n", + "# np.random.seed(42)\n", + " for episode in range(n_training_episodes):\n", + " # Reduce epsilon (because we need less and less exploration)\n", + " epsilon = min_epsilon + (max_epsilon - min_epsilon)*np.exp(-decay_rate*episode)\n", + " # Reset the environment\n", + " state = env.reset()\n", + " step = 0\n", + " done = False\n", + "\n", + " # repeat\n", + " for step in range(max_steps):\n", + " # Choose the action At using epsilon greedy policy\n", + " action = epsilon_greedy_policy(Qtable, state, epsilon, env)\n", + "\n", + " # Take action At and observe Rt+1 and St+1\n", + " # Take the action (a) and observe the outcome state(s') and reward (r)\n", + " new_state, reward, done, info = env.step(action)\n", + "\n", + " # Update Q(s,a):= Q(s,a) + lr [R(s,a) + gamma * max Q(s',a') - Q(s,a)]\n", + " # Qtable[state][action] = Qtable[state][action] + learning_rate * (reward + gamma * ( np.max(Qtable[new_state]) ) - Qtable[state][action] )\n", + " qvalue = get_q_value(Qtable, state, action)\n", + " if qvalue is None:\n", + " qvalue = 0\n", + "\n", + " q_max_state = get_max_q_value(Qtable, new_state)\n", + " if q_max_state is None:\n", + " q_max_state = 0\n", + " \n", + " n_qvalue = qvalue + learning_rate * (reward + gamma * ( q_max_state ) - qvalue )\n", + " Qtable = set_q_value(Qtable, state, action, n_qvalue)\n", + "\n", + " # If done, finish the episode\n", + " if done:\n", + " break\n", + " \n", + " # Our next state is the new state\n", + " state = new_state\n", + "\n", + " state_history.append(state) \n", + "\n", + " return Qtable, state_history" + ] + }, + { + "cell_type": "code", + "execution_count": 150, + "metadata": {}, + "outputs": [], + "source": [ + "from enum import Enum\n", + "class Actions(Enum):\n", + " Sell = 0\n", + " Buy = 1\n", + " Do_nothing = 2\n", + "\n", + "class CustTradingEnv(gym.Env):\n", + "\n", + " def __init__(self, df, max_steps=0, random_start=True):\n", + " self.seed()\n", + " self.df = df\n", + " self.prices, self.signal_features = self._process_data()\n", + "\n", + " # spaces\n", + " self.action_space = spaces.Discrete(3)\n", + " self.observation_space = spaces.Box(low=0, high=1999, shape=(1,) , dtype=np.float64)\n", + "\n", + " # episode\n", + " self._start_tick = 0\n", + " self._end_tick = 0\n", + " self._done = None\n", + " self._current_tick = None\n", + " self._last_trade_tick = None\n", + " self._position = None\n", + " self._position_history = None\n", + " self._total_reward = None\n", + " self._total_profit = None\n", + " self._first_rendering = None\n", + " self.history = None\n", + " self._max_steps = max_steps\n", + " self._start_episode_tick = None\n", + " self._trade_history = None\n", + " self._trade_tick_history = None\n", + " self._random_start = random_start\n", + "\n", + " def reset(self):\n", + " self._done = False\n", + " if self._random_start:\n", + " self._start_episode_tick = np.random.randint(1,high=len(self.df)- self._max_steps )\n", + " self._end_tick = self._start_episode_tick + self._max_steps\n", + " else:\n", + " self._start_episode_tick = 1\n", + " self._end_tick = len(self.df)-1\n", + " # self._start_episode_tick = np.random.randint(1,len(self.df)- self._max_steps )\n", + " # self._end_tick = self._start_episode_tick + self._max_steps\n", + " self._current_tick = self._start_episode_tick\n", + " self._last_trade_tick = self._current_tick - 1\n", + " self._position = 0\n", + " self._position_history = [0] * (len(self.prices)) \n", + " # self._position_history = (self.window_size * [None]) + [self._position]\n", + " self._total_reward = 0.\n", + " self._total_profit = 0.\n", + " self._trade_history = []\n", + " self._trade_tick_history = []\n", + " self.history = {}\n", + " return self._get_observation()\n", + "\n", + "\n", + " def step(self, action):\n", + " self._done = False\n", + " self._current_tick += 1\n", + "\n", + " if self._current_tick == self._end_tick:\n", + " self._done = True\n", + "\n", + " step_reward = self._calculate_reward(action)\n", + " self._total_reward += step_reward\n", + "\n", + " observation = self._get_observation()\n", + " info = dict(\n", + " total_reward = self._total_reward,\n", + " total_profit = self._total_profit,\n", + " position = self._position,\n", + " action = action\n", + " )\n", + " self._update_history(info)\n", + "\n", + " return observation, step_reward, self._done, info\n", + "\n", + " def seed(self, seed=None):\n", + " self.np_random, seed = seeding.np_random(seed)\n", + " return [seed]\n", + " \n", + " def _get_observation(self):\n", + " return self.signal_features[self._current_tick]\n", + "\n", + " def _update_history(self, info):\n", + " if not self.history:\n", + " self.history = {key: [] for key in info.keys()}\n", + "\n", + " for key, value in info.items():\n", + " self.history[key].append(value)\n", + "\n", + "\n", + " def render(self, mode='human'):\n", + " window_ticks = np.arange(len(self.prices))\n", + " prices = self.prices\n", + " # prices = self.prices[self._start_episode_tick:self._end_tick+1]\n", + " plt.plot(prices)\n", + "\n", + " open_buy = []\n", + " close_buy = []\n", + " open_sell = []\n", + " close_sell = []\n", + " do_nothing = []\n", + "\n", + " for i, tick in enumerate(window_ticks):\n", + " if self._position_history[i] == 1:\n", + " open_buy.append(tick)\n", + " elif self._position_history[i] == 2 :\n", + " close_buy.append(tick)\n", + " elif self._position_history[i] == 3 :\n", + " open_sell.append(tick)\n", + " elif self._position_history[i] == 4 :\n", + " close_sell.append(tick)\n", + " elif self._position_history[i] == 0 :\n", + " do_nothing.append(tick)\n", + "\n", + " plt.plot(open_buy, prices[open_buy], 'go', marker=\"^\")\n", + " plt.plot(close_buy, prices[close_buy], 'go', marker=\"v\")\n", + " plt.plot(open_sell, prices[open_sell], 'ro', marker=\"v\")\n", + " plt.plot(close_sell, prices[close_sell], 'ro', marker=\"^\")\n", + " \n", + " plt.plot(do_nothing, prices[do_nothing], 'yo')\n", + "\n", + " plt.suptitle(\n", + " \"Total Reward: %.6f\" % self._total_reward + ' ~ ' +\n", + " \"Total Profit: %.6f\" % self._total_profit\n", + " )\n", + "\n", + " # the action is taken when the market is off after the session ends and we receive the close price of the day\n", + " # this will be the current price \n", + " def _calculate_reward(self, action):\n", + " step_reward = 0\n", + "\n", + " current_price = self.prices[self._current_tick]\n", + " last_price = self.prices[self._current_tick - 1]\n", + " price_diff = current_price - last_price\n", + "\n", + " penalty = -1 * last_price * 0.01\n", + " # OPEN BUY - 1\n", + " if action == Actions.Buy.value and self._position == 0:\n", + " self._position = 1\n", + " step_reward += price_diff\n", + " self._last_trade_tick = self._current_tick - 1\n", + " self._position_history[self._current_tick-1]=1\n", + "\n", + " # CLOSE BUY - 2\n", + " elif action == Actions.Sell.value and self._position > 0:\n", + " self._position = 0\n", + " step_reward += self.prices[self._current_tick-1] - self.prices[self._last_trade_tick] \n", + " self._total_profit += step_reward\n", + " self._position_history[self._current_tick-1]=2\n", + " self._trade_history.append(step_reward)\n", + " self._trade_tick_history.append((self._last_trade_tick, self._current_tick-1, self.prices[self._last_trade_tick], self.prices[self._current_tick-1], step_reward))\n", + "\n", + " elif action == Actions.Buy.value and self._position > 0:\n", + " step_reward += penalty\n", + " self._position_history[self._current_tick-1]=-1\n", + " # CLOSE SELL - 4\n", + " elif action == Actions.Buy.value and self._position < 0:\n", + " self._position = 0\n", + " step_reward += -1 * (self.prices[self._current_tick-1] - self.prices[self._last_trade_tick]) \n", + " self._total_profit += step_reward\n", + " self._position_history[self._current_tick-1]=4\n", + " self._trade_history.append(step_reward)\n", + " self._trade_tick_history.append((self._last_trade_tick, self._current_tick-1, self.prices[self._last_trade_tick], self.prices[self._current_tick-1], step_reward))\n", + "\n", + " # OPEN SELL - 3\n", + " elif action == Actions.Sell.value and self._position == 0:\n", + " self._position = -1\n", + " step_reward += -1 * price_diff\n", + " self._last_trade_tick = self._current_tick - 1\n", + " self._position_history[self._current_tick-1]=3\n", + "\n", + " elif action == Actions.Sell.value and self._position < 0:\n", + " step_reward += penalty\n", + " self._position_history[self._current_tick-1]=-1\n", + "\n", + " # DO NOTHING - 0\n", + " elif action == Actions.Do_nothing.value and self._position > 0:\n", + " step_reward += price_diff\n", + " elif action == Actions.Do_nothing.value and self._position < 0:\n", + " step_reward += -1 * price_diff\n", + " elif action == Actions.Do_nothing.value and self._position == 0:\n", + " step_reward += -1 * abs(price_diff)\n", + "\n", + " return step_reward\n", + "\n", + " def _do_bin(self,df):\n", + " df = pd.cut(df,bins=[0,10,20,30,40,50,60,70,80,90,100],labels=False, include_lowest=True)\n", + " return df\n", + " # Our state will be encode with 4 features MFI and Stochastic(only D line), ADX and DI+DI-\n", + " # the values of each feature will be binned in 10 bins, ex:\n", + " # MFI goes from 0-100, if we get 25 will put on the second bin \n", + " # DI+DI- if DI+ is over DI- set (1 otherwise 0) \n", + " # \n", + " # that will give a state space of 10(MFI) * 10(STOCH) * 10(ADX) * 2(DI) = 2000 states\n", + " # encoded as bins of DI MFI STOCH ADX = 1 45.2 25.4 90.1 , binned = 1 4 2 9 state = 1429 \n", + " def _process_data(self):\n", + " timeperiod = 14\n", + " self.df = self.df.copy()\n", + " \n", + " self.df['adx_r'] = ta.ADX(self.df['High'], self.df['Low'], self.df['Close'], timeperiod=timeperiod)\n", + " self.df['mfi_r'] = ta.MFI(self.df['High'], self.df['Low'], self.df['Close'],self.df['Volume'], timeperiod=timeperiod)\n", + " _, self.df['stock_d_r'] = ta.STOCH(self.df['High'], self.df['Low'], self.df['Close'], fastk_period=5, slowk_period=3, slowk_matype=0, slowd_period=3, slowd_matype=0)\n", + " self.df['p_di'] = ta.PLUS_DI(self.df['High'], self.df['Low'], self.df['Close'], timeperiod=timeperiod)\n", + " self.df['m_di'] = ta.MINUS_DI(self.df['High'], self.df['Low'], self.df['Close'], timeperiod=timeperiod)\n", + " self.df['di'] = np.where( self.df['p_di'] > self.df['m_di'], 1, 0)\n", + " self.df = self.df.dropna()\n", + " self.df['mfi'] = self._do_bin(self.df['mfi_r'])\n", + " self.df['stock_d'] = self._do_bin(self.df['stock_d_r'])\n", + " self.df['adx'] = self._do_bin(self.df['adx_r'])\n", + "\n", + " # self.df['state'] = self.df['di']*1000+ self.df['mfi']*100 + self.df['stock_d']*10 + self.df['adx']\n", + "\n", + " prices = self.df.loc[:, 'Close'].to_numpy()\n", + " # signal_features = self.df.loc[:, 'state'].to_numpy()\n", + " signal_features = self.df.loc[:, ['di', 'mfi', 'stock_d','adx']].to_numpy()\n", + "\n", + " return prices, signal_features" + ] + }, + { + "cell_type": "code", + "execution_count": 151, + "metadata": {}, + "outputs": [], + "source": [ + "# Training parameters\n", + "n_training_episodes = 20000 # Total training episodes\n", + "learning_rate = 0.2 # Learning rate\n", + "\n", + "# Environment parameters\n", + "max_steps = 20 # Max steps per episode\n", + "gamma = 0.95 # Discounting rate\n", + "\n", + "# Exploration parameters\n", + "max_epsilon = 1.0 # Exploration probability at start\n", + "# max_epsilon = 1.0 # Exploration probability at start\n", + "min_epsilon = 0.05 # Minimum exploration probability \n", + "# min_epsilon = 0.05 # Minimum exploration probability \n", + "decay_rate = 0.0005 # Exponential decay rate for exploration prob" + ] + }, + { + "cell_type": "code", + "execution_count": 152, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "REhmfLkYNTiN", + "outputId": "cf676f6d-83df-43f5-89fe-3258e0041d9d" + }, + "outputs": [], + "source": [ + "# create env\n", + "env = CustTradingEnv(df=eth_train, max_steps=max_steps)" + ] + }, + { + "cell_type": "code", + "execution_count": 162, + "metadata": {}, + "outputs": [], + "source": [ + "# create q-table\n", + "\n", + "# action_space = env.action_space.n # buy sell do_nothing\n", + "state_elements = 4\n", + "\n", + "Qtable_trading = initialize_q_table(state_elements)" + ] + }, + { + "cell_type": "code", + "execution_count": 168, + "metadata": {}, + "outputs": [ + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31mFailed to interrupt the Kernel. \n", + "debug session not found. \n", + "View Jupyter log for further details." + ] + } + ], + "source": [ + "# train with ETH\n", + "Qtable_trading, state_history = train(n_training_episodes, min_epsilon, max_epsilon, \n", + " decay_rate, env, max_steps, Qtable_trading, learning_rate, gamma )\n", + "len(Qtable_trading )\n", + "\n", + "# #train with BTC\n", + "# env = CustTradingEnv(df=btc_train, max_steps=max_steps)\n", + "# Qtable_trading, state_history = train(n_training_episodes, min_epsilon, max_epsilon, \n", + "# decay_rate, env, max_steps, Qtable_trading, learning_rate, gamma )\n", + "# len(np.where( Qtable_trading > 0 )[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 164, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([([1, 5, 4, 1], 0, 6.3886476), ([1, 5, 6, 1], 2, 5.7134356),\n", + " ([1, 4, 7, 1], 2, 5.263815 ), ([0, 4, 6, 1], 2, 6.4853797),\n", + " ([0, 4, 5, 0], 0, 5.94876 ), ([0, 5, 4, 0], 2, 6.310561 ),\n", + " ([0, 5, 3, 0], 0, 5.8414774), ([0, 5, 3, 0], 1, 5.5268397),\n", + " ([0, 4, 4, 0], 0, 6.5970645), ([0, 4, 3, 1], 0, 5.570093 ),\n", + " ([0, 5, 3, 0], 2, 5.4820514), ([1, 5, 5, 1], 1, 5.6464224),\n", + " ([1, 4, 6, 1], 1, 5.9535584), ([1, 3, 6, 1], 2, 5.133297 ),\n", + " ([0, 3, 6, 1], 2, 6.110096 ), ([0, 3, 5, 0], 0, 5.4430504),\n", + " ([1, 4, 4, 0], 1, 6.5041904), ([1, 4, 3, 0], 0, 6.913344 ),\n", + " ([1, 5, 3, 0], 2, 6.4121075), ([1, 4, 3, 0], 1, 5.9901047),\n", + " ([0, 4, 4, 0], 2, 7.078845 ), ([0, 4, 3, 0], 1, 6.8532906),\n", + " ([0, 5, 3, 1], 1, 6.590377 ), ([1, 5, 4, 0], 2, 6.6638174),\n", + " ([1, 5, 5, 1], 0, 6.888194 ), ([1, 5, 6, 1], 0, 6.164531 ),\n", + " ([1, 4, 6, 1], 0, 6.347888 ), ([0, 4, 6, 0], 2, 6.5830064),\n", + " ([0, 4, 4, 0], 1, 7.6808543), ([1, 5, 4, 0], 0, 6.7460346),\n", + " ([1, 5, 3, 0], 0, 6.583798 ), ([0, 4, 3, 0], 2, 6.012948 ),\n", + " ([0, 4, 3, 1], 1, 7.226693 ), ([1, 5, 5, 1], 2, 5.595151 ),\n", + " ([1, 5, 4, 1], 2, 6.446043 ), ([1, 3, 6, 1], 1, 5.654243 ),\n", + " ([0, 3, 5, 0], 1, 6.6004143), ([1, 4, 3, 0], 2, 5.522101 ),\n", + " ([0, 3, 3, 0], 2, 6.037884 ), ([0, 3, 4, 0], 2, 5.9696383),\n", + " ([1, 4, 7, 1], 0, 6.0073075), ([0, 4, 6, 1], 1, 6.220406 ),\n", + " ([0, 4, 6, 0], 1, 6.3034377), ([0, 5, 4, 0], 1, 6.212141 ),\n", + " ([0, 4, 5, 0], 2, 6.1146936), ([1, 5, 4, 0], 1, 6.2903223),\n", + " ([0, 4, 3, 0], 0, 6.016704 ), ([0, 4, 3, 1], 2, 5.3646536),\n", + " ([0, 5, 3, 1], 2, 5.0738063), ([1, 4, 7, 1], 1, 6.0157747),\n", + " ([0, 4, 5, 0], 1, 6.7929273), ([0, 5, 4, 0], 0, 5.7795157),\n", + " ([1, 5, 4, 1], 1, 5.5192404), ([1, 5, 6, 1], 1, 5.9067903),\n", + " ([1, 4, 6, 1], 2, 5.4087076), ([0, 4, 6, 1], 0, 5.17841 ),\n", + " ([0, 5, 3, 1], 0, 5.611172 ), ([0, 4, 6, 0], 0, 4.9781823),\n", + " ([1, 5, 7, 1], 2, 5.59492 ), ([0, 5, 6, 1], 2, 6.593539 ),\n", + " ([0, 5, 5, 0], 2, 7.0204678), ([1, 5, 3, 0], 1, 5.790296 ),\n", + " ([1, 3, 6, 1], 0, 5.7677345), ([0, 3, 5, 0], 2, 5.6948934),\n", + " ([0, 3, 3, 0], 1, 6.1886024), ([0, 3, 4, 0], 1, 5.877254 ),\n", + " ([1, 4, 4, 0], 2, 6.4953384), ([0, 5, 6, 1], 0, 6.8340735),\n", + " ([0, 5, 5, 0], 1, 6.5648694), ([0, 3, 3, 0], 0, 6.706435 ),\n", + " ([1, 4, 4, 0], 0, 6.5895467), ([0, 3, 6, 1], 1, 5.6762486),\n", + " ([0, 3, 6, 1], 0, 4.500813 ), ([0, 3, 3, 1], 2, 6.779972 ),\n", + " ([0, 3, 5, 1], 2, 6.239167 ), ([1, 4, 4, 1], 2, 6.7267 ),\n", + " ([0, 3, 3, 1], 1, 6.7674727), ([1, 5, 3, 1], 2, 7.6566343),\n", + " ([1, 5, 3, 1], 0, 7.8168364), ([0, 5, 5, 1], 2, 7.094757 ),\n", + " ([0, 5, 6, 1], 1, 6.5727158), ([0, 3, 4, 0], 0, 6.625018 ),\n", + " ([1, 5, 7, 1], 0, 6.717288 ), ([0, 3, 3, 1], 0, 6.2723055),\n", + " ([0, 5, 5, 0], 0, 4.5931926), ([1, 5, 7, 1], 1, 6.2858872),\n", + " ([0, 3, 5, 1], 0, 5.904992 ), ([1, 4, 4, 1], 0, 6.8397455),\n", + " ([1, 4, 4, 1], 1, 5.7449455), ([1, 5, 3, 1], 1, 7.3127966),\n", + " ([0, 5, 5, 1], 0, 5.038298 ), ([0, 4, 4, 1], 2, 6.454152 ),\n", + " ([0, 4, 4, 1], 0, 6.5479946), ([0, 5, 4, 1], 1, 4.2780933),\n", + " ([0, 4, 4, 1], 1, 6.582608 ), ([0, 3, 5, 1], 1, 6.1100645),\n", + " ([0, 5, 5, 1], 1, 7.002036 ), ([0, 5, 4, 1], 0, 4.55483 )],\n", + " dtype=[('state', ' 0:\n", + " episode_positive_perc_trades.append(np.count_nonzero(np.array(env._trade_history) > 0)/len(env._trade_history))\n", + " episode_rewards.append(total_rewards_ep)\n", + " episode_profits.append(env.history['total_profit'][-1])\n", + " # print(env.history)\n", + " # env.render()\n", + " # assert 0\n", + "\n", + " mean_reward = np.mean(episode_rewards)\n", + " std_reward = np.std(episode_rewards)\n", + " mean_profit = np.mean(episode_profits)\n", + " std_profit = np.std(episode_profits)\n", + " positive_perc_trades = np.mean(episode_positive_perc_trades)\n", + "\n", + " return mean_reward, std_reward, mean_profit, std_profit, positive_perc_trades" + ] + }, + { + "cell_type": "code", + "execution_count": 165, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "baf1e7f0d9d44a6fa13da6ca540ef03e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/1000 [00:00" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(15,6))\n", + "plt.cla()\n", + "env_test.render()" + ] + }, + { + "cell_type": "code", + "execution_count": 167, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7f776014f0b24b018108b6c77ee42e88", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/1 [00:00" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(15,6))\n", + "plt.cla()\n", + "env_test.render()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(1, 4, 8.645273113640826, 11.755207560128675, 3.1099344464878484),\n", + " (5, 7, 11.171935380608351, 9.6441605861055, 1.527774794502852),\n", + " (9, 13, 10.295810456549015, 8.282581743389205, 2.01322871315981),\n", + " (14, 16, 9.137837754794978, 11.628818062612993, 2.490980307818015),\n", + " (17, 19, 11.608355073821109, 9.857818739078045, 1.7505363347430638),\n", + " (21, 26, 10.057416515635682, 8.528742398078922, 1.5286741175567595),\n", + " (27, 32, 9.74902775472201, 9.670699802197579, 0.0783279525244307),\n", + " (33, 35, 9.78769038301581, 10.300434254468536, 0.5127438714527255),\n", + " (36, 38, 9.60217789374197, 8.250852689557544, 1.3513252041844268),\n", + " (39, 40, 8.971004003020443, 10.395078818094584, 1.4240748150741407),\n", + " (42, 44, 11.673729766919589, 9.944263607173118, 1.7294661597464707),\n", + " (45, 46, 9.636292392244973, 9.991269928983368, 0.35497753673839405),\n", + " (47, 51, 10.360163262862152, 8.430825035963325, 1.9293382268988264),\n", + " (52, 54, 9.554103757397273, 11.742814826136176, 2.188711068738902),\n", + " (55, 57, 11.403617185189336, 9.71178561119097, 1.6918315739983658),\n", + " (58, 59, 9.736562354250092, 10.197672768283018, 0.46111041403292674),\n", + " (60, 64, 10.337689357211167, 8.817539963222865, 1.5201493939883015),\n", + " (65, 69, 10.199032988393924, 10.042850712802524, 0.15618227559139974),\n", + " (70, 71, 9.63107312063706, 9.925392109060335, 0.2943189884232744),\n", + " (72, 76, 10.338918119518627, 8.35252995759046, 1.9863881619281667),\n", + " (77, 83, 9.365049578445921, 9.693865537165568, -0.3288159587196464),\n", + " (84, 89, 10.138407662002876, 8.67933961768415, 1.4590680443187267),\n", + " (90, 95, 10.000361732237739, 9.63951969965352, 0.3608420325842179),\n", + " (96, 97, 9.861816695922618, 10.30627224611643, 0.4444555501938119),\n", + " (98, 107, 10.232441825356547, 9.836950696890277, 0.3954911284662703),\n", + " (108, 109, 9.661180634141186, 10.074844279839029, 0.4136636456978433),\n", + " (110, 114, 10.368921355130126, 8.558052792767318, 1.810868562362808),\n", + " (115, 120, 9.801685702157227, 9.662201837344819, 0.1394838648124086),\n", + " (121, 123, 9.80253246200774, 10.288038598865318, 0.4855061368575786),\n", + " (124, 127, 9.567447370545475, 9.01427913814844, 0.5531682323970344),\n", + " (128, 132, 10.44659531472739, 9.919971609023063, 0.5266237057043259),\n", + " (133, 134, 9.639891825308629, 10.008971159712608, 0.36907933440397933),\n", + " (135, 139, 10.363750373990332, 8.455059573520186, 1.9086908004701453),\n", + " (140, 145, 9.605625627674012, 9.699404019036715, -0.0937783913627026),\n", + " (146, 148, 9.749444489037334, 10.32917728184808, 0.5797327928107467),\n", + " (149, 152, 9.695479843750826, 8.857054399227408, 0.838425444523418),\n", + " (153, 157, 10.25168805575754, 10.015435969355776, 0.23625208640176432),\n", + " (158, 159, 9.63115885189913, 9.942821850118737, 0.31166299821960664),\n", + " (160, 164, 10.345751755784269, 8.371449341610663, 1.9743024141736054),\n", + " (165, 170, 9.414760057555497, 9.751117491956888, -0.3363574344013909)]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "env_test._trade_tick_history" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-1.762380278209026,\n", + " 4.858275486625676,\n", + " 0.0521930932010648,\n", + " 2.6794070513026282,\n", + " 0.5064714285714286)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Test for random n_eval_episodes\n", + "max_steps = 20 \n", + "env_test_rand = CustTradingEnv(df=eth_test, max_steps=max_steps, random_start=True)\n", + "n_eval_episodes = 1000\n", + "\n", + "evaluate_agent(env_test_rand, max_steps, n_eval_episodes, Qtable_trading, random=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean profit -0.20975956329301532\n" + ] + } + ], + "source": [ + "# trade sequentially with random actions \n", + "max_steps = len(eth_test)\n", + "env_test = CustTradingEnv(df=eth_test, max_steps=max_steps, random_start=False)\n", + "n_eval_episodes = 1\n", + "\n", + "all_profit=[]\n", + "for i in range(1000):\n", + " _,_,profit,_,_=evaluate_agent(env_test, max_steps, n_eval_episodes, Qtable_trading, random=True)\n", + " all_profit.append(profit)\n", + "print(f\"Mean profit {np.mean(all_profit)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## This is the result\n", + "\n", + "| Model | 1000 trades 20 steps | Sequential trading | 1000 trades 20 steps random actions | Sequential random|\n", + "|------------|----------------------|--------------------|-------------------------------------|------------------|\n", + "|Q-learning | 113.14 | 563.67 | -18.10 | 39.30 |\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def count_equal(env, Qtable):\n", + " count=0\n", + " for i in env.signal_features:\n", + " if abs(np.max(Qtable[i])) > 0:\n", + " count+=1\n", + " # else:\n", + " # print(i)\n", + " # assert 0\n", + " \n", + " print(len(env.signal_features), count, count / len(env.signal_features))\n", + "\n", + "count_equal(env_test, Qtable_trading)" + ] + }, + { + "cell_type": "code", + "execution_count": 150, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([([0, 0, 0], 0), ([0, 0, 0], 0), ([0, 0, 0], 0)],\n", + " dtype=[('state', '
Copy a token from your Hugging Face\ntokens page and paste it below.
Immediately click login after copying\nyour token or it might be stored in plain text in this notebook file. " + } + }, + "a2cfb91cf66447d7899292854bd64a07": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c1a82965ae26479a98e4fdbde1e64ec2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "HTMLView", + "description": "", + "description_tooltip": null, + "layout": "IPY_MODEL_9d847f9a7d47458d8cd57d9b599e47c6", + "placeholder": "​", + "style": "IPY_MODEL_42d140b838b844819bc127afc1b7bc84", + "value": "\nPro Tip: If you don't already have one, you can create a dedicated\n'notebooks' token with 'write' access, that you can then easily reuse for all\nnotebooks. " + } + }, + "caef095934ec47bbb8b64eab22049284": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "eaba3f1de4444aabadfea2a3dadb1d80": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "ee4a21bedc504171ad09d205d634b528": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ButtonStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "button_color": null, + "font_weight": "" + } + }, + "f1675c09d16a4251b403f9c56255f168": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "ButtonModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ButtonModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ButtonView", + "button_style": "", + "description": "Login", + "disabled": false, + "icon": "", + "layout": "IPY_MODEL_a2cfb91cf66447d7899292854bd64a07", + "style": "IPY_MODEL_ee4a21bedc504171ad09d205d634b528", + "tooltip": "" + } + }, + "f6c845330d6743c0b35c2c7ad834de77": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "CheckboxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "CheckboxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "CheckboxView", + "description": "Add token as git credential?", + "description_tooltip": null, + "disabled": false, + "indent": true, + "layout": "IPY_MODEL_3e753b0212644990b558c68853ff2041", + "style": "IPY_MODEL_eaba3f1de4444aabadfea2a3dadb1d80", + "value": true + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +}