{"action":"created","comment":{"url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/comments/319624443","pull_request_review_id":282177671,"id":319624443,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDMxOTYyNDQ0Mw==","diff_hunk":"@@ -0,0 +1,538 @@\n+'''\n+CLA class implements the famous Critical Line Algorithm for mean-variance portfolio\n+optimisation. It is reproduced with modification from the following paper:\n+D.H. Bailey and M.L. Prado “An Open-Source Implementation of the Critical- Line Algorithm for\n+Portfolio Optimization”,Algorithms, 6 (2013), 169-196. http://dx.doi.org/10.3390/a6010169\n+'''\n+\n+import numbers\n+import numpy as np\n+import pandas as pd\n+from math import log, ceil\n+\n+\n+def _infnone(x):\n+    return float(\"-inf\") if x is None else x\n+\n+\n+class CLA:\n+    def __init__(self, weight_bounds=(0, 1), calculate_returns=\"mean\"):\n+        '''\n+        Initialise the storage arrays and some preprocessing.\n+\n+        :param weight_bounds: (tuple) a tuple specifying the lower and upper bound ranges for the portfolio weights\n+        :param calculate_returns: (str) the method to use for calculation of expected returns.\n+                                        Currently supports \"mean\" and \"exponential\"\n+        '''\n+\n+        self.weight_bounds = weight_bounds\n+        self.calculate_returns = calculate_returns\n+\n+    def _calculate_mean_historical_returns(self, X, frequency=252):\n+        '''\n+        Calculate the annualised mean historical returns from asset price data\n+\n+        :param X: (pd.DataFrame) asset price data\n+        :return: (np.array) returns per asset\n+        '''\n+\n+        returns = X.pct_change().dropna(how=\"all\")\n+        returns = returns.mean() * frequency\n+        return returns\n+\n+    def _calculate_exponential_historical_returns(self, X, frequency=252, span=500):\n+        '''\n+        Calculate the exponentially-weighted mean of (daily) historical returns, giving\n+        higher weight to more recent data.\n+\n+        :param X: (pd.DataFrame) asset price data\n+        :return: (np.array) returns per asset\n+        '''\n+\n+        returns = X.pct_change().dropna(how=\"all\")\n+        returns = returns.ewm(span=span).mean().iloc[-1] * frequency\n+        return returns\n+\n+    def _init_algo(self):\n+        '''\n+        Initial setting up of the algorithm. Calculates the first free weight of the first turning point.\n+\n+        :return: (list, list) asset index and the corresponding free weight value\n+        '''\n+\n+        # Form structured array\n+        structured_array = np.zeros((self.expected_returns.shape[0]), dtype=[(\"id\", int), (\"mu\", float)])\n+        expected_returns = [self.expected_returns[i][0] for i in range(self.expected_returns.shape[0])]  # dump array into list\n+\n+        # Fill structured array\n+        structured_array[:] = list(zip(list(range(self.expected_returns.shape[0])), expected_returns))\n+\n+        # Sort structured array based on increasing return value\n+        expected_returns = np.sort(structured_array, order=\"mu\")\n+\n+        # First free weight\n+        i, w = expected_returns.shape[0], np.copy(self.lower_bounds)\n+        while np.sum(w) < 1:\n+            i -= 1\n+\n+            # Set weights one by one to the upper bounds\n+            w[expected_returns[i][0]] = self.upper_bounds[expected_returns[i][0]]\n+        w[expected_returns[i][0]] += 1 - np.sum(w)\n+        return [expected_returns[i][0]], w\n+\n+    def _compute_bi(self, c, asset_bounds_i):\n+        '''\n+        Calculates which bound value to assign to a bounded asset - lower bound or upper bound.\n+\n+        :param c: (float) a value calculated using the covariance matrices of free weights.\n+                          Refer to https://pdfs.semanticscholar.org/4fb1/2c1129ba5389bafe47b03e595d098d0252b9.pdf for\n+                          more information.\n+        :param asset_bounds_i: (list) a list containing the lower and upper bound values for the ith weight\n+        :return: bounded weight value\n+        '''\n+\n+        bounded_asset_i = 0\n+        if c > 0:\n+            bounded_asset_i = asset_bounds_i[1][0]\n+        if c < 0:\n+            bounded_asset_i = asset_bounds_i[0][0]\n+        return bounded_asset_i\n+\n+    def _compute_w(self, covarF_inv, covar_FB, mean_F, w_B):\n+        '''\n+        Compute the turning point associated with the current set of free weights F\n+\n+        :param covarF_inv: (np.array) inverse of covariance matrix of free assets\n+        :param covar_FB: (np.array) covariance matrix between free assets and bounded assets\n+        :param mean_F: (np.array) expected returns of free assets\n+        :param w_B: (np.array) bounded asset weight values\n+\n+        :return: (array, array) list of turning point weights and gamma value from the langrange equation\n+        '''\n+\n+        # Compute gamma\n+        ones_F = np.ones(mean_F.shape)\n+        g_1 = np.dot(np.dot(ones_F.T, covarF_inv), mean_F)\n+        g_2 = np.dot(np.dot(ones_F.T, covarF_inv), ones_F)\n+        if w_B is None:\n+            g, w_1 = float(-self.lambdas[-1] * g_1 / g_2 + 1 / g_2), 0\n+        else:\n+            onesB = np.ones(w_B.shape)\n+            g_3 = np.dot(onesB.T, w_B)\n+            g_4 = np.dot(covarF_inv, covar_FB)\n+            w_1 = np.dot(g_4, w_B)\n+            g_4 = np.dot(ones_F.T, w_1)\n+            g = float(-self.lambdas[-1] * g_1 / g_2 + (1 - g_3 + g_4) / g_2)\n+\n+        # Compute weights\n+        w_2 = np.dot(covarF_inv, ones_F)\n+        w_3 = np.dot(covarF_inv, mean_F)\n+        return -w_1 + g * w_2 + self.lambdas[-1] * w_3, g\n+\n+    def _compute_lambda(self, covarF_inv, covar_FB, mean_F, w_B, i, bi):\n+        '''\n+        Calculate the lambda value in the langrange optimsation equation\n+\n+        :param covarF_inv: (np.array) inverse of covariance matrix of free assets\n+        :param covar_FB: (np.array) covariance matrix between free assets and bounded assets\n+        :param mean_F: (np.array) expected returns of free assets\n+        :param w_B: (np.array) bounded asset weight values\n+        :param i: (int) asset index\n+        :param bi: (list) list of upper and lower bounded weight values\n+        :return: (float) lambda value\n+        '''\n+\n+        # Compute C\n+        ones_F = np.ones(mean_F.shape)\n+        c1 = np.dot(np.dot(ones_F.T, covarF_inv), ones_F)\n+        c2 = np.dot(covarF_inv, mean_F)\n+        c3 = np.dot(np.dot(ones_F.T, covarF_inv), mean_F)\n+        c4 = np.dot(covarF_inv, ones_F)\n+        c = -c1 * c2[i] + c3 * c4[i]\n+        if c == 0:\n+            return None, None\n+\n+        # Compute bi\n+        if type(bi) == list:\n+            bi = self._compute_bi(c, bi)\n+\n+        # Compute Lambda\n+        if w_B is None:\n+\n+            # All free assets\n+            return float((c4[i] - c1 * bi) / c), bi\n+        else:\n+            onesB = np.ones(w_B.shape)\n+            l1 = np.dot(onesB.T, w_B)\n+            l2 = np.dot(covarF_inv, covar_FB)\n+            l3 = np.dot(l2, w_B)\n+            l2 = np.dot(ones_F.T, l3)\n+            return float(((1 - l1 + l2) * c4[i] - c1 * (bi + l3[i])) / c), bi\n+\n+    def _get_matrices(self, f):\n+        '''\n+        Calculate the required matrices between free and bounded assets\n+\n+        :param f: (list) list of free assets/weights\n+        :return: (tuple of np.array matrices) the corresponding matrices\n+        '''\n+\n+        covarF = self._reduce_matrix(self.cov_matrix, f, f)\n+        mean_F = self._reduce_matrix(self.expected_returns, f, [0])\n+        b = self._get_b(f)\n+        covar_FB = self._reduce_matrix(self.cov_matrix, f, b)\n+        w_B = self._reduce_matrix(self.weights[-1], b, [0])\n+        return covarF, covar_FB, mean_F, w_B\n+\n+    def _get_b(self, f):\n+        '''\n+        Compute the list of bounded assets\n+\n+        :param f: (np.array) list of free weights/assets\n+        :return: (np.array) list of bounded assets/weights\n+        '''\n+\n+        return self._diff_lists(list(range(self.expected_returns.shape[0])), f)\n+\n+    @staticmethod\n+    def _diff_lists(list1, list2):\n+        '''\n+        Calculate the set difference between two lists\n+        '''\n+\n+        return list(set(list1) - set(list2))\n+\n+    @staticmethod\n+    def _reduce_matrix(matrix, listX, listY):\n+        '''\n+        Reduce a matrix to the provided set of rows and columns\n+        '''\n+\n+        return matrix[np.ix_(listX, listY)]\n+\n+    def _purge_num_err(self, tol):\n+        '''\n+        Purge violations of inequality constraints (associated with ill-conditioned cov matrix)\n+        '''\n+\n+        i = 0\n+        while True:\n+            flag = False\n+            if i == len(self.weights):\n+                break\n+            if abs(sum(self.weights[i]) - 1) > tol:\n+                flag = True\n+            else:\n+                for j in range(self.weights[i].shape[0]):\n+                    if (\n+                            self.weights[i][j] - self.lower_bounds[j] < -tol\n+                            or self.weights[i][j] - self.upper_bounds[j] > tol\n+                    ):\n+                        flag = True\n+                        break\n+            if flag is True:\n+                del self.weights[i]\n+                del self.lambdas[i]\n+                del self.gammas[i]\n+                del self.free_weights[i]\n+            else:\n+                i += 1\n+\n+    def _purge_excess(self):\n+        '''\n+        Remove violations of the convex hull\n+        '''\n+\n+        i, repeat = 0, False\n+        while True:\n+            if repeat is False:\n+                i += 1\n+            if i == len(self.weights) - 1:\n+                break\n+            w = self.weights[i]\n+            mu = np.dot(w.T, self.expected_returns)[0, 0]\n+            j, repeat = i + 1, False\n+            while True:\n+                if j == len(self.weights):\n+                    break\n+                w = self.weights[j]\n+                mu_ = np.dot(w.T, self.expected_returns)[0, 0]\n+                if mu < mu_:\n+                    del self.weights[i]\n+                    del self.lambdas[i]\n+                    del self.gammas[i]\n+                    del self.free_weights[i]\n+                    repeat = True\n+                    break\n+                else:\n+                    j += 1\n+\n+    def _golden_section(self, obj, a, b, **kargs):\n+        '''\n+        Golden section method. Maximum if kargs['minimum']==False is passed\n+        '''\n+\n+        tol, sign, args = 1.0e-9, 1, None\n+        if \"minimum\" in kargs and kargs[\"minimum\"] is False:\n+            sign = -1\n+        if \"args\" in kargs:\n+            args = kargs[\"args\"]\n+        numIter = int(ceil(-2.078087 * log(tol / abs(b - a))))\n+        r = 0.618033989\n+        c = 1.0 - r\n+\n+        # Initialize\n+        x1 = r * a + c * b\n+        x2 = c * a + r * b\n+        f1 = sign * obj(x1, *args)\n+        f2 = sign * obj(x2, *args)\n+\n+        # Loop\n+        for i in range(numIter):\n+            if f1 > f2:\n+                a = x1\n+                x1 = x2\n+                f1 = f2\n+                x2 = c * a + r * b\n+                f2 = sign * obj(x2, *args)\n+            else:\n+                b = x2\n+                x2 = x1\n+                f2 = f1\n+                x1 = r * a + c * b\n+                f1 = sign * obj(x1, *args)\n+        if f1 < f2:\n+            return x1, sign * f1\n+        else:\n+            return x2, sign * f2\n+\n+    def _eval_sr(self, a, w0, w1):\n+        '''\n+        Evaluate SR of the portfolio within the convex combination\n+        '''\n+\n+        w = a * w0 + (1 - a) * w1\n+        b = np.dot(w.T, self.expected_returns)[0, 0]\n+        c = np.dot(np.dot(w.T, self.cov_matrix), w)[0, 0] ** 0.5\n+        return b / c\n+\n+    def _bound_free_weight(self, free_weights):\n+        '''\n+        Add a free weight to list of bounded weights\n+        '''\n+\n+        lambda_in = None\n+        i_in = None\n+        bi_in = None\n+        if len(free_weights) > 1:\n+            covarF, covar_FB, mean_F, w_B = self._get_matrices(free_weights)\n+            covarF_inv = np.linalg.inv(covarF)\n+            j = 0\n+            for i in free_weights:\n+                l, bi = self._compute_lambda(\n+                    covarF_inv, covar_FB, mean_F, w_B, j, [self.lower_bounds[i], self.upper_bounds[i]]\n+                )\n+                if _infnone(l) > _infnone(lambda_in):\n+                    lambda_in, i_in, bi_in = l, i, bi\n+                j += 1\n+        return lambda_in, i_in, bi_in\n+\n+    def _free_bound_weight(self, free_weights):\n+        '''\n+        Add a bounded weight to list of free weights\n+        '''\n+\n+        lambda_out = None\n+        i_out = None\n+        if len(free_weights) < self.expected_returns.shape[0]:\n+            b = self._get_b(free_weights)\n+            for i in b:\n+                covarF, covar_FB, mean_F, w_B = self._get_matrices(free_weights + [i])\n+                covarF_inv = np.linalg.inv(covarF)\n+                l, bi = self._compute_lambda(\n+                    covarF_inv,\n+                    covar_FB,\n+                    mean_F,\n+                    w_B,\n+                    mean_F.shape[0] - 1,\n+                    self.weights[-1][i],\n+                )\n+                if (self.lambdas[-1] is None or l < self.lambdas[-1]) and l > _infnone(lambda_out):\n+                    lambda_out, i_out = l, i\n+        return lambda_out, i_out\n+\n+    def _initialise(self, asset_prices):\n+\n+        # Initial checks\n+        if type(asset_prices) != pd.DataFrame:\n+            raise ValueError(\"Asset prices matrix must be a dataframe\")\n+        if not isinstance(asset_prices.index, pd.DatetimeIndex):\n+            raise ValueError(\"Asset prices dataframe must be indexed by date.\")\n+\n+        # Calculate the expected returns\n+        if self.calculate_returns == \"mean\":\n+            self.expected_returns = self._calculate_mean_historical_returns(X=asset_prices)\n+        else:\n+            self.expected_returns = self._calculate_exponential_historical_returns(X=asset_prices)\n+        self.expected_returns = np.array(self.expected_returns).reshape((len(self.expected_returns), 1))\n+        if (self.expected_returns == np.ones(self.expected_returns.shape) * self.expected_returns.mean()).all():\n+            self.expected_returns[-1, 0] += 1e-5\n+\n+        # Calculate the covariance matrix\n+        self.cov_matrix = np.asarray(asset_prices.cov())\n+\n+        if isinstance(self.weight_bounds[0], numbers.Real):\n+            self.lower_bounds = np.ones(self.expected_returns.shape) * self.weight_bounds[0]\n+        else:\n+            self.lower_bounds = np.array(self.weight_bounds[0]).reshape(self.expected_returns.shape)\n+\n+        if isinstance(self.weight_bounds[0], numbers.Real):\n+            self.upper_bounds = np.ones(self.expected_returns.shape) * self.weight_bounds[1]\n+        else:\n+            self.upper_bounds = np.array(self.weight_bounds[1]).reshape(self.expected_returns.shape)\n+\n+        # Initialise storage buffers\n+        self.weights = []\n+        self.lambdas = []\n+        self.gammas = []\n+        self.free_weights = []\n+\n+    def _max_sharpe(self):\n+        '''\n+        Compute the maximum sharpe portfolio allocation\n+\n+        :return: (float, np.array) tuple of max. sharpe value and the set of weight allocations\n+        '''\n+\n+        # 1) Compute the local max SR portfolio between any two neighbor turning points\n+        w_sr, sr = [], []\n+        for i in range(len(self.weights) - 1):\n+            w0 = np.copy(self.weights[i])\n+            w1 = np.copy(self.weights[i + 1])\n+            kargs = {\"minimum\": False, \"args\": (w0, w1)}\n+            a, b = self._golden_section(self._eval_sr, 0, 1, **kargs)\n+            w_sr.append(a * w0 + (1 - a) * w1)\n+            sr.append(b)\n+        return max(sr), w_sr[sr.index(max(sr))]\n+\n+    def _min_volatility(self):\n+        '''\n+        Compute minimum volatility portfolio allocation\n+\n+        :return: (float, np.array) tuple of minimum variance value and the set of weight allocations\n+        '''\n+\n+        var = []\n+        for w in self.weights:\n+            a = np.dot(np.dot(w.T, self.cov_matrix), w)\n+            var.append(a)\n+        min_var = min(var)\n+        return min_var ** .5, self.weights[var.index(min_var)]\n+\n+    def _efficient_frontier(self, points=100):\n+        '''\n+        Compute the entire efficient frontier solution\n+\n+        :param points: (int) number of efficient frontier points to be calculated\n+        :return: tuple of mean, variance amd weights of the frontier solutions\n+        '''\n+\n+        mu, sigma, weights = [], [], []\n+\n+        # remove the 1, to avoid duplications","path":"mlfinlab/portfolio_optimization/cla.py","position":448,"original_position":442,"commit_id":"e49f64698dd21236c2ebadcf321dc63dab51c5a9","original_commit_id":"f68fa3b80e381f169882714b6cc912c3019c184c","user":{"login":"Jackal08","id":6911714,"node_id":"MDQ6VXNlcjY5MTE3MTQ=","avatar_url":"https://avatars0.githubusercontent.com/u/6911714?v=4","gravatar_id":"","url":"https://api.github.com/users/Jackal08","html_url":"https://github.com/Jackal08","followers_url":"https://api.github.com/users/Jackal08/followers","following_url":"https://api.github.com/users/Jackal08/following{/other_user}","gists_url":"https://api.github.com/users/Jackal08/gists{/gist_id}","starred_url":"https://api.github.com/users/Jackal08/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Jackal08/subscriptions","organizations_url":"https://api.github.com/users/Jackal08/orgs","repos_url":"https://api.github.com/users/Jackal08/repos","events_url":"https://api.github.com/users/Jackal08/events{/privacy}","received_events_url":"https://api.github.com/users/Jackal08/received_events","type":"User","site_admin":false},"body":"Capital letters","created_at":"2019-08-30T18:14:15Z","updated_at":"2019-08-30T18:18:49Z","html_url":"https://github.com/hudson-and-thames/mlfinlab/pull/105#discussion_r319624443","pull_request_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/105","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/comments/319624443"},"html":{"href":"https://github.com/hudson-and-thames/mlfinlab/pull/105#discussion_r319624443"},"pull_request":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/105"}}},"pull_request":{"url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/105","id":304028417,"node_id":"MDExOlB1bGxSZXF1ZXN0MzA0MDI4NDE3","html_url":"https://github.com/hudson-and-thames/mlfinlab/pull/105","diff_url":"https://github.com/hudson-and-thames/mlfinlab/pull/105.diff","patch_url":"https://github.com/hudson-and-thames/mlfinlab/pull/105.patch","issue_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues/105","number":105,"state":"open","locked":false,"title":"Add Portfolio Optimisation Module","user":{"login":"aditya1702","id":15054664,"node_id":"MDQ6VXNlcjE1MDU0NjY0","avatar_url":"https://avatars3.githubusercontent.com/u/15054664?v=4","gravatar_id":"","url":"https://api.github.com/users/aditya1702","html_url":"https://github.com/aditya1702","followers_url":"https://api.github.com/users/aditya1702/followers","following_url":"https://api.github.com/users/aditya1702/following{/other_user}","gists_url":"https://api.github.com/users/aditya1702/gists{/gist_id}","starred_url":"https://api.github.com/users/aditya1702/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aditya1702/subscriptions","organizations_url":"https://api.github.com/users/aditya1702/orgs","repos_url":"https://api.github.com/users/aditya1702/repos","events_url":"https://api.github.com/users/aditya1702/events{/privacy}","received_events_url":"https://api.github.com/users/aditya1702/received_events","type":"User","site_admin":false},"body":"# Description\r\n\r\nAdds a portfolio optimisation module in the package. Currently this module houses the following algorithms - \r\n1. CLA\r\n2. HRP\r\n3. Inverse Variance Portfolio\r\n\r\n## Type of change\r\nAdd main code files for the algorithms and their respective unittests\r\n\r\nPlease delete options that are not relevant.\r\n\r\n- [ ] Bug fix (non-breaking change which fixes an issue)\r\n- [x] New feature (non-breaking change which adds functionality)\r\n- [x] This change requires a documentation update\r\n\r\n# How Has This Been Tested?\r\n\r\nPlease describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration\r\n\r\n- [x] Wrote unit tests for all code\r\n\r\n**Test Configuration**:\r\n* Operating system: Mac OSX\r\n* IDE used: Pycharm\r\n\r\n\r\n# Checklist:\r\n\r\n- [x] My code follows the style guidelines of this project\r\n- [x] I have performed a self-review of my own code\r\n- [x] I have commented my code, particularly in hard-to-understand areas\r\n- [ ] I have made corresponding changes to the documentation\r\n- [ ] My changes generate no new warnings\r\n- [x] I have added tests that prove my fix is effective or that my feature works\r\n- [x] New and existing unit tests pass locally with my changes\r\n- [ ] Any dependent changes have been merged and published in downstream modules","created_at":"2019-08-03T22:09:33Z","updated_at":"2019-08-30T18:18:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4229c8cc6386ed156af23787d0ecbec81dcdaa8f","assignee":{"login":"aditya1702","id":15054664,"node_id":"MDQ6VXNlcjE1MDU0NjY0","avatar_url":"https://avatars3.githubusercontent.com/u/15054664?v=4","gravatar_id":"","url":"https://api.github.com/users/aditya1702","html_url":"https://github.com/aditya1702","followers_url":"https://api.github.com/users/aditya1702/followers","following_url":"https://api.github.com/users/aditya1702/following{/other_user}","gists_url":"https://api.github.com/users/aditya1702/gists{/gist_id}","starred_url":"https://api.github.com/users/aditya1702/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aditya1702/subscriptions","organizations_url":"https://api.github.com/users/aditya1702/orgs","repos_url":"https://api.github.com/users/aditya1702/repos","events_url":"https://api.github.com/users/aditya1702/events{/privacy}","received_events_url":"https://api.github.com/users/aditya1702/received_events","type":"User","site_admin":false},"assignees":[{"login":"aditya1702","id":15054664,"node_id":"MDQ6VXNlcjE1MDU0NjY0","avatar_url":"https://avatars3.githubusercontent.com/u/15054664?v=4","gravatar_id":"","url":"https://api.github.com/users/aditya1702","html_url":"https://github.com/aditya1702","followers_url":"https://api.github.com/users/aditya1702/followers","following_url":"https://api.github.com/users/aditya1702/following{/other_user}","gists_url":"https://api.github.com/users/aditya1702/gists{/gist_id}","starred_url":"https://api.github.com/users/aditya1702/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aditya1702/subscriptions","organizations_url":"https://api.github.com/users/aditya1702/orgs","repos_url":"https://api.github.com/users/aditya1702/repos","events_url":"https://api.github.com/users/aditya1702/events{/privacy}","received_events_url":"https://api.github.com/users/aditya1702/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":1232033394,"node_id":"MDU6TGFiZWwxMjMyMDMzMzk0","url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/labels/enhancement","name":"enhancement","color":"a2eeef","default":true}],"milestone":null,"commits_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/105/commits","review_comments_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/105/comments","review_comment_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/comments{/number}","comments_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues/105/comments","statuses_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/statuses/e49f64698dd21236c2ebadcf321dc63dab51c5a9","head":{"label":"hudson-and-thames:portfolio_optimization","ref":"portfolio_optimization","sha":"e49f64698dd21236c2ebadcf321dc63dab51c5a9","user":{"login":"hudson-and-thames","id":47124270,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3MTI0Mjcw","avatar_url":"https://avatars3.githubusercontent.com/u/47124270?v=4","gravatar_id":"","url":"https://api.github.com/users/hudson-and-thames","html_url":"https://github.com/hudson-and-thames","followers_url":"https://api.github.com/users/hudson-and-thames/followers","following_url":"https://api.github.com/users/hudson-and-thames/following{/other_user}","gists_url":"https://api.github.com/users/hudson-and-thames/gists{/gist_id}","starred_url":"https://api.github.com/users/hudson-and-thames/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hudson-and-thames/subscriptions","organizations_url":"https://api.github.com/users/hudson-and-thames/orgs","repos_url":"https://api.github.com/users/hudson-and-thames/repos","events_url":"https://api.github.com/users/hudson-and-thames/events{/privacy}","received_events_url":"https://api.github.com/users/hudson-and-thames/received_events","type":"Organization","site_admin":false},"repo":{"id":170544934,"node_id":"MDEwOlJlcG9zaXRvcnkxNzA1NDQ5MzQ=","name":"mlfinlab","full_name":"hudson-and-thames/mlfinlab","private":false,"owner":{"login":"hudson-and-thames","id":47124270,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3MTI0Mjcw","avatar_url":"https://avatars3.githubusercontent.com/u/47124270?v=4","gravatar_id":"","url":"https://api.github.com/users/hudson-and-thames","html_url":"https://github.com/hudson-and-thames","followers_url":"https://api.github.com/users/hudson-and-thames/followers","following_url":"https://api.github.com/users/hudson-and-thames/following{/other_user}","gists_url":"https://api.github.com/users/hudson-and-thames/gists{/gist_id}","starred_url":"https://api.github.com/users/hudson-and-thames/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hudson-and-thames/subscriptions","organizations_url":"https://api.github.com/users/hudson-and-thames/orgs","repos_url":"https://api.github.com/users/hudson-and-thames/repos","events_url":"https://api.github.com/users/hudson-and-thames/events{/privacy}","received_events_url":"https://api.github.com/users/hudson-and-thames/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/hudson-and-thames/mlfinlab","description":"Package based on the work of Dr Marcos Lopez de Prado regarding his research with respect to Advances in Financial Machine Learning","fork":false,"url":"https://api.github.com/repos/hudson-and-thames/mlfinlab","forks_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/forks","keys_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/teams","hooks_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/hooks","issue_events_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues/events{/number}","events_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/events","assignees_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/assignees{/user}","branches_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/branches{/branch}","tags_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/tags","blobs_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/refs{/sha}","trees_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/statuses/{sha}","languages_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/languages","stargazers_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/stargazers","contributors_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/contributors","subscribers_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/subscribers","subscription_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/subscription","commits_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/commits{/sha}","git_commits_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/commits{/sha}","comments_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/comments{/number}","issue_comment_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues/comments{/number}","contents_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/contents/{+path}","compare_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/merges","archive_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/downloads","issues_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues{/number}","pulls_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls{/number}","milestones_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/milestones{/number}","notifications_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/labels{/name}","releases_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/releases{/id}","deployments_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/deployments","created_at":"2019-02-13T16:57:25Z","updated_at":"2019-08-30T15:37:49Z","pushed_at":"2019-08-30T18:09:37Z","git_url":"git://github.com/hudson-and-thames/mlfinlab.git","ssh_url":"git@github.com:hudson-and-thames/mlfinlab.git","clone_url":"https://github.com/hudson-and-thames/mlfinlab.git","svn_url":"https://github.com/hudson-and-thames/mlfinlab","homepage":"https://hudsonthames.org/blog/","size":9093,"stargazers_count":418,"watchers_count":418,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":96,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":14,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":96,"open_issues":14,"watchers":418,"default_branch":"master"}},"base":{"label":"hudson-and-thames:dev","ref":"dev","sha":"9114452852c872f1ec1a4479e7f0e36befd3f3e5","user":{"login":"hudson-and-thames","id":47124270,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3MTI0Mjcw","avatar_url":"https://avatars3.githubusercontent.com/u/47124270?v=4","gravatar_id":"","url":"https://api.github.com/users/hudson-and-thames","html_url":"https://github.com/hudson-and-thames","followers_url":"https://api.github.com/users/hudson-and-thames/followers","following_url":"https://api.github.com/users/hudson-and-thames/following{/other_user}","gists_url":"https://api.github.com/users/hudson-and-thames/gists{/gist_id}","starred_url":"https://api.github.com/users/hudson-and-thames/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hudson-and-thames/subscriptions","organizations_url":"https://api.github.com/users/hudson-and-thames/orgs","repos_url":"https://api.github.com/users/hudson-and-thames/repos","events_url":"https://api.github.com/users/hudson-and-thames/events{/privacy}","received_events_url":"https://api.github.com/users/hudson-and-thames/received_events","type":"Organization","site_admin":false},"repo":{"id":170544934,"node_id":"MDEwOlJlcG9zaXRvcnkxNzA1NDQ5MzQ=","name":"mlfinlab","full_name":"hudson-and-thames/mlfinlab","private":false,"owner":{"login":"hudson-and-thames","id":47124270,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ3MTI0Mjcw","avatar_url":"https://avatars3.githubusercontent.com/u/47124270?v=4","gravatar_id":"","url":"https://api.github.com/users/hudson-and-thames","html_url":"https://github.com/hudson-and-thames","followers_url":"https://api.github.com/users/hudson-and-thames/followers","following_url":"https://api.github.com/users/hudson-and-thames/following{/other_user}","gists_url":"https://api.github.com/users/hudson-and-thames/gists{/gist_id}","starred_url":"https://api.github.com/users/hudson-and-thames/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hudson-and-thames/subscriptions","organizations_url":"https://api.github.com/users/hudson-and-thames/orgs","repos_url":"https://api.github.com/users/hudson-and-thames/repos","events_url":"https://api.github.com/users/hudson-and-thames/events{/privacy}","received_events_url":"https://api.github.com/users/hudson-and-thames/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/hudson-and-thames/mlfinlab","description":"Package based on the work of Dr Marcos Lopez de Prado regarding his research with respect to Advances in Financial Machine Learning","fork":false,"url":"https://api.github.com/repos/hudson-and-thames/mlfinlab","forks_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/forks","keys_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/teams","hooks_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/hooks","issue_events_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues/events{/number}","events_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/events","assignees_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/assignees{/user}","branches_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/branches{/branch}","tags_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/tags","blobs_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/refs{/sha}","trees_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/statuses/{sha}","languages_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/languages","stargazers_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/stargazers","contributors_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/contributors","subscribers_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/subscribers","subscription_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/subscription","commits_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/commits{/sha}","git_commits_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/git/commits{/sha}","comments_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/comments{/number}","issue_comment_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues/comments{/number}","contents_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/contents/{+path}","compare_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/merges","archive_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/downloads","issues_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues{/number}","pulls_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls{/number}","milestones_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/milestones{/number}","notifications_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/labels{/name}","releases_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/releases{/id}","deployments_url":"https://api.github.com/repos/hudson-and-thames/mlfinlab/deployments","created_at":"2019-02-13T16:57:25Z","updated_at":"2019-08-30T15:37:49Z","pushed_at":"2019-08-30T18:09:37Z","git_url":"git://github.com/hudson-and-thames/mlfinlab.git","ssh_url":"git@github.com:hudson-and-thames/mlfinlab.git","clone_url":"https://github.com/hudson-and-thames/mlfinlab.git","svn_url":"https://github.com/hudson-and-thames/mlfinlab","homepage":"https://hudsonthames.org/blog/","size":9093,"stargazers_count":418,"watchers_count":418,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":96,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":14,"license":{"key":"bsd-3-clause","name":"BSD 3-Clause \"New\" or \"Revised\" License","spdx_id":"BSD-3-Clause","url":"https://api.github.com/licenses/bsd-3-clause","node_id":"MDc6TGljZW5zZTU="},"forks":96,"open_issues":14,"watchers":418,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/105"},"html":{"href":"https://github.com/hudson-and-thames/mlfinlab/pull/105"},"issue":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues/105"},"comments":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/issues/105/comments"},"review_comments":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/105/comments"},"review_comment":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/pulls/105/commits"},"statuses":{"href":"https://api.github.com/repos/hudson-and-thames/mlfinlab/statuses/e49f64698dd21236c2ebadcf321dc63dab51c5a9"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"Jackal08"}}