├── portfolio ├── optimizations │ ├── __init__.py │ ├── solvers.py │ ├── kelly.py │ └── gmv.py ├── __init__.py ├── constraints.py ├── analytics.py ├── objectives.py ├── zipline.py └── utils.py ├── requirements.txt ├── .landscape.yaml ├── setup.cfg ├── dev-requirements.txt ├── .coveragerc ├── Dockerfile ├── shippable.yml ├── .gitignore ├── tests └── test_portfolio.py ├── setup.py ├── Makefile ├── README.md └── LICENSE /portfolio/optimizations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.8.1 2 | pandas==0.14.1 3 | scipy==0.14.0 4 | -------------------------------------------------------------------------------- /.landscape.yaml: -------------------------------------------------------------------------------- 1 | doc-warnings: true 2 | test-warnings: true 3 | strictness: veryhigh 4 | autodetect: true 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | verbosity=3 3 | detailed-errors=1 4 | 5 | [metadata] 6 | description-file = README.md 7 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | pep8==1.5.7 2 | nose==1.3.3 3 | yanc==0.2.4 4 | coveralls==0.4.2 5 | piprot==0.6.0 6 | radon==0.5.3 7 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */__init__.py 4 | */python?.?/* 5 | */.virtualenvs/* 6 | */site-packages/* 7 | */quant/src/* 8 | */quant/lib/* 9 | 10 | [run] 11 | source = portfolio 12 | -------------------------------------------------------------------------------- /portfolio/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Portfolio 3 | --------- 4 | 5 | :copyright (c) 2014 Xavier Bruhiere 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | __project__ = 'portfolio-analytics' 10 | __teaser__ = 'Keep calm and optimize.' 11 | __author__ = 'Xavier Bruhiere' 12 | __copyright__ = 'Xavier Bruhiere' 13 | __licence__ = 'Apache 2.0' 14 | __version__ = '0.0.1' 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # hivetech/intuition image 2 | # A raring box with Intuition (https://github.com/hackliff/intuition installed 3 | # and ready to use 4 | # docker run -i -t hivetech/portfolio bash 5 | # VERSION 0.0.0 6 | 7 | FROM hivetech/batcave:base 8 | MAINTAINER Xavier Bruhiere 9 | 10 | ADD . /portfolio 11 | RUN cd /portfolio && make 12 | RUN apt-get clean && rm -rf \ 13 | /portofolio/build \ 14 | /tmp/* /var/tmp/* \ 15 | /var/lib/apt/lists/* \ 16 | /etc/dpkg/dpkg.cfg.d/02apt-speedup \ 17 | /etc/ssh/ssh_host_* 18 | -------------------------------------------------------------------------------- /shippable.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | 5 | env: 6 | - COVERALLS_REPO_TOKEN=uwZXSYVHQUdUBcUeUXCbzFktqamvuh2TA 7 | 8 | install: 9 | - make 10 | - pip install --upgrade -r dev-requirements.txt 11 | 12 | before_script: 13 | - mkdir -p shippable/codecoverage shippable/testresults 14 | - pep8 --exclude _review --ignore E265 portfolio tests 15 | 16 | script: 17 | - nosetests tests --with-coverage --cover-package=portfolio --with-xunit --xunit-file=shippable/testresults/nosetests.xml 18 | - coverage xml -o shippable/codecoverage/coverage.xml 19 | 20 | after_success: 21 | - coveralls 22 | 23 | notifications: 24 | mail: 25 | xavier.bruhiere@gmail.com 26 | 27 | branches: 28 | only: 29 | - master 30 | - develop 31 | -------------------------------------------------------------------------------- /portfolio/constraints.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | Constraints functions 6 | --------------------- 7 | 8 | :copyright (c) 2014 Xavier Bruhiere. 9 | :license: %LICENCE%, see LICENSE for more details. 10 | ''' 11 | 12 | import numpy as np 13 | 14 | 15 | def box_constraint(min_bound, max_bound): 16 | return {'type': 'box', 'min': min_bound, 'max': max_bound} 17 | 18 | 19 | def long_only(max_bound=None): 20 | ''' Limit sid allocation to positive values ''' 21 | return box_constraint(0, max_bound) 22 | 23 | 24 | def limited_investment(limit): 25 | ''' Sum of optimized weights must be 100 % ''' 26 | return {'type': 'eq', 'fun': lambda x: np.sum(x) - limit} 27 | 28 | 29 | def full_investment(): 30 | ''' Sum of optimized weights must be 100 % ''' 31 | return limited_investment(limit=1.0) 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | 24 | # PyInstaller 25 | # Usually these files are written by a python script from a template 26 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 27 | *.manifest 28 | *.spec 29 | 30 | # Installer logs 31 | pip-log.txt 32 | pip-delete-this-directory.txt 33 | 34 | # Unit test / coverage reports 35 | htmlcov/ 36 | .tox/ 37 | .coverage 38 | .cache 39 | nosetests.xml 40 | coverage.xml 41 | 42 | # Translations 43 | *.mo 44 | *.pot 45 | 46 | # Django stuff: 47 | *.log 48 | 49 | # Sphinx documentation 50 | docs/_build/ 51 | 52 | # PyBuilder 53 | target/ 54 | 55 | # Project specific 56 | _review 57 | -------------------------------------------------------------------------------- /tests/test_portfolio.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Xavier Bruhiere. 6 | :license: %LICENCE%, see LICENSE for more details. 7 | ''' 8 | 9 | import unittest 10 | from nose.tools import eq_, nottest 11 | from portfolio.analytics import PortfolioAnalytics 12 | 13 | 14 | class BasicPortfolio(PortfolioAnalytics): 15 | def optimize(signals, *args): 16 | return {} 17 | 18 | 19 | class TestPortfolioObject(unittest.TestCase): 20 | 21 | @nottest 22 | def id_objective(self, x): 23 | return x 24 | 25 | def test_has_zipline_attributes(self): 26 | pf = BasicPortfolio() 27 | eq_(pf.positions, {}) 28 | eq_(pf.start_date, None) 29 | for attribute in ['capital_used', 'cash', 'pnl', 30 | 'portfolio_value', 'positions_value', 31 | 'returns', 'starting_cash']: 32 | eq_(pf[attribute], 0.0) 33 | -------------------------------------------------------------------------------- /portfolio/analytics.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Xavier Bruhiere. 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | import abc 10 | import portfolio.zipline 11 | 12 | NO_BOUND = None 13 | 14 | 15 | class PortfolioAnalytics(portfolio.zipline.FactoryPortfolio): 16 | ''' 17 | Zipline compatible extension of Portfolio, with analytics superpowers 18 | ''' 19 | 20 | __metaclass__ = abc.ABCMeta 21 | 22 | def __init__(self): 23 | super(PortfolioAnalytics, self).__init__() 24 | self._constraints = [] 25 | self._min_bound, self._max_bound = NO_BOUND, NO_BOUND 26 | 27 | def add_constraint(self, constraint): 28 | # TODO Check constraint structure (with schema ?) 29 | if constraint['type'] == 'box': 30 | self._min_bound = constraint['min'] 31 | self._max_bound = constraint['max'] 32 | else: 33 | self._constraints.append(constraint) 34 | 35 | def _box_constraint(self, i): 36 | ''' Lower and upper bound current state ''' 37 | return [[self._min_bound, self._max_bound] for _ in range(i)] 38 | 39 | @abc.abstractmethod 40 | def optimize(self, signals, *args): 41 | pass 42 | -------------------------------------------------------------------------------- /portfolio/objectives.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Xavier Bruhiere. 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | import numpy as np 10 | 11 | 12 | def risk(x, sids, data, penalty=0): 13 | ''' Use the covariance matrix from returns ''' 14 | returns = np.diff(data[sids], axis=0) 15 | p = np.asarray(returns) 16 | Acov = np.cov(p.T) 17 | return np.dot(x, np.dot(Acov, x)) + penalty 18 | 19 | 20 | def expected_return(x, sids, data, target=None): 21 | ''' Maximize expected returns ''' 22 | returns = (data[sids].shift() / data[sids] - 1).mean().values 23 | if target: 24 | fitness = np.dot((target - returns), x) 25 | else: 26 | fitness = 1 / np.dot(returns, x) 27 | return fitness 28 | 29 | 30 | def sharpe_ratio(x, sids, data, factor=252, rf=0.15): 31 | ''' Maximize Sharpe ration indicator ''' 32 | returns = data[sids].shift() / data[sids] - 1 33 | data_ret = returns.mean().values 34 | data_std = returns.std().values 35 | sharpe = (data_ret * factor - rf) / (data_std * np.sqrt(factor)) 36 | return 1 / np.dot(sharpe, x) 37 | 38 | 39 | def fair(x, *args): 40 | ''' Reduce gap between weights ''' 41 | return np.std(x) 42 | -------------------------------------------------------------------------------- /portfolio/zipline.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Quantopian, Inc. 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | 10 | class Position(object): 11 | 12 | def __init__(self, sid): 13 | self.sid = sid 14 | self.amount = 0 15 | self.cost_basis = 0.0 # per share 16 | self.last_sale_price = 0.0 17 | 18 | def __getitem__(self, key): 19 | return self.__dict__[key] 20 | 21 | def __repr__(self): 22 | return "Position({0})".format(self.__dict__) 23 | 24 | 25 | class Positions(dict): 26 | 27 | def __missing__(self, key): 28 | pos = Position(key) 29 | self[key] = pos 30 | return pos 31 | 32 | 33 | class FactoryPortfolio(object): 34 | 35 | def __init__(self): 36 | self.capital_used = 0.0 37 | self.starting_cash = 0.0 38 | self.portfolio_value = 0.0 39 | self.pnl = 0.0 40 | self.returns = 0.0 41 | self.cash = 0.0 42 | self.positions = Positions() 43 | self.start_date = None 44 | self.positions_value = 0.0 45 | 46 | def __getitem__(self, key): 47 | return self.__dict__[key] 48 | 49 | def __repr__(self): 50 | return "Portfolio({0})".format(self.__dict__) 51 | -------------------------------------------------------------------------------- /portfolio/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Xavier Bruhiere. 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | import numpy as np 10 | 11 | 12 | # NOTE Available with version 0.0.8 of dna 13 | def round_if_close(number, target=0.0, approx=0.01): 14 | return target if abs(number - target) < approx else number 15 | 16 | 17 | def neutral_guess(n_variables, factor=1.0): 18 | return np.ones(n_variables, dtype=float) * factor / n_variables 19 | 20 | 21 | def preprocess(fct): 22 | def inner(self, signals, *args): 23 | if isinstance(signals, list): 24 | # Default is a list of interesting stocks to buy 25 | signals = {'buy': signals} 26 | sell_signals = signals.get('sell', []) 27 | buy_signals = signals.get('buy', []) 28 | 29 | positions = [p for p in self.positions 30 | if (p not in sell_signals) 31 | and self.positions[p]['amount'] != 0] 32 | universe = [sid for sid in set(buy_signals).union(positions)] 33 | 34 | # Close every positoins on 'sell' signal 35 | allocation = { 36 | sid: 0.0 for sid in sell_signals if sid in self.positions 37 | } 38 | if universe and len(signals['buy']) > 0: 39 | allocation.update(fct(self, universe, *args)) 40 | return allocation 41 | return inner 42 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Xavier Bruhiere 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | import multiprocessing 10 | import setuptools 11 | from portfolio import ( 12 | __version__, __author__, __licence__, __project__, __teaser__ 13 | ) 14 | 15 | REQUIREMENTS = [ 16 | 'numpy', 17 | 'pandas', 18 | 'scipy' 19 | ] 20 | 21 | 22 | def long_description(): 23 | ''' Safely read README.md ''' 24 | try: 25 | with open('README.md') as fd: 26 | return fd.read() 27 | except IOError: 28 | return "failed to read README.md" 29 | 30 | 31 | setuptools.setup( 32 | name=__project__, 33 | version=__version__, 34 | description=__teaser__, 35 | author=__author__, 36 | author_email='xavier.bruhiere@gmail.com', 37 | packages=setuptools.find_packages(), 38 | long_description=long_description(), 39 | license=__licence__, 40 | install_requires=REQUIREMENTS, 41 | url="https://github.com/intuition-io/portfolio", 42 | classifiers=[ 43 | 'Development Status :: 2 - Pre-Alpha', 44 | 'License :: OSI Approved :: Apache Software License', 45 | 'Natural Language :: English', 46 | 'Programming Language :: Python', 47 | 'Programming Language :: Python :: 2.7', 48 | 'Operating System :: OS Independent', 49 | 'Intended Audience :: Science/Research', 50 | 'Topic :: Office/Business :: Financial', 51 | 'Topic :: Scientific/Engineering :: Information Analysis', 52 | 'Topic :: System :: Distributed Computing', 53 | ] 54 | ) 55 | -------------------------------------------------------------------------------- /portfolio/optimizations/solvers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Xavier Bruhiere. 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | from scipy import optimize 10 | import portfolio.utils 11 | from portfolio.analytics import PortfolioAnalytics 12 | 13 | 14 | class SolverPortfolio(PortfolioAnalytics): 15 | 16 | def __init__(self, objective_func, solver='SLSQP'): 17 | self._objective = objective_func 18 | self._solver = solver 19 | self._optimizer = optimize.minimize 20 | 21 | super(SolverPortfolio, self).__init__() 22 | 23 | # TODO Make the solver pluggable 24 | # TODO If no universe provided, try to use *args quotes ? 25 | @portfolio.utils.preprocess 26 | def optimize(self, universe, *args): 27 | L = len(universe) 28 | kwargs = { 29 | 'fun': self._objective, 30 | # NOTE What is the influence of this vector ? 31 | 'x0': portfolio.utils.neutral_guess(L), 32 | 'args': tuple([universe] + list(args)), 33 | 'method': self._solver, 34 | 'constraints': self._constraints, 35 | 'bounds': self._box_constraint(L), 36 | 'options': {'disp': False, 'maxiter': 30} 37 | } 38 | result = self._optimizer(**kwargs) 39 | print('Done: {} ({} iterations)'.format( 40 | result.message, result.nit)) 41 | 42 | if result.success: 43 | allocation = { 44 | sid: portfolio.utils.round_if_close(result.x[i]) 45 | for i, sid in enumerate(universe) 46 | } 47 | 48 | return allocation if result.success else {} 49 | -------------------------------------------------------------------------------- /portfolio/optimizations/kelly.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Xavier Bruhiere. 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | import numpy as np 10 | import pandas as pd 11 | import portfolio.utils 12 | from portfolio.analytics import PortfolioAnalytics 13 | 14 | 15 | class KellyPortfolio(PortfolioAnalytics): 16 | 17 | def __init__(self, **kwargs): 18 | # TODO Each parameters should be implemented as constraints 19 | # TODO And support other constaints like long_only 20 | self.leverage = kwargs.get('leverage', 1.0) 21 | self.short_pct = kwargs.get('short_pct', 0.7) 22 | self.port_size = kwargs.get('port_size', 25) 23 | 24 | super(KellyPortfolio, self).__init__() 25 | 26 | @portfolio.utils.preprocess 27 | def optimize(self, universe, prices): 28 | 29 | R = prices[universe].pct_change().dropna() 30 | # Remove bad data (non-variant) 31 | kelly = (R.mean() / R.var()).dropna() 32 | kelly.sort() 33 | picks = kelly.tail(self.port_size) 34 | # Assume a relationship between the securities and calculate the Kelly 35 | # leverages 36 | R = R[picks.index] 37 | C_inv = np.linalg.inv(R.cov()) 38 | kelly = pd.Series(np.dot(C_inv, R.mean()), index=R.columns) 39 | 40 | # Limit short exposure if the Kelly score is negative 41 | # NOTE Kind of generic portfolio constraint 42 | kelly = kelly.apply(lambda x: max(x, self.short_pct * x)) 43 | 44 | # Adjust result to keep the account leverage constant 45 | kelly *= (self.leverage / kelly.abs().sum()) 46 | 47 | print('Done: {}'.format(kelly)) 48 | return kelly.to_dict() 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # vim:ft=make 3 | 4 | LOGS?=/tmp/make-portfolio.logs 5 | 6 | all: dependencies install 7 | 8 | install: 9 | python setup.py install 10 | 11 | dependencies: 12 | @echo "[make] Installing packages" 13 | sudo apt-get update -y 14 | sudo apt-get -y --no-install-recommends install libopenblas-dev liblapack-dev gfortran 2>&1 >> ${LOGS} 15 | @echo "[make] Installing python modules" 16 | pip install --quiet --use-mirrors distribute 2>&1 >> ${LOGS} 17 | pip install --quiet --use-mirrors numpy 2>&1 >> ${LOGS} 18 | 19 | package: 20 | # NOTE Replace the version in portfolio.__init__.py ? 21 | @echo "[make] Committing changes" 22 | git add -A 23 | git commit 24 | git tag ${VERSION} 25 | git push --tags 26 | @echo "[make] Packaging portfolio" 27 | python setup.py sdist 28 | python setup.py sdist upload 29 | 30 | tests: warn_missing_linters 31 | # TODO Recursively analyze all files and fail on conditions 32 | @echo -e '\tChecking complexity (experimental) ...' 33 | radon cc -ana portfolio/analytics.py 34 | @echo -e '\tChecking requirements ...' 35 | # TODO Fail if outdated 36 | piprot --outdated requirements.txt dev-requirements.txt 37 | @echo -e '\tChecking syntax ...' 38 | pep8 --exclude _review --ignore E265 tests portfolio 39 | @echo -e '\tRunning tests ...' 40 | nosetests -s -w tests --with-yanc --with-coverage --cover-package=portfolio 41 | 42 | present_pep8=$(shell which pep8) 43 | present_radon=$(shell which radon) 44 | present_nose=$(shell which nosetests) 45 | present_piprot=$(shell which piprot) 46 | warn_missing_linters: 47 | @test -n "$(present_radon)" || echo "WARNING: radon not installed." 48 | @test -n "$(present_pep8)" || echo "WARNING: pep8 not installed." 49 | @test -n "$(present_nose)" || echo "WARNING: nose not installed." 50 | @test -n "$(present_piprot)" || echo "WARNING: piprot not installed." 51 | 52 | .PHONY: dependencies install warn_missing_linters tests package 53 | -------------------------------------------------------------------------------- /portfolio/optimizations/gmv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim:fenc=utf-8 3 | 4 | ''' 5 | :copyright (c) 2014 Xavier Bruhiere. 6 | :license: Apache 2.0, see LICENSE for more details. 7 | ''' 8 | 9 | import numpy as np 10 | import portfolio.utils 11 | from portfolio.analytics import PortfolioAnalytics 12 | 13 | 14 | # https://www.quantopian.com/posts/global-minimum-variance-portfolio?c=1 15 | class GlobalMinimumVariance(PortfolioAnalytics): 16 | 17 | @portfolio.utils.preprocess 18 | def optimize(self, universe, prices): 19 | allocation = {} 20 | try: 21 | returns = (prices.shift() / prices - 1).fillna(method='bfill') 22 | returns = returns[universe].transpose() 23 | L = len(returns) 24 | # create a covariance matrix 25 | covariance_matrix = np.cov(returns, y=None, rowvar=1, 26 | bias=0, ddof=None) 27 | covariance_matrix = np.matrix(covariance_matrix) 28 | 29 | # calculate global minimum portfolio weights 30 | # NOTE This must be x 31 | one_vector = np.matrix(np.ones(L)).transpose() 32 | # NOTE Or this one ? 33 | one_row = np.matrix(np.ones(L)) 34 | covariance_matrix_inv = np.linalg.inv(covariance_matrix) 35 | numerator = np.dot(covariance_matrix_inv, one_vector) 36 | denominator = np.dot(np.dot(one_row, covariance_matrix_inv), 37 | one_vector) 38 | 39 | weights = numerator / denominator 40 | except Exception, error: 41 | print(error) 42 | weights = [np.nan] * L 43 | 44 | if np.isnan(weights).any() or not weights.any(): 45 | print('Could not compute weigths') 46 | else: 47 | for i, sid in enumerate(prices[universe]): 48 | allocation[sid] = float(weights[i]) 49 | 50 | return allocation 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Portfolio Analytics 2 | =================== 3 | 4 | [![Build Status](https://api.shippable.com/projects/53ce99a67c72335f045a19bb/badge/master)](https://www.shippable.com/projects/53ce99a67c72335f045a19bb) 5 | [![Coverage Status](https://img.shields.io/coveralls/intuition-io/portfolio.svg)](https://coveralls.io/r/intuition-io/portfolio) 6 | [![Code Health](https://landscape.io/github/intuition-io/portfolio/master/landscape.png)](https://landscape.io/github/intuition-io/portfolio/master) 7 | [![Requirements Status](https://requires.io/github/intuition-io/portfolio/requirements.png?branch=master)](https://requires.io/github/intuition-io/portfolio/requirements/?branch=master) 8 | [![License](https://pypip.in/license/intuition/badge.png)](https://pypi.python.org/pypi/intuition/) 9 | [![Gitter chat](https://badges.gitter.im/intuition-io.png)](https://gitter.im/intuition-io) 10 | 11 | > [Zipline][1] compatible extension of Portfolio, with analytics superpowers. 12 | > Inspired by [PortfolioAnalytics][6] R package. 13 | 14 | The project provides several portfolio optimizations that compute optimal 15 | assets allocation regarding a various set of factors and constraints. Currently 16 | you will get the following implementations : 17 | 18 | * [General optimization problem with solvers][7] 19 | * [Global Minimum Variance][8] 20 | * [Kelly criterion][9] 21 | 22 | To learn more about the API, check [the full documentation][3]. 23 | 24 | This project is currently part of the **intuition project**, signup for [the 25 | private beta][2] and/or [clone your own hedge fund][4]. 26 | 27 | Finally, the whole thing is compatible with [zipline backtester][1]. 28 | 29 | 30 | Install 31 | ------- 32 | 33 | ``` 34 | $ sudo apt-get install libopenblas-dev liblapack-dev gfortran 35 | $ pip install portfolio-analytics 36 | 37 | ... blablabla it compiles a lot of maths, grab a coffee ... 38 | ``` 39 | 40 | Or play with it right away with [docker][11]: 41 | 42 | ``` 43 | $ docker run -i -t hivetech/portfolio bash 44 | ``` 45 | 46 | A taste of it 47 | ------------- 48 | 49 | ```python 50 | # Download some historical data 51 | from pandas.io.data import get_data_google 52 | ohlc_data = get_data_google(['adsk', 'ctxs', 'fb', 'nflx', 'qcom'], start='2013/01/01', end='2013/12/01') 53 | data = ohlc_data['Close'] 54 | 55 | # Now let's optimize our portfolio weights 56 | from portfolio.optimizations.solvers import SolverPortfolio 57 | import portfolio.objectives as objective 58 | import portfolio.constraints as constraint 59 | 60 | portfolio = SolverPortfolio(objective.risk) 61 | # Forbid short positions 62 | portfolio.add_constraint(constraint.long_only()) 63 | # Invest every cent of our cash 64 | portfolio.add_constraint(constraint.full_investment()) 65 | 66 | # Get optimal weights in % 67 | pf.optimize(['ctxs', 'fb', 'nflx', 'qcom', 'adsk'], data) 68 | Out[66]: 69 | {'adsk': 0.49, 70 | 'ctxs': 0.04, 71 | 'fb': 0.17, 72 | 'nflx': 0.0, 73 | 'qcom': 0.29} 74 | ``` 75 | 76 | Contributing 77 | ------------ 78 | 79 | Contributors are happily welcome, [here is a place to start][10]. 80 | 81 | 82 | License 83 | ------- 84 | 85 | Copyright 2014 Xavier Bruhiere. 86 | 87 | *Portfolio* is available under the [Apache License, Version 2.0][5]. 88 | 89 | 90 | [1]: https://github.com/quantopian/zipline 91 | [2]: http://intuition.io 92 | [3]: http://doc.intuition.io 93 | [4]: https://github.com/intuition-io/intuition 94 | [5]: http://www.apache.org/licenses/LICENSE-2.0.html 95 | [6]: https://r-forge.r-project.org/R/?group_id=579 96 | [7]: http://docs.scipy.org/doc/scipy/reference/optimize.html 97 | [8]: http://www.investopedia.com/terms/p/portfolio-variance.asp 98 | [9]: http://www.investopedia.com/articles/trading/04/091504.asp 99 | [10]: http://doc.intuition.io/articles/contributors.html 100 | [11]: http://docker.io 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | --------------------------------------------------------------------------------