├── tests
├── __init__.py
└── unit
│ ├── __init__.py
│ └── technical_indicators.py
├── quantpy
├── gui
│ ├── __init__.py
│ ├── data
│ │ ├── app_menu.ui
│ │ └── quantpy.ui
│ ├── app.py
│ ├── settings.py
│ └── utils.py
├── fundamental_indicators.py
├── __init__.py
├── event_profiler.py
├── portfolio.py
└── technical_indicators.py
├── docs
├── requirements.txt
├── index.rst
├── _templates
│ └── layout.html
├── getting_started.rst
├── sphinxext
│ ├── numpydoc.py
│ ├── ipython_console_highlighting.py
│ ├── docscrape_sphinx.py
│ ├── inheritance_diagram.py
│ ├── docscrape.py
│ └── apigen.py
├── make.bat
├── Makefile
├── conf.py
└── _static
│ └── default.css
├── .gitignore
├── examples
├── efficient_frontier_plot_ex.py
├── nplot_ex.py
├── min_variance_returns_ex.py
└── event_ex.py
├── setup.py
├── CHANGES.txt
├── LICENSE
└── README.md
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/quantpy/gui/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/quantpy/fundamental_indicators.py:
--------------------------------------------------------------------------------
1 | def peg():
2 | pass
3 |
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | f2py
2 | numpy
3 | matplotlib
4 | pandas
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *pyc
2 | *swp
3 | docs/_build
4 | build
5 | .idea/
6 | .project
7 | .pydevproject
8 |
9 |
--------------------------------------------------------------------------------
/examples/efficient_frontier_plot_ex.py:
--------------------------------------------------------------------------------
1 | # Example plotting effienct frontier.
2 |
3 | import quantpy as qp
4 |
5 | # Grap portfolio
6 | P = qp.Portfolio(['GOOG','IBM','INTC','MSFT','AAPL'])
7 |
8 | # Plot effiecent frontier
9 | P.efficient_frontier_plot()
10 |
--------------------------------------------------------------------------------
/quantpy/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from quantpy.portfolio import Portfolio
4 | from quantpy.event_profiler import *
5 | from quantpy.fundamental_indicators import *
6 | from quantpy.technical_indicators import *
7 |
8 |
9 | if sys.version > '3':
10 | long = int
11 |
--------------------------------------------------------------------------------
/examples/nplot_ex.py:
--------------------------------------------------------------------------------
1 | # Example plotting returns.
2 | from pylab import *
3 | import quantpy as qp
4 |
5 | # Get symbols.
6 | syms = ['IBM','GOOG','MSFT','AAPL','INTC']
7 | cols = ['b','r','g','k','m','y']
8 |
9 | # Get portfolio
10 | P = qp.Portfolio(syms)
11 |
12 | # Make plots of normalized returns.
13 | for sym,col in zip(syms,cols):
14 | P.nplot(sym,col)
15 | show()
16 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 |
3 | setup(
4 | name='QuantPy',
5 | version='0.1',
6 | author='Joseph Smidt',
7 | author_email='josephsmidt@gmail.com',
8 | packages=['quantpy'],
9 | url='https://github.com/jsmidt/QuantPy',
10 | license='LICENSE',
11 | description='A framework for quantitative finance In python',
12 | long_description=open('README.md').read(),
13 | install_requires=[
14 | "pandas >= 0.10.0",
15 | "matplotlib >= 1.1.0",
16 | ],
17 | )
18 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. QuantPy documentation master file, created by
2 | sphinx-quickstart on Thu Mar 7 12:45:01 2013.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to QuantPy's documentation!
7 | ===================================
8 |
9 | Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 | getting_started.rst
15 |
16 | Indices and tables
17 | ==================
18 |
19 | * :ref:`genindex`
20 | * :ref:`modindex`
21 | * :ref:`search`
22 |
23 |
--------------------------------------------------------------------------------
/examples/min_variance_returns_ex.py:
--------------------------------------------------------------------------------
1 | from pylab import *
2 | import quantpy as qp
3 |
4 | # Get portfolio.
5 | P = qp.Portfolio(['IBM','GOOG','MSFT','AAPL','INTC'])
6 |
7 | # Calculate the returns buying 1 share of everything.
8 | bb = P.ret_for_w(ones(5))
9 | cumsum(bb).plot(color='r',label='Buy and Hold Equally.')
10 | mm = cumsum(bb)[-1]
11 |
12 | # Find the optimal weighting that yields the same return with minimum variance.
13 | w = P.min_var_w_ret(mm)
14 | aa = P.ret_for_w(w)
15 | cumsum(aa).plot(label='Same return but min variance.')
16 | legend(loc='best',shadow=True, fancybox=True)
17 | show()
18 |
19 |
20 |
--------------------------------------------------------------------------------
/quantpy/gui/data/app_menu.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
--------------------------------------------------------------------------------
/docs/_templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "!layout.html" %}
2 |
3 |
4 | {% block rootrellink %}
5 |
home|
6 | search|
7 | {% endblock %}
8 |
9 |
10 | {% block relbar1 %}
11 |
12 |
13 |

15 |
16 | {{ super() }}
17 | {% endblock %}
18 |
19 | {# put the sidebar before the body #}
20 | {% block sidebar1 %}{{ sidebar() }}{% endblock %}
21 | {% block sidebar2 %}{% endblock %}
22 |
23 |
--------------------------------------------------------------------------------
/examples/event_ex.py:
--------------------------------------------------------------------------------
1 | import quantpy as qp
2 | from numpy import *
3 |
4 |
5 | # Create your own truth function describing your even.
6 | # In this case the event is: whenever the price chanegs $1.
7 | def truth_function(asset):
8 | truth = zeros(len(asset))
9 | for i in range(1,len(asset)):
10 | if asset[i] - asset[i-1] > 1.0:
11 | truth[i] = 1
12 | return truth
13 |
14 |
15 | # Grab a profile
16 | P = qp.Portfolio(['GOOG','IBM','INTC'])
17 |
18 | # Define your asset you want to test.
19 | asset = P.asset['IBM']['Adj Close']
20 |
21 | # Generate your truth function.
22 | truth = truth_function(asset)
23 |
24 | # Get profiles for these events
25 | profiles = qp.event_profiler(asset,truth)
26 |
27 | # Plot them
28 | qp.plot_event_profile(profiles,name='When price increases $1.')
29 |
--------------------------------------------------------------------------------
/quantpy/event_profiler.py:
--------------------------------------------------------------------------------
1 | # This concept was inspired by QSTK:
2 | # http://wiki.quantsoftware.org/index.php?title=QuantSoftware_ToolKit.
3 | # However, no source code from that toolkit was used or consulted directly for
4 | # this code. (The concept only)
5 | from pylab import errorbar, xlabel, ylabel, show, legend
6 | from numpy import array, arange
7 |
8 |
9 | def event_profiler(asset, truth, periods=5):
10 | cut = []
11 | for i in range(periods, len(asset) - periods):
12 | if truth[i] == 1 and asset[i] > 0:
13 | cut.append(asset[i - periods:i + periods] / asset[i])
14 | return array(cut)
15 |
16 |
17 | def plot_event_profile(events, name=''):
18 | mn = events.mean(axis=0) - 1.0
19 | st = events.std(axis=0)
20 | errorbar(arange(len(mn)), mn, st, label=name)
21 | xlabel('Periods', fontsize=16)
22 | ylabel('Price Change %', fontsize=16)
23 | if len(name) > 1:
24 | legend(loc='best', shadow=True, fancybox=True)
25 | show()
26 |
--------------------------------------------------------------------------------
/CHANGES.txt:
--------------------------------------------------------------------------------
1 | # Changelog format:
2 | #
3 | # ** Release v **
4 | #
5 | # Release highlights:
6 | # * Highlight 1
7 | # * Highlight 2
8 | # * Etc...
9 | # Bugs fixed:
10 | # * Fix description 1 (Closes 1234)
11 | # * Fix description 2 (Closes 5678)
12 | # * Etc...
13 | # Miscellaneous notes and known issues:
14 | # * Etc...
15 | #
16 | # git-, , ,
17 | #
18 |
19 | ** Release v0.1 **
20 |
21 | Release Highlights:
22 | * Initial Release
23 | * Portfolio class that can import daily returns from Yahoo.
24 | * Calculation of optimal weights for Sharpe ratio and efficient frontier
25 | * Bare bones event profiler
26 | Bugs fixed:
27 | * Add distutils install like a normal python module enhancement. (Closes 1)
28 | Miscellaneous notes and known issues:
29 | * Please see the "issues" section on Github and jump right in.
30 |
31 | git-4da9cbb, Wed Mar 6 10:42:54, Joseph Smidt
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Joseph Smidt
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | 1. Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | 2. Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | 3. All advertising materials mentioning features or use of this software
12 | must display the following acknowledgement:
13 | This product includes software developed by the QuantPy developers.
14 | 4. Neither the name QuantPy nor the names of its contributors may be used
15 | to endorse or promote products derived from this software without
16 | specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY Joseph Smidt ''AS IS'' AND ANY EXPRESS OR IMPLIED
19 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
21 | EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
24 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 | POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/tests/unit/technical_indicators.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import numpy as np
4 | import quantpy as qp
5 | import pandas as pd
6 |
7 |
8 | class SmaTest(unittest.TestCase):
9 | def setUp(self):
10 | self.series = pd.Series((10, 11, 12, 13, 14, 15))
11 | self.data_frame = pd.DataFrame({
12 | 'IBM': pd.Series(
13 | [1., 2., 3.],
14 | index=['1/1/13', '2/1/13', '3/1/13']
15 | ),
16 | 'MSFT': pd.Series(
17 | [1., 1., 2., 8.],
18 | index=['1/1/13', '2/1/13', '3/1/13', '4/1/13']
19 | )
20 | })
21 |
22 | def tearDown(self):
23 | pass
24 |
25 | def test_wrong_window(self):
26 | self.assertRaises(ValueError, qp.sma, self.series, None)
27 | self.assertRaises(ValueError, qp.sma, self.series, '')
28 | self.assertRaises(ValueError, qp.sma, self.series, 'five')
29 | self.assertRaises(ValueError, qp.sma, self.series, -5)
30 | self.assertRaises(ValueError, qp.sma, self.series, -5.5)
31 | self.assertRaises(ValueError, qp.sma, self.series, 0)
32 | self.assertRaises(ValueError, qp.sma, self.series, 5.5)
33 |
34 | def test_wrong_data(self):
35 | import array
36 | self.assertRaises(ValueError, qp.sma, (1, 2, 3), 2)
37 | self.assertRaises(ValueError, qp.sma, [1, 2, 3], 2)
38 | self.assertRaises(ValueError, qp.sma, {1, 2, 3}, 2)
39 | self.assertRaises(ValueError, qp.sma, array.array('i', (1, 2, 3)), 2)
40 |
41 | def test_data(self):
42 | np.testing.assert_array_equal(
43 | pd.Series((np.NaN, np.NaN, 11.0, 12.0, 13.0, 14.0)),
44 | qp.sma(self.series, 3)
45 | )
46 |
47 | def test_data_frame(self):
48 | df = pd.DataFrame({
49 | 'IBM': pd.Series(
50 | [np.NaN, 1.5, 2.5, np.NaN],
51 | index=['1/1/13', '2/1/13', '3/1/13', '4/1/13']
52 | ),
53 | 'MSFT': pd.Series(
54 | [np.NaN, 1.0, 1.5, 5],
55 | index=['1/1/13', '2/1/13', '3/1/13', '4/1/13']
56 | )
57 | })
58 | np.testing.assert_array_equal(df, qp.sma(self.data_frame, 2))
59 |
60 | if __name__ == '__main__':
61 | unittest.main()
62 |
--------------------------------------------------------------------------------
/quantpy/gui/app.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | # 3rd party
5 | from gi.repository import Gio, Gtk
6 | from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as\
7 | FigureCanvas
8 | from matplotlib.figure import Figure
9 | import pandas as pd
10 | import pylab as pl
11 |
12 | import quantpy as qp
13 |
14 | DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
15 |
16 |
17 | class App(Gtk.Application):
18 | def __init__(self):
19 | Gtk.Application.__init__(self, application_id='apps.quantpy',
20 | flags=Gio.ApplicationFlags.FLAGS_NONE)
21 |
22 | def do_startup(self):
23 | Gtk.Application.do_startup(self)
24 |
25 | self.builder = Gtk.Builder()
26 | self.builder.add_from_file(os.path.join(DATA_DIR, 'app_menu.ui'))
27 | self.builder.add_from_file(os.path.join(DATA_DIR, "quantpy.ui"))
28 |
29 | self.builder.connect_signals(Handler(self))
30 |
31 | app_menu = self.builder.get_object('app_menu')
32 | self.set_app_menu(app_menu)
33 |
34 | # todo: look for a way to attach actions automatically, like handlers
35 | about_action = Gio.SimpleAction.new('about', None)
36 | about_action.connect('activate', self.onAppMenuAbout)
37 | self.add_action(about_action)
38 |
39 | quit_action = Gio.SimpleAction.new('quit', None)
40 | quit_action.connect('activate', self.onAppMenuQuit)
41 | self.add_action(quit_action)
42 |
43 | def do_activate(self):
44 | window = self.builder.get_object("window1")
45 | window.set_application(self)
46 |
47 | window.show_all()
48 |
49 | def onAppMenuAbout(self, action, parameter):
50 | self.builder.get_object('aboutdialog1').present()
51 |
52 | def onAppMenuQuit(self, action, parameter):
53 | self.quit()
54 |
55 |
56 | class Handler:
57 | def __init__(self, app):
58 | self.app = app
59 | self.symbol = self.app.builder.get_object('entry1')
60 |
61 | def onDeleteWindow(self, *args):
62 | self.app.quit()
63 |
64 | def onGoPressed(self, button):
65 | symbol = self.symbol.get_text()
66 | asset = pd.io.data.DataReader(symbol, 'yahoo')
67 |
68 | figure = Figure(figsize=(5, 4), dpi=100, frameon=False)
69 | subplot = figure.add_subplot(1, 1, 1)
70 | subplot.plot(asset.index, asset['Adj Close'])
71 | subplot.autoscale_view(True, True, True)
72 |
73 | canvas = FigureCanvas(figure)
74 | canvas.set_size_request(500, 250)
75 |
76 | sw = self.app.builder.get_object('scrolledwindow1')
77 | # remove old children
78 | for child in sw.get_children():
79 | sw.remove(child)
80 | sw.add(canvas)
81 | sw.show_all()
82 |
83 | def onAboutResponse(self, dialog, response_id):
84 | if response_id in (Gtk.ResponseType.CANCEL,
85 | Gtk.ResponseType.DELETE_EVENT):
86 | dialog.hide_on_delete()
87 |
88 | def onAboutDestroy(self, *args):
89 | # we don't want to destroy the about dialog
90 | return True
91 |
92 |
93 | def main():
94 | app = App()
95 | status = app.run(sys.argv)
96 | return status
97 |
98 |
99 | if __name__ == "__main__":
100 | sys.exit(main())
101 |
--------------------------------------------------------------------------------
/quantpy/gui/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
4 |
5 | # database file location
6 | DB_FILE = os.path.join(DATA_DIR, 'db.sqlite3')
7 |
8 | # indexes that are displayed on the dashboard
9 | DASHBOARD_INDEXES = {
10 | #symbol, short name, long name
11 | '^DJI': ('DJIA', 'Dow Jones Industrial Average'), # unfortunately, no data
12 | '^GSPC': ('S&P', 'S&P 500'),
13 | '^IXIC': ('NASDAQ', 'NASDAQ Composite'),
14 | 'eurusd=x': ('EUR/USD', 'EUR/USD'),
15 | '^TNX': ('10-Year Bond', 'CBOE Interest Rate 10-Year T-No'),
16 | 'gcq13.cmx': ('Gold', 'Gold Aug 13'),
17 | 'CLN13.NYM': ('Oil', 'Crude Oil Jul 13'),
18 | }
19 |
20 |
21 | # http://www.gummy-stuff.org/Yahoo-data.htm
22 | YAHOO_SYMBOL_TAGS = {
23 | "a": "Ask",
24 | "a2": "Average Daily Volume",
25 | "a5": "Ask Size",
26 | "b": "Bid",
27 | "b2": "Ask (Real-time)",
28 | "b3": "Bid (Real-time)",
29 | "b4": "Book Value",
30 | "b6": "Bid Size",
31 | "c": "Change & Percent Change",
32 | "c1": "Change",
33 | "c3": "Commission",
34 | "c6": "Change (Real-time)",
35 | "c8": "After Hours Change (Real-time)",
36 | "d": "Dividend/Share",
37 | "d1": "Last Trade Date",
38 | "d2": "Trade Date",
39 | "e": "Earnings/Share",
40 | "e1": "Error Indication (returned for symbol changed / invalid)",
41 | "e7": "EPS Estimate Current Year",
42 | "e8": "EPS Estimate Next Year",
43 | "e9": "EPS Estimate Next Quarter",
44 | "f6": "Float Shares",
45 | "g": "Day's Low",
46 | "g1": "Holdings Gain Percent",
47 | "g3": "Annualized Gain",
48 | "g4": "Holdings Gain",
49 | "g5": "Holdings Gain Percent (Real-time)",
50 | "g6": "Holdings Gain (Real-time)",
51 | "h": "Day's High",
52 | "i": "More Info",
53 | "i5": "Order Book (Real-time)",
54 | "j": "52-week Low",
55 | "j1": "Market Capitalization",
56 | "j3": "Market Cap (Real-time)",
57 | "j4": "EBITDA",
58 | "j5": "Change From 52-week Low",
59 | "j6": "Percent Change From 52-week Low",
60 | "k": "52-week High",
61 | "k1": "Last Trade (Real-time) With Time",
62 | "k2": "Change Percent (Real-time)",
63 | "k3": "Last Trade Size",
64 | "k4": "Change From 52-week High",
65 | "k5": "Percebt Change From 52-week High",
66 | "l": "Last Trade (With Time)",
67 | "l1": "Last Trade (Price Only)",
68 | "l2": "High Limit",
69 | "l3": "Low Limit",
70 | "m": "Day's Range",
71 | "m2": "Day's Range (Real-time)",
72 | "m3": "50-day Moving Average",
73 | "m4": "200-day Moving Average",
74 | "m5": "Change From 200-day Moving Average",
75 | "m6": "Percent Change From 200-day Moving Average",
76 | "m7": "Change From 50-day Moving Average",
77 | "m8": "Percent Change From 50-day Moving Average",
78 | "n": "Name",
79 | "n4": "Notes",
80 | "o": "Open",
81 | "p": "Previous Close",
82 | "p1": "Price Paid",
83 | "p2": "Change in Percent",
84 | "p5": "Price/Sales",
85 | "p6": "Price/Book",
86 | "q": "Ex-Dividend Date",
87 | "r": "P/E Ratio",
88 | "r1": "Dividend Pay Date",
89 | "r2": "P/E Ratio (Real-time)",
90 | "r5": "PEG Ratio",
91 | "r6": "Price/EPS Estimate Current Year",
92 | "r7": "Price/EPS Estimate Next Year",
93 | "s": "Symbol",
94 | "s1": "Shares Owned",
95 | "s7": "Short Ratio",
96 | "t1": "Last Trade Time",
97 | "t6": "Trade Links",
98 | "t7": "Ticker Trend",
99 | "t8": "1 yr Target Price",
100 | "v": "Volume",
101 | "v1": "Holdings Value",
102 | "v7": "Holdings Value (Real-time)",
103 | "w": "52-week Range",
104 | "w1": "Day's Value Change",
105 | "w4": "Day's Value Change (Real-time)",
106 | "x": "Stock Exchange",
107 | "y": "Dividend Yield"
108 | }
109 |
--------------------------------------------------------------------------------
/quantpy/gui/utils.py:
--------------------------------------------------------------------------------
1 | import xml.etree.ElementTree as ET
2 | import urllib.request
3 |
4 | import numpy as np
5 | import pandas as pd
6 |
7 | from quantpy.gui import settings
8 |
9 |
10 | def fetch_news(tickers, kind='company'):
11 | """Download company news headlines from yahoo
12 |
13 | 'tickers' is a comma separated list of tickers: yhoo,msft,tivo
14 | or just one ticker symbol
15 |
16 | 'kind' can either be 'company' or 'industry'. If it's 'industry' industry
17 | (that the company belongs to) news will be fetched. A ValueError is raised
18 | if kind if neither 'company' nor 'industry'.
19 | """
20 | if not tickers:
21 | return None
22 |
23 | if not kind in ('company', 'industry'):
24 | raise ValueError("'kind' must be one of 'company' or 'industry'.")
25 |
26 | if kind == 'company':
27 | url = 'http://finance.yahoo.com/rss/headline?s=%s' % tickers
28 | else:
29 | url = 'http://finance.yahoo.com/rss/industry?s=%s' % tickers
30 |
31 | feed = urllib.request.urlopen(url)
32 |
33 | tree = ET.parse(feed)
34 | root = tree.getroot()
35 |
36 | news = []
37 | for item in root.iter('item'):
38 | try:
39 | news.append({
40 | 'description': item.find('description').text,
41 | 'link': item.find('link').text,
42 | 'pub_date': item.find('pubDate').text,
43 | 'title': item.find('title').text,
44 | })
45 | except AttributeError:
46 | pass
47 |
48 | return news
49 |
50 |
51 | def get_market_updates(symbols, special_tags):
52 | """
53 | Get current yahoo quote.
54 |
55 | 'special_tags' is a list of tags. More info about tags can be found at
56 | http://www.gummy-stuff.org/Yahoo-data.htm
57 |
58 | Returns a DataFrame
59 | """
60 | if isinstance(symbols, str):
61 | sym_list = symbols
62 | elif not isinstance(symbols, pd.Series):
63 | symbols = pd.Series(symbols)
64 | sym_list = str.join('+', symbols)
65 | else:
66 | sym_list = str.join('+', symbols)
67 |
68 | # Symbol must be in the special_tags for now
69 | if not 's' in special_tags:
70 | special_tags.insert(0, 's')
71 | request = ''.join(special_tags) # code request string
72 | special_tag_names = [settings.YAHOO_SYMBOL_TAGS[x] for x in special_tags]
73 | header = special_tag_names
74 |
75 | data = dict(list(zip(
76 | list(special_tag_names), [[] for i in range(len(special_tags))]
77 | )))
78 |
79 | urlStr = 'http://finance.yahoo.com/d/quotes.csv?s=%s&f=%s' % (
80 | sym_list, request)
81 |
82 | try:
83 | lines = urllib.request.urlopen(urlStr).readlines()
84 | except Exception as e:
85 | s = "Failed to download:\n{0}".format(e)
86 | print(s)
87 | return None
88 |
89 | for line in lines:
90 | fields = line.decode('utf-8').strip().split(',')
91 | for i, field in enumerate(fields):
92 | if field[-2:] == '%"':
93 | data[header[i]].append(float(field.strip('"%')))
94 | elif field[0] == '"':
95 | data[header[i]].append(field.strip('"'))
96 | else:
97 | try:
98 | data[header[i]].append(float(field))
99 | except ValueError:
100 | data[header[i]].append(np.nan)
101 |
102 | idx = data.pop('Symbol')
103 |
104 | return pd.DataFrame(data, index=idx)
105 |
106 |
107 | def get_dashboard_index_updates():
108 | """Fetch updates for assets in the settings.DASHBOARD_INDEXES
109 | Return a pandas data frame.
110 | """
111 | symbols = [x for x in settings.DASHBOARD_INDEXES.keys()]
112 | special_tags = ['s', 'c6', 'd1', 'l1', 'p2'] # settings.YAHOO_SYMBOL_TAGS
113 | return get_market_updates(symbols, special_tags)
114 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | QuantPy
2 | ==========
3 |
4 | A framework for quantitative finance in python.
5 |
6 | **Disclaimer:** This is a *very* alpha project. It is not ready to be used and
7 | won't be for a while. In fact, the author is still very much learning what
8 | such a framework needs to entail. However, if you consider yourself a helpful
9 | soul contributions of any type are more then welcome. Thanks!
10 |
11 |
12 | Some current capabilities:
13 | * Portfolio class that can import daily returns from Yahoo.
14 | * Calculation of optimal weights for Sharpe ratio and efficient frontier
15 | * Bare bones event profiler
16 |
17 | Documentation:
18 | --------------
19 |
20 | The main documentation can be read at [Read The Docs](https://quantpy.readthedocs.org/en/latest/). Please start their for more information.
21 |
22 | Contributions Welcome.
23 | ----------------------
24 |
25 | Any and all contributions for the project are welcome whether they be feature
26 | requests, bug reports, contributions to documentation, or patches for new
27 | features, bug fixes of other improvements. Just [fork the
28 | repo](https://help.github.com/articles/fork-a-repo), add some content and [make
29 | a pull request] (https://help.github.com/articles/be-social). If you are new
30 | to Git [this tutorial](http://learn.github.com/p/intro.html) is nice for further
31 | details.
32 |
33 | Also, just downloading the code and providing feedback is also extremely
34 | useful. Submit your feedback to the [issues page
35 | here](https://github.com/jsmidt/QuantPy/issues?state=open). Thanks in advance.
36 |
37 | You may also join us at #quantpy on irc.freenode.net.
38 |
39 | How To Install.
40 | ---------------
41 |
42 | QuantPy may be downloaded from GitHub as::
43 |
44 | > git clone https://github.com/jsmidt/QuantPy.git
45 |
46 | To install QuantPy type::
47 |
48 | > cd QuantPy
49 | > python setup.py install
50 |
51 | The prerequisites for Quantpy are:
52 |
53 | * pandas (> 0.10.0)
54 | * matplotlib (> 0.1.0)
55 | * PyGObject (> 3.8.2, only needed for GUI)
56 |
57 | Why Python?
58 | -----------
59 |
60 | Python is popular, easy to use, cross-platform, contains many helpful
61 | numerical, statistical and visualization libraries and in reality can be made
62 | as fast as C/C++ through Cython extensions. I know of no other language that
63 | meets *all* of these requirements.
64 |
65 | Why BSD license?
66 | ----------------
67 |
68 | It is a desire that QuantPy is as useful as possible, including for those who
69 | want to incorporate QuantPy into their proprietary software. The BSD license is
70 | an open source license that permits this. Please see the attached LICENSE file
71 | and http://www.linfo.org/bsdlicense.html for more information which states "Due
72 | to the extremely minimal restrictions of BSD-style licenses, software released
73 | under such licenses can be freely modified and used in proprietary (i.e.,
74 | commercial) software for which the source code is kept secret."
75 |
76 | Why Git and GitHub?
77 | -------------------
78 |
79 | There are a few great distributed revision control systems. Git was chosen for
80 | the simple reason that git was designed with the ability to create many
81 | anonymous untracked branches where code can be pushed and pulled from without
82 | revealing the anonymous history. We feel this design choice is important for
83 | entities with proprietary code who want to make contributions but keep their
84 | branches anonymous. Github was chosen because it seems to give the most user
85 | friendly git experience across all platforms: Windows, Mac and Linux.
86 |
87 |
88 | Is Non-Commercial/Proprietary Use allowed?
89 | ------------------------------------------
90 |
91 | Yes. Though this is an open source project, it was understood from day one
92 | that there may be a need to incorporate QuantPy into proprietary software. The
93 | above sections regarding the BSD licence and the use of Git discuss how we
94 | have addressed these concerns. We hope, however, entities repay the generosity
95 | by submitting patches for new features, bug fixes, and other improvements.
96 |
97 |
98 | With that said, I hope you very much enjoy Quantpy. I hope it meets your
99 | needs, makes you happy and that your retrun the favor through the types of
100 | contributions mentioned above.
101 |
--------------------------------------------------------------------------------
/docs/getting_started.rst:
--------------------------------------------------------------------------------
1 | .. _getting_started:
2 |
3 |
4 | ***************
5 | Getting started
6 | ***************
7 |
8 | Welcome to QuantPy.
9 |
10 | **Disclaimer:** This is a *very* alpha project. It is not ready to be used and
11 | won't be for a while. In fact, the author is still very much learning what
12 | such a framework needs to entail. However, if you consider yourself a helpful
13 | soul contributions of any type are more then welcome. Thanks!
14 |
15 |
16 | Some current capabilities:
17 | * Portfolio class that can import daily returns from Yahoo.
18 | * Calculation of optimal weights for Sharpe ratio and efficient frontier
19 | * Bare bones event profiler
20 |
21 | Contributions Welcome.
22 | =========================
23 |
24 | Any and all contributions for the project are welcome whether they be feature
25 | requests, bug reports, contributions to documentation, or patches for new
26 | features, bug fixes of other improvements. Just `fork the repo
27 | `_, add some content and `make a
28 | pull request `_. If you are new to
29 | Git `this tutorial `_ is nice for
30 | further details.
31 |
32 |
33 | Also, just downloading the code and providing feedback is also extremely
34 | useful. Submit your feedback to the `issues page here
35 | `_. Thanks in advance.
36 |
37 |
38 | .. _installing-docdir:
39 |
40 | Installing QuantPy
41 | =============================
42 |
43 | QuantPy may be downloaded from GitHub as::
44 |
45 | > git clone https://github.com/jsmidt/QuantPy.git
46 |
47 | To install QuantPy type::
48 |
49 | > cd QuantPy
50 | > python setup.py install
51 |
52 | The prerequisites for Quantpy are:
53 |
54 | * pandas (> 0.10.0)
55 | * matplotlib (> 0.1.0)
56 |
57 |
58 | .. _example-scripts-highlighting-functionality:
59 |
60 | Example Scripts Highlighting Functionality
61 | ==============================================
62 |
63 | Import any portfolio and plot the returns.
64 | ----------------------------------------------
65 |
66 | The example script imports a portfolio of stocks and plots the normalized retuns:
67 |
68 | .. plot:: ../examples/nplot_ex.py
69 | :include-source:
70 |
71 | Calculate the weighting that gives minimum variance.
72 | ------------------------------------------------------
73 |
74 | QuantPy can tell you the portfolio weighting that will give you the minimum
75 | variance. (Max sharpe ratio) This is illustrated in the
76 | min_variance_returns_ex.py script where you compare how your portfolio would
77 | have changed if you bought an equal number of all stocks in the portfolio
78 | versus used optimal sharpe ratio weighting for the same return.
79 |
80 | .. plot:: ../examples/min_variance_returns_ex.py
81 | :include-source:
82 |
83 | Plot the Efficient Frontier.
84 | -------------------------------
85 |
86 | We can plot the entire efficient frontier as done in
87 | efficient_frontier_plot_ex.py. This is defined as followed: you tell me what
88 | returns you want and this will tell you the weighting that will generate those
89 | returns with minimum variance. The relation or risk to return for such a
90 | weighting is plotted in efficient_frontier.png. The line showing the optimal
91 | return for risk (optimal share ratio) is also plotted.
92 |
93 | .. plot:: ../examples/efficient_frontier_plot_ex.py
94 | :include-source:
95 |
96 | Using an Event Profiler
97 | -------------------------
98 |
99 | Event profiler allows you to track what happens to a stock price after
100 | historical events. Tell me any event, like an EMA cross, and this will plot
101 | how the price historically has changed with such an event with error bars.
102 | This helps us find statistically meaningful events.
103 |
104 | Below is a bare bones version of this demonstrated by running event_ex.py. It
105 | just asks what happens to the price after it goes up at least $1. As the plot
106 | event_ex.png shows, nothing meaningful. Notice you must create your own "truth function" describing your event.
107 |
108 | .. plot:: ../examples/event_ex.py
109 | :include-source:
110 |
111 | Thanks Again!
112 | =================
113 |
114 | We want to thank you for trying out QuantPy. Any contributions are again very appreciated.
115 |
116 |
--------------------------------------------------------------------------------
/docs/sphinxext/numpydoc.py:
--------------------------------------------------------------------------------
1 | """
2 | ========
3 | numpydoc
4 | ========
5 |
6 | Sphinx extension that handles docstrings in the Numpy standard format. [1]
7 |
8 | It will:
9 |
10 | - Convert Parameters etc. sections to field lists.
11 | - Convert See Also section to a See also entry.
12 | - Renumber references.
13 | - Extract the signature from the docstring, if it can't be determined otherwise.
14 |
15 | .. [1] http://projects.scipy.org/scipy/numpy/wiki/CodingStyleGuidelines#docstring-standard
16 |
17 | """
18 |
19 | import os, re, pydoc
20 | from docscrape_sphinx import get_doc_object, SphinxDocString
21 | import inspect
22 |
23 | def mangle_docstrings(app, what, name, obj, options, lines,
24 | reference_offset=[0]):
25 | if what == 'module':
26 | # Strip top title
27 | title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*',
28 | re.I|re.S)
29 | lines[:] = title_re.sub('', "\n".join(lines)).split("\n")
30 | else:
31 | doc = get_doc_object(obj, what, "\n".join(lines))
32 | lines[:] = str(doc).split("\n")
33 |
34 | if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \
35 | obj.__name__:
36 | if hasattr(obj, '__module__'):
37 | v = dict(full_name="%s.%s" % (obj.__module__, obj.__name__))
38 | else:
39 | v = dict(full_name=obj.__name__)
40 | lines += ['', '.. htmlonly::', '']
41 | lines += [' %s' % x for x in
42 | (app.config.numpydoc_edit_link % v).split("\n")]
43 |
44 | # replace reference numbers so that there are no duplicates
45 | references = []
46 | for l in lines:
47 | l = l.strip()
48 | if l.startswith('.. ['):
49 | try:
50 | references.append(int(l[len('.. ['):l.index(']')]))
51 | except ValueError:
52 | print "WARNING: invalid reference in %s docstring" % name
53 |
54 | # Start renaming from the biggest number, otherwise we may
55 | # overwrite references.
56 | references.sort()
57 | if references:
58 | for i, line in enumerate(lines):
59 | for r in references:
60 | new_r = reference_offset[0] + r
61 | lines[i] = lines[i].replace('[%d]_' % r,
62 | '[%d]_' % new_r)
63 | lines[i] = lines[i].replace('.. [%d]' % r,
64 | '.. [%d]' % new_r)
65 |
66 | reference_offset[0] += len(references)
67 |
68 | def mangle_signature(app, what, name, obj, options, sig, retann):
69 | # Do not try to inspect classes that don't define `__init__`
70 | if (inspect.isclass(obj) and
71 | 'initializes x; see ' in pydoc.getdoc(obj.__init__)):
72 | return '', ''
73 |
74 | if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): return
75 | if not hasattr(obj, '__doc__'): return
76 |
77 | doc = SphinxDocString(pydoc.getdoc(obj))
78 | if doc['Signature']:
79 | sig = re.sub("^[^(]*", "", doc['Signature'])
80 | return sig, ''
81 |
82 | def initialize(app):
83 | try:
84 | app.connect('autodoc-process-signature', mangle_signature)
85 | except:
86 | monkeypatch_sphinx_ext_autodoc()
87 |
88 | def setup(app, get_doc_object_=get_doc_object):
89 | global get_doc_object
90 | get_doc_object = get_doc_object_
91 |
92 | app.connect('autodoc-process-docstring', mangle_docstrings)
93 | app.connect('builder-inited', initialize)
94 | app.add_config_value('numpydoc_edit_link', None, True)
95 |
96 | #------------------------------------------------------------------------------
97 | # Monkeypatch sphinx.ext.autodoc to accept argspecless autodocs (Sphinx < 0.5)
98 | #------------------------------------------------------------------------------
99 |
100 | def monkeypatch_sphinx_ext_autodoc():
101 | global _original_format_signature
102 | import sphinx.ext.autodoc
103 |
104 | if sphinx.ext.autodoc.format_signature is our_format_signature:
105 | return
106 |
107 | print "[numpydoc] Monkeypatching sphinx.ext.autodoc ..."
108 | _original_format_signature = sphinx.ext.autodoc.format_signature
109 | sphinx.ext.autodoc.format_signature = our_format_signature
110 |
111 | def our_format_signature(what, obj):
112 | r = mangle_signature(None, what, None, obj, None, None, None)
113 | if r is not None:
114 | return r[0]
115 | else:
116 | return _original_format_signature(what, obj)
117 |
--------------------------------------------------------------------------------
/docs/sphinxext/ipython_console_highlighting.py:
--------------------------------------------------------------------------------
1 | """reST directive for syntax-highlighting ipython interactive sessions.
2 |
3 | XXX - See what improvements can be made based on the new (as of Sept 2009)
4 | 'pycon' lexer for the python console. At the very least it will give better
5 | highlighted tracebacks.
6 | """
7 |
8 | #-----------------------------------------------------------------------------
9 | # Needed modules
10 |
11 | # Standard library
12 | import re
13 |
14 | # Third party
15 | from pygments.lexer import Lexer, do_insertions
16 | from pygments.lexers.agile import (PythonConsoleLexer, PythonLexer,
17 | PythonTracebackLexer)
18 | from pygments.token import Comment, Generic
19 |
20 | from sphinx import highlighting
21 |
22 | #-----------------------------------------------------------------------------
23 | # Global constants
24 | line_re = re.compile('.*?\n')
25 |
26 | #-----------------------------------------------------------------------------
27 | # Code begins - classes and functions
28 |
29 | class IPythonConsoleLexer(Lexer):
30 | """
31 | For IPython console output or doctests, such as:
32 |
33 | .. sourcecode:: ipython
34 |
35 | In [1]: a = 'foo'
36 |
37 | In [2]: a
38 | Out[2]: 'foo'
39 |
40 | In [3]: print a
41 | foo
42 |
43 | In [4]: 1 / 0
44 |
45 | Notes:
46 |
47 | - Tracebacks are not currently supported.
48 |
49 | - It assumes the default IPython prompts, not customized ones.
50 | """
51 |
52 | name = 'IPython console session'
53 | aliases = ['ipython']
54 | mimetypes = ['text/x-ipython-console']
55 | input_prompt = re.compile("(In \[[0-9]+\]: )|( \.\.\.+:)")
56 | output_prompt = re.compile("(Out\[[0-9]+\]: )|( \.\.\.+:)")
57 | continue_prompt = re.compile(" \.\.\.+:")
58 | tb_start = re.compile("\-+")
59 |
60 | def get_tokens_unprocessed(self, text):
61 | pylexer = PythonLexer(**self.options)
62 | tblexer = PythonTracebackLexer(**self.options)
63 |
64 | curcode = ''
65 | insertions = []
66 | for match in line_re.finditer(text):
67 | line = match.group()
68 | input_prompt = self.input_prompt.match(line)
69 | continue_prompt = self.continue_prompt.match(line.rstrip())
70 | output_prompt = self.output_prompt.match(line)
71 | if line.startswith("#"):
72 | insertions.append((len(curcode),
73 | [(0, Comment, line)]))
74 | elif input_prompt is not None:
75 | insertions.append((len(curcode),
76 | [(0, Generic.Prompt, input_prompt.group())]))
77 | curcode += line[input_prompt.end():]
78 | elif continue_prompt is not None:
79 | insertions.append((len(curcode),
80 | [(0, Generic.Prompt, continue_prompt.group())]))
81 | curcode += line[continue_prompt.end():]
82 | elif output_prompt is not None:
83 | # Use the 'error' token for output. We should probably make
84 | # our own token, but error is typicaly in a bright color like
85 | # red, so it works fine for our output prompts.
86 | insertions.append((len(curcode),
87 | [(0, Generic.Error, output_prompt.group())]))
88 | curcode += line[output_prompt.end():]
89 | else:
90 | if curcode:
91 | for item in do_insertions(insertions,
92 | pylexer.get_tokens_unprocessed(curcode)):
93 | yield item
94 | curcode = ''
95 | insertions = []
96 | yield match.start(), Generic.Output, line
97 | if curcode:
98 | for item in do_insertions(insertions,
99 | pylexer.get_tokens_unprocessed(curcode)):
100 | yield item
101 |
102 |
103 | def setup(app):
104 | """Setup as a sphinx extension."""
105 |
106 | # This is only a lexer, so adding it below to pygments appears sufficient.
107 | # But if somebody knows that the right API usage should be to do that via
108 | # sphinx, by all means fix it here. At least having this setup.py
109 | # suppresses the sphinx warning we'd get without it.
110 | pass
111 |
112 | #-----------------------------------------------------------------------------
113 | # Register the extension as a valid pygments lexer
114 | highlighting.lexers['ipython'] = IPythonConsoleLexer()
115 |
--------------------------------------------------------------------------------
/docs/sphinxext/docscrape_sphinx.py:
--------------------------------------------------------------------------------
1 | import re, inspect, textwrap, pydoc
2 | from docscrape import NumpyDocString, FunctionDoc, ClassDoc
3 |
4 | class SphinxDocString(NumpyDocString):
5 | # string conversion routines
6 | def _str_header(self, name, symbol='`'):
7 | return ['.. rubric:: ' + name, '']
8 |
9 | def _str_field_list(self, name):
10 | return [':' + name + ':']
11 |
12 | def _str_indent(self, doc, indent=4):
13 | out = []
14 | for line in doc:
15 | out += [' '*indent + line]
16 | return out
17 |
18 | def _str_signature(self):
19 | return ['']
20 | if self['Signature']:
21 | return ['``%s``' % self['Signature']] + ['']
22 | else:
23 | return ['']
24 |
25 | def _str_summary(self):
26 | return self['Summary'] + ['']
27 |
28 | def _str_extended_summary(self):
29 | return self['Extended Summary'] + ['']
30 |
31 | def _str_param_list(self, name):
32 | out = []
33 | if self[name]:
34 | out += self._str_field_list(name)
35 | out += ['']
36 | for param,param_type,desc in self[name]:
37 | out += self._str_indent(['**%s** : %s' % (param.strip(),
38 | param_type)])
39 | out += ['']
40 | out += self._str_indent(desc,8)
41 | out += ['']
42 | return out
43 |
44 | def _str_section(self, name):
45 | out = []
46 | if self[name]:
47 | out += self._str_header(name)
48 | out += ['']
49 | content = textwrap.dedent("\n".join(self[name])).split("\n")
50 | out += content
51 | out += ['']
52 | return out
53 |
54 | def _str_see_also(self, func_role):
55 | out = []
56 | if self['See Also']:
57 | see_also = super(SphinxDocString, self)._str_see_also(func_role)
58 | out = ['.. seealso::', '']
59 | out += self._str_indent(see_also[2:])
60 | return out
61 |
62 | def _str_warnings(self):
63 | out = []
64 | if self['Warnings']:
65 | out = ['.. warning::', '']
66 | out += self._str_indent(self['Warnings'])
67 | return out
68 |
69 | def _str_index(self):
70 | idx = self['index']
71 | out = []
72 | if len(idx) == 0:
73 | return out
74 |
75 | out += ['.. index:: %s' % idx.get('default','')]
76 | for section, references in idx.iteritems():
77 | if section == 'default':
78 | continue
79 | elif section == 'refguide':
80 | out += [' single: %s' % (', '.join(references))]
81 | else:
82 | out += [' %s: %s' % (section, ','.join(references))]
83 | return out
84 |
85 | def _str_references(self):
86 | out = []
87 | if self['References']:
88 | out += self._str_header('References')
89 | if isinstance(self['References'], str):
90 | self['References'] = [self['References']]
91 | out.extend(self['References'])
92 | out += ['']
93 | return out
94 |
95 | def __str__(self, indent=0, func_role="obj"):
96 | out = []
97 | out += self._str_signature()
98 | out += self._str_index() + ['']
99 | out += self._str_summary()
100 | out += self._str_extended_summary()
101 | for param_list in ('Parameters', 'Attributes', 'Methods',
102 | 'Returns','Raises'):
103 | out += self._str_param_list(param_list)
104 | out += self._str_warnings()
105 | out += self._str_see_also(func_role)
106 | out += self._str_section('Notes')
107 | out += self._str_references()
108 | out += self._str_section('Examples')
109 | out = self._str_indent(out,indent)
110 | return '\n'.join(out)
111 |
112 | class SphinxFunctionDoc(SphinxDocString, FunctionDoc):
113 | pass
114 |
115 | class SphinxClassDoc(SphinxDocString, ClassDoc):
116 | pass
117 |
118 | def get_doc_object(obj, what=None, doc=None):
119 | if what is None:
120 | if inspect.isclass(obj):
121 | what = 'class'
122 | elif inspect.ismodule(obj):
123 | what = 'module'
124 | elif callable(obj):
125 | what = 'function'
126 | else:
127 | what = 'object'
128 | if what == 'class':
129 | return SphinxClassDoc(obj, '', func_doc=SphinxFunctionDoc, doc=doc)
130 | elif what in ('function', 'method'):
131 | return SphinxFunctionDoc(obj, '', doc=doc)
132 | else:
133 | if doc is None:
134 | doc = pydoc.getdoc(obj)
135 | return SphinxDocString(doc)
136 |
137 |
--------------------------------------------------------------------------------
/quantpy/gui/data/quantpy.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
88 |
89 | False
90 | 5
91 | True
92 | center-on-parent
93 | dialog
94 | window1
95 | QuantPy
96 | https://github.com/jsmidt/QuantPy
97 |
98 |
99 |
100 |
101 | False
102 | vertical
103 | 2
104 |
105 |
106 | False
107 | end
108 |
109 |
110 | False
111 | True
112 | end
113 | 0
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/quantpy/portfolio.py:
--------------------------------------------------------------------------------
1 | from pandas.io.data import DataReader
2 | from pylab import legend, xlabel, ylabel, sqrt, ylim,\
3 | cov, sqrt, mean, std, plot, show, figure
4 | from numpy import array, zeros, matrix, ones, shape, linspace, hstack
5 | from pandas import Series, DataFrame
6 | from numpy.linalg import inv
7 |
8 |
9 | # Portfolio class.
10 | class Portfolio:
11 | def __init__(self, symbols, start=None, end=None, bench='^GSPC'):
12 |
13 | # Make sure input is a list
14 | if type(symbols) != list:
15 | symbols = [symbols]
16 |
17 | # Create distionary to hold assets.
18 | self.asset = {}
19 |
20 | # Retrieve assets from data source (IE. Yahoo)
21 | for symbol in symbols:
22 | try:
23 | self.asset[symbol] = DataReader(
24 | symbol, "yahoo", start=start, end=end)
25 | except:
26 | print("Asset " + str(symbol) + " not found!")
27 |
28 | # Get Benchmark asset.
29 | self.benchmark = DataReader(bench, "yahoo", start=start, end=end)
30 | self.benchmark['Return'] = self.benchmark['Adj Close'].diff()
31 |
32 | # Get returns, beta, alpha, and sharp ratio.
33 | for symbol in symbols:
34 | # Get returns.
35 | self.asset[symbol]['Return'] = \
36 | self.asset[symbol]['Adj Close'].diff()
37 | # Get Beta.
38 | A = self.asset[symbol]['Return'].fillna(0)
39 | B = self.benchmark['Return'].fillna(0)
40 | self.asset[symbol]['Beta'] = cov(A, B)[0, 1] / cov(A, B)[1, 1]
41 | # Get Alpha
42 | self.asset[symbol]['Alpha'] = self.asset[symbol]['Return'] - \
43 | self.asset[symbol]['Beta'] * self.benchmark['Return']
44 |
45 | # Get Sharpe Ratio
46 | tmp = self.asset[symbol]['Return']
47 | self.asset[symbol]['Sharpe'] = \
48 | sqrt(len(tmp)) * mean(tmp.fillna(0)) / std(tmp.fillna(0))
49 |
50 | def nplot(self, symbol, color='b', nval=0):
51 | tmp = (self.benchmark if symbol == 'bench' else self.asset[symbol]) ['Adj Close']
52 | tmp = tmp / tmp[nval]
53 | tmp.plot(color=color, label=symbol)
54 | legend(loc='best', shadow=True, fancybox=True)
55 |
56 | def betas(self):
57 | betas = [v['Beta'][0] for k, v in self.asset.items()]
58 | return Series(betas, index=self.asset.keys())
59 |
60 | def returns(self):
61 | returns = [v['Return'].dropna() for k, v in self.asset.items()]
62 | return Series(returns, index=self.asset.keys())
63 |
64 | def cov(self):
65 | keys, values = self.returns().keys(), self.returns().values()
66 | return DataFrame(
67 | cov(array(values)), index=keys, columns=keys)
68 |
69 | def get_w(self, kind='sharpe'):
70 | V = self.cov()
71 | iV = matrix(inv(V))
72 |
73 | if kind == 'characteristic':
74 | e = matrix(ones(len(self.asset.keys()))).T
75 | elif kind == 'sharpe':
76 | suml = [ self.returns()[symbol].sum() for symbol in self.asset.keys()]
77 | e = matrix(suml).T
78 | else:
79 | print('\n *Error: There is no weighting for kind ' + kind)
80 | return
81 |
82 | num = iV * e
83 | denom = e.T * iV * e
84 | w = array(num / denom).flatten()
85 | return Series(w, index=self.asset.keys())
86 |
87 | def efficient_frontier_w(self, fp):
88 | wc = self.get_w(kind='characteristic')
89 | wq = self.get_w(kind='sharpe')
90 |
91 | fc = self.ret_for_w(wc).sum()
92 | fq = self.ret_for_w(wq).sum()
93 |
94 | denom = fq - fc
95 | w = (fq - fp) * wc + (fp - fc) * wq
96 | return Series(w / denom, index=self.asset.keys())
97 |
98 | def efficient_frontier(self, xi=0.01, xf=4, npts=100, scale=10):
99 | frontier = linspace(xi, xf, npts)
100 |
101 | i = 0
102 | rets = zeros(len(frontier))
103 | sharpe = zeros(len(frontier))
104 | for f in frontier:
105 | w = self.efficient_frontier_w(f)
106 | tmp = self.ret_for_w(w)
107 | rets[i] = tmp.sum() * scale
108 | sharpe[i] = mean(tmp) / std(tmp) * sqrt(len(tmp))
109 | i += 1
110 | risk = rets/sharpe
111 | return Series(rets, index=risk), sharpe.max()
112 |
113 | def efficient_frontier_plot(self, xi=0.01, xf=4, npts=100, scale=0.1,
114 | col1='b', col2='r', newfig=1, plabel=''):
115 | eff, m = self.efficient_frontier()
116 |
117 | if newfig == 1:
118 | figure()
119 |
120 | plot(array(eff.index), array(eff), col1, linewidth=2,
121 | label="Efficient Frontier " + plabel)
122 | tmp = zeros(1)
123 | plot(hstack((tmp, array(eff.index))),
124 | hstack((0, m * array(eff.index))),
125 | col2, linewidth=2, label="Max Sharpe Ratio: %6.2g" % m)
126 | legend(loc='best', shadow=True, fancybox=True)
127 | xlabel('Risk %', fontsize=16)
128 | ylabel('Return %', fontsize=16)
129 | show()
130 |
131 | def min_var_w_ret(self, ret):
132 | V = self.cov()
133 | suml = [self.returns()[symbol].sum() for symbol in self.asset.keys()]
134 | e = matrix(suml).T
135 | iV = matrix(inv(V))
136 | num = iV * e * ret
137 | denom = e.T * iV * e
138 | return Series(array(num / denom).flatten(), index=self.asset.keys())
139 |
140 | def ret_for_w(self, w):
141 | tmp = self.returns()
142 | tmpl = [v * wi for v, wi in zip(tmp.values(), w) ]
143 | return Series(tmpl, index=tmp.keys()).sum()
144 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\QuantPy.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\QuantPy.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/QuantPy.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/QuantPy.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/QuantPy"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/QuantPy"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/quantpy/technical_indicators.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pandas as pd
3 |
4 |
5 | def sma(data, window):
6 | """Simple Moving Average: The average price of a security over a
7 | specific number of periods.
8 |
9 | 'data' is a pandas Series or DataFrame of prices. A ValueError is raised
10 | if 'data' is of different data type.
11 |
12 | 'window' is the number of observations.
13 | It must be a positive integer less than or equal to the length of the data.
14 | Otherwise a ValueError will be raised.
15 | """
16 |
17 | # todo: maybe add 'long' too?
18 | if not isinstance(window, int) or not 0 < window <= len(data):
19 | raise ValueError("'window' must be an integer " +
20 | "between 1 and %d." % len(data))
21 |
22 | if not isinstance(data, (pd.Series, pd.DataFrame)):
23 | raise ValueError("'data' must be a pandas Series or DataFrame.")
24 |
25 | return pd.rolling_mean(data, window)
26 |
27 |
28 | def smstd(data, window):
29 | """Simple Moving Standard Deviation: The standard deviation price of a
30 | security over a specific number of periods.
31 |
32 | 'data' is a pandas Series or DataFrame of prices. A ValueError is raised
33 | if 'data' is of different data type.
34 |
35 | 'window' is the number of observations.
36 | It must be a positive integer less than or equal to the length of the data.
37 | Otherwise a ValueError will be raised.
38 | """
39 |
40 | # todo: maybe add 'long' too?
41 | if not isinstance(window, int) or not 0 < window <= len(data):
42 | raise ValueError("'window' must be an integer " +
43 | "between 1 and %d." % len(data))
44 |
45 | if not isinstance(data, (pd.Series, pd.DataFrame)):
46 | raise ValueError("'data' must be a pandas Series or DataFrame.")
47 |
48 | return pd.rolling_std(data, window)
49 |
50 |
51 | def ema(data, window):
52 | """Exponential Moving Average: The exponentially weighted average price of
53 | a security over a specific number of periods.
54 |
55 | 'data' is a pandas Series or DataFrame of prices. A ValueError is raised
56 | if 'data' is of different data type.
57 |
58 | 'window' is the number of observations.
59 | It must be a positive integer less than or equal to the length of the data.
60 | Otherwise a ValueError will be raised.
61 | """
62 |
63 | # todo: maybe add 'long' too?
64 | if not isinstance(window, int) or not 0 < window <= len(data):
65 | raise ValueError("'window' must be an integer " +
66 | "between 1 and %d." % len(data))
67 |
68 | if not isinstance(data, (pd.Series, pd.DataFrame)):
69 | raise ValueError("'data' must be a pandas Series or DataFrame.")
70 |
71 | return pd.ewma(data, span=window)
72 |
73 |
74 | def emstd(data, window):
75 | """Exponential Moving Standard Deviation: The exponentially weighted
76 | standard deviation of the price of a security over a specific number of
77 | periods.
78 |
79 | 'data' is a pandas Series or DataFrame of prices. A ValueError is raised
80 | if 'data' is of different data type.
81 |
82 | 'window' is the number of observations.
83 | It must be a positive integer less than or equal to the length of the data.
84 | Otherwise a ValueError will be raised.
85 | """
86 |
87 | # todo: maybe add 'long' too?
88 | if not isinstance(window, int) or not 0 < window <= len(data):
89 | raise ValueError("'window' must be an integer " +
90 | "between 1 and %d." % len(data))
91 |
92 | if not isinstance(data, (pd.Series, pd.DataFrame)):
93 | raise ValueError("'data' must be a pandas Series or DataFrame.")
94 |
95 | return pd.ewmstd(data, span = window)
96 |
97 |
98 | def macd(data, ema_fast=12, ema_slow=26, ema_macd=9):
99 | """Moving Average Convergence/Divergence.
100 |
101 | Parameters:
102 |
103 | 'data' is a pandas Series or DataFrame of prices. A ValueError is
104 | raised if 'data' is of different data type.
105 |
106 | 'ema_fast' The window period of the "fast" EMA. (Default = 12)
107 |
108 | 'ema_slow' The window period of the "slow" EMA. (Default = 26)
109 |
110 | Returns:
111 |
112 | MACD: The difference between the ema_fast and ema_slow day EMAs of a
113 | security.
114 |
115 | MACD Signal: The ema_macd day EMA of the MACD.
116 |
117 | MACD Histogram: Difference between the MACD and MACD Signal.
118 | """
119 |
120 | # todo: maybe add 'long' too?
121 | if not isinstance(ema_fast, int) or not 0 < ema_fast <= len(data):
122 | raise ValueError("'ema_fast' must be an integer " +
123 | "between 1 and %d." % len(data))
124 |
125 | if not isinstance(ema_slow, int) or not 0 < ema_slow <= len(data):
126 | raise ValueError("'ema_slow' must be an integer " +
127 | "between 1 and %d." % len(data))
128 |
129 | if not isinstance(data, (pd.Series, pd.DataFrame)):
130 | raise ValueError("'data' must be a pandas Series or DataFrame.")
131 |
132 | if not isinstance(ema_macd, int) or not 0 < ema_macd <= len(data):
133 | raise ValueError("'ema_macd' must be an integer " +
134 | "between 1 and %d." % len(data))
135 |
136 | macd = pd.ewma(data, span = ema_fast) - pd.ewma(data, span = ema_slow)
137 | macds = pd.ewma(macd, span = ema_macd)
138 |
139 | return macd, macds, macd - macds
140 |
141 | def full_stochastic(data, lookback = 14, d_sma = 3, full_sma = 3):
142 | """Full Stochastic Oscillator: The full stochastic oscillator for
143 | (lookback,d_sma,full_sma) is defined by:
144 |
145 | %K = (Current Close - Lowest Low)/(Highest High - Lowest Low) * 100
146 | %D = d_sma-day SMA of %K
147 | full %K = %D
148 | full %D = full_sma-day SMA of full %K
149 |
150 | Parameters:
151 |
152 | 'data' is a pandas Series or DataFrame of prices. Must contain
153 | 'High', 'Low' and 'Close' information.
154 |
155 | 'lookback' Lookback period for calculating highest and Lowst low
156 |
157 | 'd_sma' The SMA used for calculating %D
158 |
159 | 'full_sma' The SMA used for calculating full %D
160 |
161 | Returns:
162 |
163 | full %K: (Or standard %D) as defined above
164 |
165 | full %D: as defined above
166 |
167 | %K: Standard %K as defined above
168 | """
169 |
170 | l_low = pd.rolling_min(data['Low'], lookback)
171 | h_high = pd.rolling_max(data['High'], lookback)
172 |
173 | K = (data['Close'] - l_low)/(h_high - l_low) * 100.0
174 | full_K = pd.rolling_mean(K,d_sma)
175 | full_D = pd.rolling_mean(full_K,full_sma)
176 |
177 | return full_K, full_D, K
178 |
179 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # QunatPy documentation build configuration file, created by
4 | # sphinx-quickstart on Thu Mar 7 13:05:29 2013.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 |
17 | # If extensions (or modules to document with autodoc) are in another directory,
18 | # add these directories to sys.path here. If the directory is relative to the
19 | # documentation root, use os.path.abspath to make it absolute, like shown here.
20 | #sys.path.insert(0, os.path.abspath('.'))
21 | sys.path.append(os.path.abspath('sphinxext'))
22 |
23 | # -- General configuration -----------------------------------------------------
24 |
25 | # If your documentation needs a minimal Sphinx version, state it here.
26 | #needs_sphinx = '1.0'
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be extensions
29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
30 | extensions = [
31 | 'matplotlib.sphinxext.mathmpl',
32 | 'matplotlib.sphinxext.only_directives',
33 | 'matplotlib.sphinxext.plot_directive',
34 | 'sphinx.ext.autodoc',
35 | 'sphinx.ext.doctest',
36 | 'ipython_console_highlighting',
37 | 'inheritance_diagram',
38 | 'numpydoc']
39 |
40 | # Add any paths that contain templates here, relative to this directory.
41 | templates_path = ['_templates']
42 |
43 | # The suffix of source filenames.
44 | source_suffix = '.rst'
45 |
46 | # The encoding of source files.
47 | #source_encoding = 'utf-8-sig'
48 |
49 | # The master toctree document.
50 | master_doc = 'index'
51 |
52 | # General information about the project.
53 | project = u'QunatPy'
54 | copyright = u'2013, Joseph Smidt'
55 |
56 | # The version info for the project you're documenting, acts as replacement for
57 | # |version| and |release|, also used in various other places throughout the
58 | # built documents.
59 | #
60 | # The short X.Y version.
61 | version = 'v1.0'
62 | # The full version, including alpha/beta/rc tags.
63 | release = 'v1.0'
64 |
65 | # The language for content autogenerated by Sphinx. Refer to documentation
66 | # for a list of supported languages.
67 | #language = None
68 |
69 | # There are two options for replacing |today|: either, you set today to some
70 | # non-false value, then it is used:
71 | #today = ''
72 | # Else, today_fmt is used as the format for a strftime call.
73 | #today_fmt = '%B %d, %Y'
74 |
75 | # List of patterns, relative to source directory, that match files and
76 | # directories to ignore when looking for source files.
77 | exclude_patterns = ['_build']
78 |
79 | # The reST default role (used for this markup: `text`) to use for all documents.
80 | #default_role = None
81 |
82 | # If true, '()' will be appended to :func: etc. cross-reference text.
83 | #add_function_parentheses = True
84 |
85 | # If true, the current module name will be prepended to all description
86 | # unit titles (such as .. function::).
87 | #add_module_names = True
88 |
89 | # If true, sectionauthor and moduleauthor directives will be shown in the
90 | # output. They are ignored by default.
91 | #show_authors = False
92 |
93 | # The name of the Pygments (syntax highlighting) style to use.
94 | pygments_style = 'sphinx'
95 |
96 | # A list of ignored prefixes for module index sorting.
97 | #modindex_common_prefix = []
98 |
99 |
100 | # -- Options for HTML output ---------------------------------------------------
101 |
102 | # The theme to use for HTML and HTML Help pages. See the documentation for
103 | # a list of builtin themes.
104 | html_theme = 'default'
105 |
106 | # Theme options are theme-specific and customize the look and feel of a theme
107 | # further. For a list of options available for each theme, see the
108 | # documentation.
109 | #html_theme_options = {}
110 |
111 | # Add any paths that contain custom themes here, relative to this directory.
112 | #html_theme_path = []
113 |
114 | # The name for this set of Sphinx documents. If None, it defaults to
115 | # " v documentation".
116 | #html_title = None
117 |
118 | # A shorter title for the navigation bar. Default is the same as html_title.
119 | #html_short_title = None
120 |
121 | # The name of an image file (relative to this directory) to place at the top
122 | # of the sidebar.
123 | #html_logo = None
124 |
125 | # The name of an image file (within the static path) to use as favicon of the
126 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
127 | # pixels large.
128 | #html_favicon = None
129 |
130 | # Add any paths that contain custom static files (such as style sheets) here,
131 | # relative to this directory. They are copied after the builtin static files,
132 | # so a file named "default.css" will overwrite the builtin "default.css".
133 | html_static_path = ['_static']
134 |
135 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
136 | # using the given strftime format.
137 | #html_last_updated_fmt = '%b %d, %Y'
138 |
139 | # If true, SmartyPants will be used to convert quotes and dashes to
140 | # typographically correct entities.
141 | #html_use_smartypants = True
142 |
143 | # Custom sidebar templates, maps document names to template names.
144 | #html_sidebars = {}
145 |
146 | # Additional templates that should be rendered to pages, maps page names to
147 | # template names.
148 | #html_additional_pages = {}
149 |
150 | # If false, no module index is generated.
151 | #html_domain_indices = True
152 |
153 | # If false, no index is generated.
154 | #html_use_index = True
155 |
156 | # If true, the index is split into individual pages for each letter.
157 | #html_split_index = False
158 |
159 | # If true, links to the reST sources are added to the pages.
160 | #html_show_sourcelink = True
161 |
162 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
163 | #html_show_sphinx = True
164 |
165 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
166 | #html_show_copyright = True
167 |
168 | # If true, an OpenSearch description file will be output, and all pages will
169 | # contain a tag referring to it. The value of this option must be the
170 | # base URL from which the finished HTML is served.
171 | #html_use_opensearch = ''
172 |
173 | # This is the file name suffix for HTML files (e.g. ".xhtml").
174 | #html_file_suffix = None
175 |
176 | # Output file base name for HTML help builder.
177 | htmlhelp_basename = 'QunatPydoc'
178 |
179 |
180 | # -- Options for LaTeX output --------------------------------------------------
181 |
182 | latex_elements = {
183 | # The paper size ('letterpaper' or 'a4paper').
184 | #'papersize': 'letterpaper',
185 |
186 | # The font size ('10pt', '11pt' or '12pt').
187 | #'pointsize': '10pt',
188 |
189 | # Additional stuff for the LaTeX preamble.
190 | #'preamble': '',
191 | }
192 |
193 | # Grouping the document tree into LaTeX files. List of tuples
194 | # (source start file, target name, title, author, documentclass [howto/manual]).
195 | latex_documents = [
196 | ('index', 'QunatPy.tex', u'QunatPy Documentation',
197 | u'Joseph Smidt', 'manual'),
198 | ]
199 |
200 | # The name of an image file (relative to this directory) to place at the top of
201 | # the title page.
202 | #latex_logo = None
203 |
204 | # For "manual" documents, if this is true, then toplevel headings are parts,
205 | # not chapters.
206 | #latex_use_parts = False
207 |
208 | # If true, show page references after internal links.
209 | #latex_show_pagerefs = False
210 |
211 | # If true, show URL addresses after external links.
212 | #latex_show_urls = False
213 |
214 | # Documents to append as an appendix to all manuals.
215 | #latex_appendices = []
216 |
217 | # If false, no module index is generated.
218 | #latex_domain_indices = True
219 |
220 |
221 | # -- Options for manual page output --------------------------------------------
222 |
223 | # One entry per manual page. List of tuples
224 | # (source start file, name, description, authors, manual section).
225 | man_pages = [
226 | ('index', 'qunatpy', u'QunatPy Documentation',
227 | [u'Joseph Smidt'], 1)
228 | ]
229 |
230 | # If true, show URL addresses after external links.
231 | #man_show_urls = False
232 |
233 |
234 | # -- Options for Texinfo output ------------------------------------------------
235 |
236 | # Grouping the document tree into Texinfo files. List of tuples
237 | # (source start file, target name, title, author,
238 | # dir menu entry, description, category)
239 | texinfo_documents = [
240 | ('index', 'QunatPy', u'QunatPy Documentation',
241 | u'Joseph Smidt', 'QunatPy', 'One line description of project.',
242 | 'Miscellaneous'),
243 | ]
244 |
245 | # Documents to append as an appendix to all manuals.
246 | #texinfo_appendices = []
247 |
248 | # If false, no module index is generated.
249 | #texinfo_domain_indices = True
250 |
251 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
252 | #texinfo_show_urls = 'footnote'
253 |
--------------------------------------------------------------------------------
/docs/_static/default.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Alternate Sphinx design
3 | * Originally created by Armin Ronacher for Werkzeug, adapted by Georg Brandl.
4 | */
5 |
6 | body {
7 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 'Verdana', sans-serif;
8 | font-size: 14px;
9 | letter-spacing: -0.01em;
10 | line-height: 150%;
11 | text-align: center;
12 | /*background-color: #AFC1C4; */
13 | background-color: #BFD1D4;
14 | color: black;
15 | padding: 0;
16 | border: 1px solid #aaa;
17 |
18 | margin: 0px 80px 0px 80px;
19 | min-width: 740px;
20 | }
21 |
22 | a {
23 | color: #CA7900;
24 | text-decoration: none;
25 | }
26 |
27 | a:hover {
28 | color: #2491CF;
29 | }
30 |
31 | pre {
32 | font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
33 | font-size: 0.95em;
34 | letter-spacing: 0.015em;
35 | padding: 0.5em;
36 | border: 1px solid #ccc;
37 | background-color: #f8f8f8;
38 | }
39 |
40 | td.linenos pre {
41 | padding: 0.5em 0;
42 | border: 0;
43 | background-color: transparent;
44 | color: #aaa;
45 | }
46 |
47 | table.highlighttable {
48 | margin-left: 0.5em;
49 | }
50 |
51 | table.highlighttable td {
52 | padding: 0 0.5em 0 0.5em;
53 | }
54 |
55 | cite, code, tt {
56 | font-family: 'Consolas', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
57 | font-size: 0.95em;
58 | letter-spacing: 0.01em;
59 | }
60 |
61 | hr {
62 | border: 1px solid #abc;
63 | margin: 2em;
64 | }
65 |
66 | tt {
67 | background-color: #f2f2f2;
68 | border-bottom: 1px solid #ddd;
69 | color: #333;
70 | }
71 |
72 | tt.descname {
73 | background-color: transparent;
74 | font-weight: bold;
75 | font-size: 1.2em;
76 | border: 0;
77 | }
78 |
79 | tt.descclassname {
80 | background-color: transparent;
81 | border: 0;
82 | }
83 |
84 | tt.xref {
85 | background-color: transparent;
86 | font-weight: bold;
87 | border: 0;
88 | }
89 |
90 | a tt {
91 | background-color: transparent;
92 | font-weight: bold;
93 | border: 0;
94 | color: #CA7900;
95 | }
96 |
97 | a tt:hover {
98 | color: #2491CF;
99 | }
100 |
101 | dl {
102 | margin-bottom: 15px;
103 | }
104 |
105 | dd p {
106 | margin-top: 0px;
107 | }
108 |
109 | dd ul, dd table {
110 | margin-bottom: 10px;
111 | }
112 |
113 | dd {
114 | margin-top: 3px;
115 | margin-bottom: 10px;
116 | margin-left: 30px;
117 | }
118 |
119 | .refcount {
120 | color: #060;
121 | }
122 |
123 | dt:target,
124 | .highlight {
125 | background-color: #fbe54e;
126 | }
127 |
128 | dl.class, dl.function {
129 | border-top: 2px solid #888;
130 | }
131 |
132 | dl.method, dl.attribute {
133 | border-top: 1px solid #aaa;
134 | }
135 |
136 | dl.glossary dt {
137 | font-weight: bold;
138 | font-size: 1.1em;
139 | }
140 |
141 | pre {
142 | line-height: 120%;
143 | }
144 |
145 | pre a {
146 | color: inherit;
147 | text-decoration: underline;
148 | }
149 |
150 | .first {
151 | margin-top: 0 !important;
152 | }
153 |
154 | div.document {
155 | background-color: white;
156 | text-align: left;
157 | background-image: url(contents.png);
158 | background-repeat: repeat-x;
159 | }
160 |
161 | /*
162 | div.documentwrapper {
163 | width: 100%;
164 | }
165 | */
166 |
167 | div.clearer {
168 | clear: both;
169 | }
170 |
171 | div.related h3 {
172 | display: none;
173 | }
174 |
175 | div.related ul {
176 | background-image: url(navigation.png);
177 | height: 2em;
178 | list-style: none;
179 | border-top: 1px solid #ddd;
180 | border-bottom: 1px solid #ddd;
181 | margin: 0;
182 | padding-left: 10px;
183 | }
184 |
185 | div.related ul li {
186 | margin: 0;
187 | padding: 0;
188 | height: 2em;
189 | float: left;
190 | }
191 |
192 | div.related ul li.right {
193 | float: right;
194 | margin-right: 5px;
195 | }
196 |
197 | div.related ul li a {
198 | margin: 0;
199 | padding: 0 5px 0 5px;
200 | line-height: 1.75em;
201 | color: #EE9816;
202 | }
203 |
204 | div.related ul li a:hover {
205 | color: #3CA8E7;
206 | }
207 |
208 | div.body {
209 | margin: 0;
210 | padding: 0.5em 20px 20px 20px;
211 | }
212 |
213 | div.bodywrapper {
214 | margin: 0 240px 0 0;
215 | border-right: 1px solid #ccc;
216 | }
217 |
218 | div.body a {
219 | text-decoration: underline;
220 | }
221 |
222 | div.sphinxsidebar {
223 | margin: 0;
224 | padding: 0.5em 15px 15px 0;
225 | width: 210px;
226 | float: right;
227 | text-align: left;
228 | /* margin-left: -100%; */
229 | }
230 |
231 | div.sphinxsidebar h4, div.sphinxsidebar h3 {
232 | margin: 1em 0 0.5em 0;
233 | font-size: 0.9em;
234 | padding: 0.1em 0 0.1em 0.5em;
235 | color: white;
236 | border: 1px solid #86989B;
237 | background-color: #AFC1C4;
238 | }
239 |
240 | div.sphinxsidebar ul {
241 | padding-left: 1.5em;
242 | margin-top: 7px;
243 | list-style: none;
244 | padding: 0;
245 | line-height: 130%;
246 | }
247 |
248 | div.sphinxsidebar ul ul {
249 | list-style: square;
250 | margin-left: 20px;
251 | }
252 |
253 | p {
254 | margin: 0.8em 0 0.5em 0;
255 | }
256 |
257 | p.rubric {
258 | font-weight: bold;
259 | }
260 |
261 | h1 {
262 | margin: 0;
263 | padding: 0.7em 0 0.3em 0;
264 | font-size: 1.5em;
265 | color: #11557C;
266 | }
267 |
268 | h2 {
269 | margin: 1.3em 0 0.2em 0;
270 | font-size: 1.35em;
271 | padding: 0;
272 | }
273 |
274 | h3 {
275 | margin: 1em 0 -0.3em 0;
276 | font-size: 1.2em;
277 | }
278 |
279 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
280 | color: black!important;
281 | }
282 |
283 | h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
284 | display: none;
285 | margin: 0 0 0 0.3em;
286 | padding: 0 0.2em 0 0.2em;
287 | color: #aaa!important;
288 | }
289 |
290 | h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
291 | h5:hover a.anchor, h6:hover a.anchor {
292 | display: inline;
293 | }
294 |
295 | h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
296 | h5 a.anchor:hover, h6 a.anchor:hover {
297 | color: #777;
298 | background-color: #eee;
299 | }
300 |
301 | table {
302 | border-collapse: collapse;
303 | margin: 0 -0.5em 0 -0.5em;
304 | }
305 |
306 | table td, table th {
307 | padding: 0.2em 0.5em 0.2em 0.5em;
308 | }
309 |
310 | div.footer {
311 | background-color: #E3EFF1;
312 | color: #86989B;
313 | padding: 3px 8px 3px 0;
314 | clear: both;
315 | font-size: 0.8em;
316 | text-align: right;
317 | }
318 |
319 | div.footer a {
320 | color: #86989B;
321 | text-decoration: underline;
322 | }
323 |
324 | div.pagination {
325 | margin-top: 2em;
326 | padding-top: 0.5em;
327 | border-top: 1px solid black;
328 | text-align: center;
329 | }
330 |
331 | div.sphinxsidebar ul.toc {
332 | margin: 1em 0 1em 0;
333 | padding: 0 0 0 0.5em;
334 | list-style: none;
335 | }
336 |
337 | div.sphinxsidebar ul.toc li {
338 | margin: 0.5em 0 0.5em 0;
339 | font-size: 0.9em;
340 | line-height: 130%;
341 | }
342 |
343 | div.sphinxsidebar ul.toc li p {
344 | margin: 0;
345 | padding: 0;
346 | }
347 |
348 | div.sphinxsidebar ul.toc ul {
349 | margin: 0.2em 0 0.2em 0;
350 | padding: 0 0 0 1.8em;
351 | }
352 |
353 | div.sphinxsidebar ul.toc ul li {
354 | padding: 0;
355 | }
356 |
357 | div.admonition, div.warning {
358 | font-size: 0.9em;
359 | margin: 1em 0 0 0;
360 | border: 1px solid #86989B;
361 | background-color: #f7f7f7;
362 | }
363 |
364 | div.admonition p, div.warning p {
365 | margin: 0.5em 1em 0.5em 1em;
366 | padding: 0;
367 | }
368 |
369 | div.admonition pre, div.warning pre {
370 | margin: 0.4em 1em 0.4em 1em;
371 | }
372 |
373 | div.admonition p.admonition-title,
374 | div.warning p.admonition-title {
375 | margin: 0;
376 | padding: 0.1em 0 0.1em 0.5em;
377 | color: white;
378 | border-bottom: 1px solid #86989B;
379 | font-weight: bold;
380 | background-color: #AFC1C4;
381 | }
382 |
383 | div.warning {
384 | border: 1px solid #940000;
385 | }
386 |
387 | div.warning p.admonition-title {
388 | background-color: #CF0000;
389 | border-bottom-color: #940000;
390 | }
391 |
392 | div.admonition ul, div.admonition ol,
393 | div.warning ul, div.warning ol {
394 | margin: 0.1em 0.5em 0.5em 3em;
395 | padding: 0;
396 | }
397 |
398 | div.versioninfo {
399 | margin: 1em 0 0 0;
400 | border: 1px solid #ccc;
401 | background-color: #DDEAF0;
402 | padding: 8px;
403 | line-height: 1.3em;
404 | font-size: 0.9em;
405 | }
406 |
407 |
408 | a.headerlink {
409 | color: #c60f0f!important;
410 | font-size: 1em;
411 | margin-left: 6px;
412 | padding: 0 4px 0 4px;
413 | text-decoration: none!important;
414 | visibility: hidden;
415 | }
416 |
417 | h1:hover > a.headerlink,
418 | h2:hover > a.headerlink,
419 | h3:hover > a.headerlink,
420 | h4:hover > a.headerlink,
421 | h5:hover > a.headerlink,
422 | h6:hover > a.headerlink,
423 | dt:hover > a.headerlink {
424 | visibility: visible;
425 | }
426 |
427 | a.headerlink:hover {
428 | background-color: #ccc;
429 | color: white!important;
430 | }
431 |
432 | table.indextable td {
433 | text-align: left;
434 | vertical-align: top;
435 | }
436 |
437 | table.indextable dl, table.indextable dd {
438 | margin-top: 0;
439 | margin-bottom: 0;
440 | }
441 |
442 | table.indextable tr.pcap {
443 | height: 10px;
444 | }
445 |
446 | table.indextable tr.cap {
447 | margin-top: 10px;
448 | background-color: #f2f2f2;
449 | }
450 |
451 | img.toggler {
452 | margin-right: 3px;
453 | margin-top: 3px;
454 | cursor: pointer;
455 | }
456 |
457 | img.inheritance {
458 | border: 0px
459 | }
460 |
461 | form.pfform {
462 | margin: 10px 0 20px 0;
463 | }
464 |
465 | table.contentstable {
466 | width: 90%;
467 | }
468 |
469 | table.contentstable p.biglink {
470 | line-height: 150%;
471 | }
472 |
473 | a.biglink {
474 | font-size: 1.3em;
475 | }
476 |
477 | span.linkdescr {
478 | font-style: italic;
479 | padding-top: 5px;
480 | font-size: 90%;
481 | }
482 |
483 | ul.search {
484 | margin: 10px 0 0 20px;
485 | padding: 0;
486 | }
487 |
488 | ul.search li {
489 | padding: 5px 0 5px 20px;
490 | background-image: url(file.png);
491 | background-repeat: no-repeat;
492 | background-position: 0 7px;
493 | }
494 |
495 | ul.search li a {
496 | font-weight: bold;
497 | }
498 |
499 | ul.search li div.context {
500 | color: #888;
501 | margin: 2px 0 0 30px;
502 | text-align: left;
503 | }
504 |
505 | ul.keywordmatches li.goodmatch a {
506 | font-weight: bold;
507 | }
508 |
--------------------------------------------------------------------------------
/docs/sphinxext/inheritance_diagram.py:
--------------------------------------------------------------------------------
1 | """
2 | Defines a docutils directive for inserting inheritance diagrams.
3 |
4 | Provide the directive with one or more classes or modules (separated
5 | by whitespace). For modules, all of the classes in that module will
6 | be used.
7 |
8 | Example::
9 |
10 | Given the following classes:
11 |
12 | class A: pass
13 | class B(A): pass
14 | class C(A): pass
15 | class D(B, C): pass
16 | class E(B): pass
17 |
18 | .. inheritance-diagram: D E
19 |
20 | Produces a graph like the following:
21 |
22 | A
23 | / \
24 | B C
25 | / \ /
26 | E D
27 |
28 | The graph is inserted as a PNG+image map into HTML and a PDF in
29 | LaTeX.
30 | """
31 |
32 | import inspect
33 | import os
34 | import re
35 | import subprocess
36 | try:
37 | from hashlib import md5
38 | except ImportError:
39 | from md5 import md5
40 |
41 | from docutils.nodes import Body, Element
42 | from docutils.parsers.rst import directives
43 | from sphinx.roles import xfileref_role
44 |
45 | def my_import(name):
46 | """Module importer - taken from the python documentation.
47 |
48 | This function allows importing names with dots in them."""
49 |
50 | mod = __import__(name)
51 | components = name.split('.')
52 | for comp in components[1:]:
53 | mod = getattr(mod, comp)
54 | return mod
55 |
56 | class DotException(Exception):
57 | pass
58 |
59 | class InheritanceGraph(object):
60 | """
61 | Given a list of classes, determines the set of classes that
62 | they inherit from all the way to the root "object", and then
63 | is able to generate a graphviz dot graph from them.
64 | """
65 | def __init__(self, class_names, show_builtins=False):
66 | """
67 | *class_names* is a list of child classes to show bases from.
68 |
69 | If *show_builtins* is True, then Python builtins will be shown
70 | in the graph.
71 | """
72 | self.class_names = class_names
73 | self.classes = self._import_classes(class_names)
74 | self.all_classes = self._all_classes(self.classes)
75 | if len(self.all_classes) == 0:
76 | raise ValueError("No classes found for inheritance diagram")
77 | self.show_builtins = show_builtins
78 |
79 | py_sig_re = re.compile(r'''^([\w.]*\.)? # class names
80 | (\w+) \s* $ # optionally arguments
81 | ''', re.VERBOSE)
82 |
83 | def _import_class_or_module(self, name):
84 | """
85 | Import a class using its fully-qualified *name*.
86 | """
87 | try:
88 | path, base = self.py_sig_re.match(name).groups()
89 | except:
90 | raise ValueError(
91 | "Invalid class or module '%s' specified for inheritance diagram" % name)
92 | fullname = (path or '') + base
93 | path = (path and path.rstrip('.'))
94 | if not path:
95 | path = base
96 | try:
97 | module = __import__(path, None, None, [])
98 | # We must do an import of the fully qualified name. Otherwise if a
99 | # subpackage 'a.b' is requested where 'import a' does NOT provide
100 | # 'a.b' automatically, then 'a.b' will not be found below. This
101 | # second call will force the equivalent of 'import a.b' to happen
102 | # after the top-level import above.
103 | my_import(fullname)
104 |
105 | except ImportError:
106 | raise ValueError(
107 | "Could not import class or module '%s' specified for inheritance diagram" % name)
108 |
109 | try:
110 | todoc = module
111 | for comp in fullname.split('.')[1:]:
112 | todoc = getattr(todoc, comp)
113 | except AttributeError:
114 | raise ValueError(
115 | "Could not find class or module '%s' specified for inheritance diagram" % name)
116 |
117 | # If a class, just return it
118 | if inspect.isclass(todoc):
119 | return [todoc]
120 | elif inspect.ismodule(todoc):
121 | classes = []
122 | for cls in todoc.__dict__.values():
123 | if inspect.isclass(cls) and cls.__module__ == todoc.__name__:
124 | classes.append(cls)
125 | return classes
126 | raise ValueError(
127 | "'%s' does not resolve to a class or module" % name)
128 |
129 | def _import_classes(self, class_names):
130 | """
131 | Import a list of classes.
132 | """
133 | classes = []
134 | for name in class_names:
135 | classes.extend(self._import_class_or_module(name))
136 | return classes
137 |
138 | def _all_classes(self, classes):
139 | """
140 | Return a list of all classes that are ancestors of *classes*.
141 | """
142 | all_classes = {}
143 |
144 | def recurse(cls):
145 | all_classes[cls] = None
146 | for c in cls.__bases__:
147 | if c not in all_classes:
148 | recurse(c)
149 |
150 | for cls in classes:
151 | recurse(cls)
152 |
153 | return all_classes.keys()
154 |
155 | def class_name(self, cls, parts=0):
156 | """
157 | Given a class object, return a fully-qualified name. This
158 | works for things I've tested in matplotlib so far, but may not
159 | be completely general.
160 | """
161 | module = cls.__module__
162 | if module == '__builtin__':
163 | fullname = cls.__name__
164 | else:
165 | fullname = "%s.%s" % (module, cls.__name__)
166 | if parts == 0:
167 | return fullname
168 | name_parts = fullname.split('.')
169 | return '.'.join(name_parts[-parts:])
170 |
171 | def get_all_class_names(self):
172 | """
173 | Get all of the class names involved in the graph.
174 | """
175 | return [self.class_name(x) for x in self.all_classes]
176 |
177 | # These are the default options for graphviz
178 | default_graph_options = {
179 | "rankdir": "LR",
180 | "size": '"8.0, 12.0"'
181 | }
182 | default_node_options = {
183 | "shape": "box",
184 | "fontsize": 10,
185 | "height": 0.25,
186 | "fontname": "Vera Sans, DejaVu Sans, Liberation Sans, Arial, Helvetica, sans",
187 | "style": '"setlinewidth(0.5)"'
188 | }
189 | default_edge_options = {
190 | "arrowsize": 0.5,
191 | "style": '"setlinewidth(0.5)"'
192 | }
193 |
194 | def _format_node_options(self, options):
195 | return ','.join(["%s=%s" % x for x in options.items()])
196 | def _format_graph_options(self, options):
197 | return ''.join(["%s=%s;\n" % x for x in options.items()])
198 |
199 | def generate_dot(self, fd, name, parts=0, urls={},
200 | graph_options={}, node_options={},
201 | edge_options={}):
202 | """
203 | Generate a graphviz dot graph from the classes that
204 | were passed in to __init__.
205 |
206 | *fd* is a Python file-like object to write to.
207 |
208 | *name* is the name of the graph
209 |
210 | *urls* is a dictionary mapping class names to http urls
211 |
212 | *graph_options*, *node_options*, *edge_options* are
213 | dictionaries containing key/value pairs to pass on as graphviz
214 | properties.
215 | """
216 | g_options = self.default_graph_options.copy()
217 | g_options.update(graph_options)
218 | n_options = self.default_node_options.copy()
219 | n_options.update(node_options)
220 | e_options = self.default_edge_options.copy()
221 | e_options.update(edge_options)
222 |
223 | fd.write('digraph %s {\n' % name)
224 | fd.write(self._format_graph_options(g_options))
225 |
226 | for cls in self.all_classes:
227 | if not self.show_builtins and cls in __builtins__.values():
228 | continue
229 |
230 | name = self.class_name(cls, parts)
231 |
232 | # Write the node
233 | this_node_options = n_options.copy()
234 | url = urls.get(self.class_name(cls))
235 | if url is not None:
236 | this_node_options['URL'] = '"%s"' % url
237 | fd.write(' "%s" [%s];\n' %
238 | (name, self._format_node_options(this_node_options)))
239 |
240 | # Write the edges
241 | for base in cls.__bases__:
242 | if not self.show_builtins and base in __builtins__.values():
243 | continue
244 |
245 | base_name = self.class_name(base, parts)
246 | fd.write(' "%s" -> "%s" [%s];\n' %
247 | (base_name, name,
248 | self._format_node_options(e_options)))
249 | fd.write('}\n')
250 |
251 | def run_dot(self, args, name, parts=0, urls={},
252 | graph_options={}, node_options={}, edge_options={}):
253 | """
254 | Run graphviz 'dot' over this graph, returning whatever 'dot'
255 | writes to stdout.
256 |
257 | *args* will be passed along as commandline arguments.
258 |
259 | *name* is the name of the graph
260 |
261 | *urls* is a dictionary mapping class names to http urls
262 |
263 | Raises DotException for any of the many os and
264 | installation-related errors that may occur.
265 | """
266 | try:
267 | dot = subprocess.Popen(['dot'] + list(args),
268 | stdin=subprocess.PIPE, stdout=subprocess.PIPE,
269 | close_fds=True)
270 | except OSError:
271 | raise DotException("Could not execute 'dot'. Are you sure you have 'graphviz' installed?")
272 | except ValueError:
273 | raise DotException("'dot' called with invalid arguments")
274 | except:
275 | raise DotException("Unexpected error calling 'dot'")
276 |
277 | self.generate_dot(dot.stdin, name, parts, urls, graph_options,
278 | node_options, edge_options)
279 | dot.stdin.close()
280 | result = dot.stdout.read()
281 | returncode = dot.wait()
282 | if returncode != 0:
283 | raise DotException("'dot' returned the errorcode %d" % returncode)
284 | return result
285 |
286 | class inheritance_diagram(Body, Element):
287 | """
288 | A docutils node to use as a placeholder for the inheritance
289 | diagram.
290 | """
291 | pass
292 |
293 | def inheritance_diagram_directive(name, arguments, options, content, lineno,
294 | content_offset, block_text, state,
295 | state_machine):
296 | """
297 | Run when the inheritance_diagram directive is first encountered.
298 | """
299 | node = inheritance_diagram()
300 |
301 | class_names = arguments
302 |
303 | # Create a graph starting with the list of classes
304 | graph = InheritanceGraph(class_names)
305 |
306 | # Create xref nodes for each target of the graph's image map and
307 | # add them to the doc tree so that Sphinx can resolve the
308 | # references to real URLs later. These nodes will eventually be
309 | # removed from the doctree after we're done with them.
310 | for name in graph.get_all_class_names():
311 | refnodes, x = xfileref_role(
312 | 'class', ':class:`%s`' % name, name, 0, state)
313 | node.extend(refnodes)
314 | # Store the graph object so we can use it to generate the
315 | # dot file later
316 | node['graph'] = graph
317 | # Store the original content for use as a hash
318 | node['parts'] = options.get('parts', 0)
319 | node['content'] = " ".join(class_names)
320 | return [node]
321 |
322 | def get_graph_hash(node):
323 | return md5(node['content'] + str(node['parts'])).hexdigest()[-10:]
324 |
325 | def html_output_graph(self, node):
326 | """
327 | Output the graph for HTML. This will insert a PNG with clickable
328 | image map.
329 | """
330 | graph = node['graph']
331 | parts = node['parts']
332 |
333 | graph_hash = get_graph_hash(node)
334 | name = "inheritance%s" % graph_hash
335 | path = '_images'
336 | dest_path = os.path.join(setup.app.builder.outdir, path)
337 | if not os.path.exists(dest_path):
338 | os.makedirs(dest_path)
339 | png_path = os.path.join(dest_path, name + ".png")
340 | path = setup.app.builder.imgpath
341 |
342 | # Create a mapping from fully-qualified class names to URLs.
343 | urls = {}
344 | for child in node:
345 | if child.get('refuri') is not None:
346 | urls[child['reftitle']] = child.get('refuri')
347 | elif child.get('refid') is not None:
348 | urls[child['reftitle']] = '#' + child.get('refid')
349 |
350 | # These arguments to dot will save a PNG file to disk and write
351 | # an HTML image map to stdout.
352 | image_map = graph.run_dot(['-Tpng', '-o%s' % png_path, '-Tcmapx'],
353 | name, parts, urls)
354 | return ('
%s' %
355 | (path, name, name, image_map))
356 |
357 | def latex_output_graph(self, node):
358 | """
359 | Output the graph for LaTeX. This will insert a PDF.
360 | """
361 | graph = node['graph']
362 | parts = node['parts']
363 |
364 | graph_hash = get_graph_hash(node)
365 | name = "inheritance%s" % graph_hash
366 | dest_path = os.path.abspath(os.path.join(setup.app.builder.outdir, '_images'))
367 | if not os.path.exists(dest_path):
368 | os.makedirs(dest_path)
369 | pdf_path = os.path.abspath(os.path.join(dest_path, name + ".pdf"))
370 |
371 | graph.run_dot(['-Tpdf', '-o%s' % pdf_path],
372 | name, parts, graph_options={'size': '"6.0,6.0"'})
373 | return '\n\\includegraphics{%s}\n\n' % pdf_path
374 |
375 | def visit_inheritance_diagram(inner_func):
376 | """
377 | This is just a wrapper around html/latex_output_graph to make it
378 | easier to handle errors and insert warnings.
379 | """
380 | def visitor(self, node):
381 | try:
382 | content = inner_func(self, node)
383 | except DotException, e:
384 | # Insert the exception as a warning in the document
385 | warning = self.document.reporter.warning(str(e), line=node.line)
386 | warning.parent = node
387 | node.children = [warning]
388 | else:
389 | source = self.document.attributes['source']
390 | self.body.append(content)
391 | node.children = []
392 | return visitor
393 |
394 | def do_nothing(self, node):
395 | pass
396 |
397 | def setup(app):
398 | setup.app = app
399 | setup.confdir = app.confdir
400 |
401 | app.add_node(
402 | inheritance_diagram,
403 | latex=(visit_inheritance_diagram(latex_output_graph), do_nothing),
404 | html=(visit_inheritance_diagram(html_output_graph), do_nothing))
405 | app.add_directive(
406 | 'inheritance-diagram', inheritance_diagram_directive,
407 | False, (1, 100, 0), parts = directives.nonnegative_int)
408 |
--------------------------------------------------------------------------------
/docs/sphinxext/docscrape.py:
--------------------------------------------------------------------------------
1 | """Extract reference documentation from the NumPy source tree.
2 |
3 | """
4 |
5 | import inspect
6 | import textwrap
7 | import re
8 | import pydoc
9 | from StringIO import StringIO
10 | from warnings import warn
11 | 4
12 | class Reader(object):
13 | """A line-based string reader.
14 |
15 | """
16 | def __init__(self, data):
17 | """
18 | Parameters
19 | ----------
20 | data : str
21 | String with lines separated by '\n'.
22 |
23 | """
24 | if isinstance(data,list):
25 | self._str = data
26 | else:
27 | self._str = data.split('\n') # store string as list of lines
28 |
29 | self.reset()
30 |
31 | def __getitem__(self, n):
32 | return self._str[n]
33 |
34 | def reset(self):
35 | self._l = 0 # current line nr
36 |
37 | def read(self):
38 | if not self.eof():
39 | out = self[self._l]
40 | self._l += 1
41 | return out
42 | else:
43 | return ''
44 |
45 | def seek_next_non_empty_line(self):
46 | for l in self[self._l:]:
47 | if l.strip():
48 | break
49 | else:
50 | self._l += 1
51 |
52 | def eof(self):
53 | return self._l >= len(self._str)
54 |
55 | def read_to_condition(self, condition_func):
56 | start = self._l
57 | for line in self[start:]:
58 | if condition_func(line):
59 | return self[start:self._l]
60 | self._l += 1
61 | if self.eof():
62 | return self[start:self._l+1]
63 | return []
64 |
65 | def read_to_next_empty_line(self):
66 | self.seek_next_non_empty_line()
67 | def is_empty(line):
68 | return not line.strip()
69 | return self.read_to_condition(is_empty)
70 |
71 | def read_to_next_unindented_line(self):
72 | def is_unindented(line):
73 | return (line.strip() and (len(line.lstrip()) == len(line)))
74 | return self.read_to_condition(is_unindented)
75 |
76 | def peek(self,n=0):
77 | if self._l + n < len(self._str):
78 | return self[self._l + n]
79 | else:
80 | return ''
81 |
82 | def is_empty(self):
83 | return not ''.join(self._str).strip()
84 |
85 |
86 | class NumpyDocString(object):
87 | def __init__(self,docstring):
88 | docstring = textwrap.dedent(docstring).split('\n')
89 |
90 | self._doc = Reader(docstring)
91 | self._parsed_data = {
92 | 'Signature': '',
93 | 'Summary': [''],
94 | 'Extended Summary': [],
95 | 'Parameters': [],
96 | 'Returns': [],
97 | 'Raises': [],
98 | 'Warns': [],
99 | 'Other Parameters': [],
100 | 'Attributes': [],
101 | 'Methods': [],
102 | 'See Also': [],
103 | 'Notes': [],
104 | 'Warnings': [],
105 | 'References': '',
106 | 'Examples': '',
107 | 'index': {}
108 | }
109 |
110 | self._parse()
111 |
112 | def __getitem__(self,key):
113 | return self._parsed_data[key]
114 |
115 | def __setitem__(self,key,val):
116 | if not self._parsed_data.has_key(key):
117 | warn("Unknown section %s" % key)
118 | else:
119 | self._parsed_data[key] = val
120 |
121 | def _is_at_section(self):
122 | self._doc.seek_next_non_empty_line()
123 |
124 | if self._doc.eof():
125 | return False
126 |
127 | l1 = self._doc.peek().strip() # e.g. Parameters
128 |
129 | if l1.startswith('.. index::'):
130 | return True
131 |
132 | l2 = self._doc.peek(1).strip() # ---------- or ==========
133 | return l2.startswith('-'*len(l1)) or l2.startswith('='*len(l1))
134 |
135 | def _strip(self,doc):
136 | i = 0
137 | j = 0
138 | for i,line in enumerate(doc):
139 | if line.strip(): break
140 |
141 | for j,line in enumerate(doc[::-1]):
142 | if line.strip(): break
143 |
144 | return doc[i:len(doc)-j]
145 |
146 | def _read_to_next_section(self):
147 | section = self._doc.read_to_next_empty_line()
148 |
149 | while not self._is_at_section() and not self._doc.eof():
150 | if not self._doc.peek(-1).strip(): # previous line was empty
151 | section += ['']
152 |
153 | section += self._doc.read_to_next_empty_line()
154 |
155 | return section
156 |
157 | def _read_sections(self):
158 | while not self._doc.eof():
159 | data = self._read_to_next_section()
160 | name = data[0].strip()
161 |
162 | if name.startswith('..'): # index section
163 | yield name, data[1:]
164 | elif len(data) < 2:
165 | yield StopIteration
166 | else:
167 | yield name, self._strip(data[2:])
168 |
169 | def _parse_param_list(self,content):
170 | r = Reader(content)
171 | params = []
172 | while not r.eof():
173 | header = r.read().strip()
174 | if ' : ' in header:
175 | arg_name, arg_type = header.split(' : ')[:2]
176 | else:
177 | arg_name, arg_type = header, ''
178 |
179 | desc = r.read_to_next_unindented_line()
180 | desc = dedent_lines(desc)
181 |
182 | params.append((arg_name,arg_type,desc))
183 |
184 | return params
185 |
186 |
187 | _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|"
188 | r" (?P[a-zA-Z0-9_.-]+))\s*", re.X)
189 | def _parse_see_also(self, content):
190 | """
191 | func_name : Descriptive text
192 | continued text
193 | another_func_name : Descriptive text
194 | func_name1, func_name2, :meth:`func_name`, func_name3
195 |
196 | """
197 | items = []
198 |
199 | def parse_item_name(text):
200 | """Match ':role:`name`' or 'name'"""
201 | m = self._name_rgx.match(text)
202 | if m:
203 | g = m.groups()
204 | if g[1] is None:
205 | return g[3], None
206 | else:
207 | return g[2], g[1]
208 | raise ValueError("%s is not a item name" % text)
209 |
210 | def push_item(name, rest):
211 | if not name:
212 | return
213 | name, role = parse_item_name(name)
214 | items.append((name, list(rest), role))
215 | del rest[:]
216 |
217 | current_func = None
218 | rest = []
219 |
220 | for line in content:
221 | if not line.strip(): continue
222 |
223 | m = self._name_rgx.match(line)
224 | if m and line[m.end():].strip().startswith(':'):
225 | push_item(current_func, rest)
226 | current_func, line = line[:m.end()], line[m.end():]
227 | rest = [line.split(':', 1)[1].strip()]
228 | if not rest[0]:
229 | rest = []
230 | elif not line.startswith(' '):
231 | push_item(current_func, rest)
232 | current_func = None
233 | if ',' in line:
234 | for func in line.split(','):
235 | push_item(func, [])
236 | elif line.strip():
237 | current_func = line
238 | elif current_func is not None:
239 | rest.append(line.strip())
240 | push_item(current_func, rest)
241 | return items
242 |
243 | def _parse_index(self, section, content):
244 | """
245 | .. index: default
246 | :refguide: something, else, and more
247 |
248 | """
249 | def strip_each_in(lst):
250 | return [s.strip() for s in lst]
251 |
252 | out = {}
253 | section = section.split('::')
254 | if len(section) > 1:
255 | out['default'] = strip_each_in(section[1].split(','))[0]
256 | for line in content:
257 | line = line.split(':')
258 | if len(line) > 2:
259 | out[line[1]] = strip_each_in(line[2].split(','))
260 | return out
261 |
262 | def _parse_summary(self):
263 | """Grab signature (if given) and summary"""
264 | if self._is_at_section():
265 | return
266 |
267 | summary = self._doc.read_to_next_empty_line()
268 | summary_str = " ".join([s.strip() for s in summary]).strip()
269 | if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str):
270 | self['Signature'] = summary_str
271 | if not self._is_at_section():
272 | self['Summary'] = self._doc.read_to_next_empty_line()
273 | else:
274 | self['Summary'] = summary
275 |
276 | if not self._is_at_section():
277 | self['Extended Summary'] = self._read_to_next_section()
278 |
279 | def _parse(self):
280 | self._doc.reset()
281 | self._parse_summary()
282 |
283 | for (section,content) in self._read_sections():
284 | if not section.startswith('..'):
285 | section = ' '.join([s.capitalize() for s in section.split(' ')])
286 | if section in ('Parameters', 'Attributes', 'Methods',
287 | 'Returns', 'Raises', 'Warns'):
288 | self[section] = self._parse_param_list(content)
289 | elif section.startswith('.. index::'):
290 | self['index'] = self._parse_index(section, content)
291 | elif section == 'See Also':
292 | self['See Also'] = self._parse_see_also(content)
293 | else:
294 | self[section] = content
295 |
296 | # string conversion routines
297 |
298 | def _str_header(self, name, symbol='-'):
299 | return [name, len(name)*symbol]
300 |
301 | def _str_indent(self, doc, indent=4):
302 | out = []
303 | for line in doc:
304 | out += [' '*indent + line]
305 | return out
306 |
307 | def _str_signature(self):
308 | if self['Signature']:
309 | return [self['Signature'].replace('*','\*')] + ['']
310 | else:
311 | return ['']
312 |
313 | def _str_summary(self):
314 | if self['Summary']:
315 | return self['Summary'] + ['']
316 | else:
317 | return []
318 |
319 | def _str_extended_summary(self):
320 | if self['Extended Summary']:
321 | return self['Extended Summary'] + ['']
322 | else:
323 | return []
324 |
325 | def _str_param_list(self, name):
326 | out = []
327 | if self[name]:
328 | out += self._str_header(name)
329 | for param,param_type,desc in self[name]:
330 | out += ['%s : %s' % (param, param_type)]
331 | out += self._str_indent(desc)
332 | out += ['']
333 | return out
334 |
335 | def _str_section(self, name):
336 | out = []
337 | if self[name]:
338 | out += self._str_header(name)
339 | out += self[name]
340 | out += ['']
341 | return out
342 |
343 | def _str_see_also(self, func_role):
344 | if not self['See Also']: return []
345 | out = []
346 | out += self._str_header("See Also")
347 | last_had_desc = True
348 | for func, desc, role in self['See Also']:
349 | if role:
350 | link = ':%s:`%s`' % (role, func)
351 | elif func_role:
352 | link = ':%s:`%s`' % (func_role, func)
353 | else:
354 | link = "`%s`_" % func
355 | if desc or last_had_desc:
356 | out += ['']
357 | out += [link]
358 | else:
359 | out[-1] += ", %s" % link
360 | if desc:
361 | out += self._str_indent([' '.join(desc)])
362 | last_had_desc = True
363 | else:
364 | last_had_desc = False
365 | out += ['']
366 | return out
367 |
368 | def _str_index(self):
369 | idx = self['index']
370 | out = []
371 | out += ['.. index:: %s' % idx.get('default','')]
372 | for section, references in idx.iteritems():
373 | if section == 'default':
374 | continue
375 | out += [' :%s: %s' % (section, ', '.join(references))]
376 | return out
377 |
378 | def __str__(self, func_role=''):
379 | out = []
380 | out += self._str_signature()
381 | out += self._str_summary()
382 | out += self._str_extended_summary()
383 | for param_list in ('Parameters','Returns','Raises'):
384 | out += self._str_param_list(param_list)
385 | out += self._str_section('Warnings')
386 | out += self._str_see_also(func_role)
387 | for s in ('Notes','References','Examples'):
388 | out += self._str_section(s)
389 | out += self._str_index()
390 | return '\n'.join(out)
391 |
392 |
393 | def indent(str,indent=4):
394 | indent_str = ' '*indent
395 | if str is None:
396 | return indent_str
397 | lines = str.split('\n')
398 | return '\n'.join(indent_str + l for l in lines)
399 |
400 | def dedent_lines(lines):
401 | """Deindent a list of lines maximally"""
402 | return textwrap.dedent("\n".join(lines)).split("\n")
403 |
404 | def header(text, style='-'):
405 | return text + '\n' + style*len(text) + '\n'
406 |
407 |
408 | class FunctionDoc(NumpyDocString):
409 | def __init__(self, func, role='func', doc=None):
410 | self._f = func
411 | self._role = role # e.g. "func" or "meth"
412 | if doc is None:
413 | doc = inspect.getdoc(func) or ''
414 | try:
415 | NumpyDocString.__init__(self, doc)
416 | except ValueError, e:
417 | print '*'*78
418 | print "ERROR: '%s' while parsing `%s`" % (e, self._f)
419 | print '*'*78
420 | #print "Docstring follows:"
421 | #print doclines
422 | #print '='*78
423 |
424 | if not self['Signature']:
425 | func, func_name = self.get_func()
426 | try:
427 | # try to read signature
428 | argspec = inspect.getargspec(func)
429 | argspec = inspect.formatargspec(*argspec)
430 | argspec = argspec.replace('*','\*')
431 | signature = '%s%s' % (func_name, argspec)
432 | except TypeError, e:
433 | signature = '%s()' % func_name
434 | self['Signature'] = signature
435 |
436 | def get_func(self):
437 | func_name = getattr(self._f, '__name__', self.__class__.__name__)
438 | if inspect.isclass(self._f):
439 | func = getattr(self._f, '__call__', self._f.__init__)
440 | else:
441 | func = self._f
442 | return func, func_name
443 |
444 | def __str__(self):
445 | out = ''
446 |
447 | func, func_name = self.get_func()
448 | signature = self['Signature'].replace('*', '\*')
449 |
450 | roles = {'func': 'function',
451 | 'meth': 'method'}
452 |
453 | if self._role:
454 | if not roles.has_key(self._role):
455 | print "Warning: invalid role %s" % self._role
456 | out += '.. %s:: %s\n \n\n' % (roles.get(self._role,''),
457 | func_name)
458 |
459 | out += super(FunctionDoc, self).__str__(func_role=self._role)
460 | return out
461 |
462 |
463 | class ClassDoc(NumpyDocString):
464 | def __init__(self,cls,modulename='',func_doc=FunctionDoc,doc=None):
465 | if not inspect.isclass(cls):
466 | raise ValueError("Initialise using a class. Got %r" % cls)
467 | self._cls = cls
468 |
469 | if modulename and not modulename.endswith('.'):
470 | modulename += '.'
471 | self._mod = modulename
472 | self._name = cls.__name__
473 | self._func_doc = func_doc
474 |
475 | if doc is None:
476 | doc = pydoc.getdoc(cls)
477 |
478 | NumpyDocString.__init__(self, doc)
479 |
480 | @property
481 | def methods(self):
482 | return [name for name,func in inspect.getmembers(self._cls)
483 | if not name.startswith('_') and callable(func)]
484 |
485 | def __str__(self):
486 | out = ''
487 | out += super(ClassDoc, self).__str__()
488 | out += "\n\n"
489 |
490 | #for m in self.methods:
491 | # print "Parsing `%s`" % m
492 | # out += str(self._func_doc(getattr(self._cls,m), 'meth')) + '\n\n'
493 | # out += '.. index::\n single: %s; %s\n\n' % (self._name, m)
494 |
495 | return out
496 |
497 |
498 |
--------------------------------------------------------------------------------
/docs/sphinxext/apigen.py:
--------------------------------------------------------------------------------
1 | """Attempt to generate templates for module reference with Sphinx
2 |
3 | XXX - we exclude extension modules
4 |
5 | To include extension modules, first identify them as valid in the
6 | ``_uri2path`` method, then handle them in the ``_parse_module`` script.
7 |
8 | We get functions and classes by parsing the text of .py files.
9 | Alternatively we could import the modules for discovery, and we'd have
10 | to do that for extension modules. This would involve changing the
11 | ``_parse_module`` method to work via import and introspection, and
12 | might involve changing ``discover_modules`` (which determines which
13 | files are modules, and therefore which module URIs will be passed to
14 | ``_parse_module``).
15 |
16 | NOTE: this is a modified version of a script originally shipped with the
17 | PyMVPA project, which we've adapted for NIPY use. PyMVPA is an MIT-licensed
18 | project."""
19 |
20 | # Stdlib imports
21 | import os
22 | import re
23 |
24 | # Functions and classes
25 | class ApiDocWriter(object):
26 | ''' Class for automatic detection and parsing of API docs
27 | to Sphinx-parsable reST format'''
28 |
29 | # only separating first two levels
30 | rst_section_levels = ['*', '=', '-', '~', '^']
31 |
32 | def __init__(self,
33 | package_name,
34 | rst_extension='.rst',
35 | package_skip_patterns=None,
36 | module_skip_patterns=None,
37 | ):
38 | ''' Initialize package for parsing
39 |
40 | Parameters
41 | ----------
42 | package_name : string
43 | Name of the top-level package. *package_name* must be the
44 | name of an importable package
45 | rst_extension : string, optional
46 | Extension for reST files, default '.rst'
47 | package_skip_patterns : None or sequence of {strings, regexps}
48 | Sequence of strings giving URIs of packages to be excluded
49 | Operates on the package path, starting at (including) the
50 | first dot in the package path, after *package_name* - so,
51 | if *package_name* is ``sphinx``, then ``sphinx.util`` will
52 | result in ``.util`` being passed for earching by these
53 | regexps. If is None, gives default. Default is:
54 | ['\.tests$']
55 | module_skip_patterns : None or sequence
56 | Sequence of strings giving URIs of modules to be excluded
57 | Operates on the module name including preceding URI path,
58 | back to the first dot after *package_name*. For example
59 | ``sphinx.util.console`` results in the string to search of
60 | ``.util.console``
61 | If is None, gives default. Default is:
62 | ['\.setup$', '\._']
63 | '''
64 | if package_skip_patterns is None:
65 | package_skip_patterns = ['\\.tests$']
66 | if module_skip_patterns is None:
67 | module_skip_patterns = ['\\.setup$', '\\._']
68 | self.package_name = package_name
69 | self.rst_extension = rst_extension
70 | self.package_skip_patterns = package_skip_patterns
71 | self.module_skip_patterns = module_skip_patterns
72 |
73 | def get_package_name(self):
74 | return self._package_name
75 |
76 | def set_package_name(self, package_name):
77 | ''' Set package_name
78 |
79 | >>> docwriter = ApiDocWriter('sphinx')
80 | >>> import sphinx
81 | >>> docwriter.root_path == sphinx.__path__[0]
82 | True
83 | >>> docwriter.package_name = 'docutils'
84 | >>> import docutils
85 | >>> docwriter.root_path == docutils.__path__[0]
86 | True
87 | '''
88 | # It's also possible to imagine caching the module parsing here
89 | self._package_name = package_name
90 | self.root_module = __import__(package_name)
91 | self.root_path = self.root_module.__path__[0]
92 | self.written_modules = None
93 |
94 | package_name = property(get_package_name, set_package_name, None,
95 | 'get/set package_name')
96 |
97 | def _get_object_name(self, line):
98 | ''' Get second token in line
99 | >>> docwriter = ApiDocWriter('sphinx')
100 | >>> docwriter._get_object_name(" def func(): ")
101 | 'func'
102 | >>> docwriter._get_object_name(" class Klass(object): ")
103 | 'Klass'
104 | >>> docwriter._get_object_name(" class Klass: ")
105 | 'Klass'
106 | '''
107 | name = line.split()[1].split('(')[0].strip()
108 | # in case we have classes which are not derived from object
109 | # ie. old style classes
110 | return name.rstrip(':')
111 |
112 | def _uri2path(self, uri):
113 | ''' Convert uri to absolute filepath
114 |
115 | Parameters
116 | ----------
117 | uri : string
118 | URI of python module to return path for
119 |
120 | Returns
121 | -------
122 | path : None or string
123 | Returns None if there is no valid path for this URI
124 | Otherwise returns absolute file system path for URI
125 |
126 | Examples
127 | --------
128 | >>> docwriter = ApiDocWriter('sphinx')
129 | >>> import sphinx
130 | >>> modpath = sphinx.__path__[0]
131 | >>> res = docwriter._uri2path('sphinx.builder')
132 | >>> res == os.path.join(modpath, 'builder.py')
133 | True
134 | >>> res = docwriter._uri2path('sphinx')
135 | >>> res == os.path.join(modpath, '__init__.py')
136 | True
137 | >>> docwriter._uri2path('sphinx.does_not_exist')
138 |
139 | '''
140 | if uri == self.package_name:
141 | return os.path.join(self.root_path, '__init__.py')
142 | path = uri.replace('.', os.path.sep)
143 | path = path.replace(self.package_name + os.path.sep, '')
144 | path = os.path.join(self.root_path, path)
145 | # XXX maybe check for extensions as well?
146 | if os.path.exists(path + '.py'): # file
147 | path += '.py'
148 | elif os.path.exists(os.path.join(path, '__init__.py')):
149 | path = os.path.join(path, '__init__.py')
150 | else:
151 | return None
152 | return path
153 |
154 | def _path2uri(self, dirpath):
155 | ''' Convert directory path to uri '''
156 | relpath = dirpath.replace(self.root_path, self.package_name)
157 | if relpath.startswith(os.path.sep):
158 | relpath = relpath[1:]
159 | return relpath.replace(os.path.sep, '.')
160 |
161 | def _parse_module(self, uri):
162 | ''' Parse module defined in *uri* '''
163 | filename = self._uri2path(uri)
164 | if filename is None:
165 | # nothing that we could handle here.
166 | return ([],[])
167 | f = open(filename, 'rt')
168 | functions, classes = self._parse_lines(f)
169 | f.close()
170 | return functions, classes
171 |
172 | def _parse_lines(self, linesource):
173 | ''' Parse lines of text for functions and classes '''
174 | functions = []
175 | classes = []
176 | for line in linesource:
177 | if line.startswith('def ') and line.count('('):
178 | # exclude private stuff
179 | name = self._get_object_name(line)
180 | if not name.startswith('_'):
181 | functions.append(name)
182 | elif line.startswith('class '):
183 | # exclude private stuff
184 | name = self._get_object_name(line)
185 | if not name.startswith('_'):
186 | classes.append(name)
187 | else:
188 | pass
189 | functions.sort()
190 | classes.sort()
191 | return functions, classes
192 |
193 | def generate_api_doc(self, uri):
194 | '''Make autodoc documentation template string for a module
195 |
196 | Parameters
197 | ----------
198 | uri : string
199 | python location of module - e.g 'sphinx.builder'
200 |
201 | Returns
202 | -------
203 | S : string
204 | Contents of API doc
205 | '''
206 | # get the names of all classes and functions
207 | functions, classes = self._parse_module(uri)
208 | if not len(functions) and not len(classes):
209 | print 'WARNING: Empty -',uri # dbg
210 | return ''
211 |
212 | # Make a shorter version of the uri that omits the package name for
213 | # titles
214 | uri_short = re.sub(r'^%s\.' % self.package_name,'',uri)
215 |
216 | ad = '.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n'
217 |
218 | chap_title = uri_short
219 | ad += (chap_title+'\n'+ self.rst_section_levels[1] * len(chap_title)
220 | + '\n\n')
221 |
222 | # Set the chapter title to read 'module' for all modules except for the
223 | # main packages
224 | if '.' in uri:
225 | title = 'Module: :mod:`' + uri_short + '`'
226 | else:
227 | title = ':mod:`' + uri_short + '`'
228 | ad += title + '\n' + self.rst_section_levels[2] * len(title)
229 |
230 | if len(classes):
231 | ad += '\nInheritance diagram for ``%s``:\n\n' % uri
232 | ad += '.. inheritance-diagram:: %s \n' % uri
233 | ad += ' :parts: 3\n'
234 |
235 | ad += '\n.. automodule:: ' + uri + '\n'
236 | ad += '\n.. currentmodule:: ' + uri + '\n'
237 | multi_class = len(classes) > 1
238 | multi_fx = len(functions) > 1
239 | if multi_class:
240 | ad += '\n' + 'Classes' + '\n' + \
241 | self.rst_section_levels[2] * 7 + '\n'
242 | elif len(classes) and multi_fx:
243 | ad += '\n' + 'Class' + '\n' + \
244 | self.rst_section_levels[2] * 5 + '\n'
245 | for c in classes:
246 | ad += '\n:class:`' + c + '`\n' \
247 | + self.rst_section_levels[multi_class + 2 ] * \
248 | (len(c)+9) + '\n\n'
249 | ad += '\n.. autoclass:: ' + c + '\n'
250 | # must NOT exclude from index to keep cross-refs working
251 | ad += ' :members:\n' \
252 | ' :undoc-members:\n' \
253 | ' :show-inheritance:\n' \
254 | ' :inherited-members:\n' \
255 | '\n' \
256 | ' .. automethod:: __init__\n'
257 | if multi_fx:
258 | ad += '\n' + 'Functions' + '\n' + \
259 | self.rst_section_levels[2] * 9 + '\n\n'
260 | elif len(functions) and multi_class:
261 | ad += '\n' + 'Function' + '\n' + \
262 | self.rst_section_levels[2] * 8 + '\n\n'
263 | for f in functions:
264 | # must NOT exclude from index to keep cross-refs working
265 | ad += '\n.. autofunction:: ' + uri + '.' + f + '\n\n'
266 | return ad
267 |
268 | def _survives_exclude(self, matchstr, match_type):
269 | ''' Returns True if *matchstr* does not match patterns
270 |
271 | ``self.package_name`` removed from front of string if present
272 |
273 | Examples
274 | --------
275 | >>> dw = ApiDocWriter('sphinx')
276 | >>> dw._survives_exclude('sphinx.okpkg', 'package')
277 | True
278 | >>> dw.package_skip_patterns.append('^\\.badpkg$')
279 | >>> dw._survives_exclude('sphinx.badpkg', 'package')
280 | False
281 | >>> dw._survives_exclude('sphinx.badpkg', 'module')
282 | True
283 | >>> dw._survives_exclude('sphinx.badmod', 'module')
284 | True
285 | >>> dw.module_skip_patterns.append('^\\.badmod$')
286 | >>> dw._survives_exclude('sphinx.badmod', 'module')
287 | False
288 | '''
289 | if match_type == 'module':
290 | patterns = self.module_skip_patterns
291 | elif match_type == 'package':
292 | patterns = self.package_skip_patterns
293 | else:
294 | raise ValueError('Cannot interpret match type "%s"'
295 | % match_type)
296 | # Match to URI without package name
297 | L = len(self.package_name)
298 | if matchstr[:L] == self.package_name:
299 | matchstr = matchstr[L:]
300 | for pat in patterns:
301 | try:
302 | pat.search
303 | except AttributeError:
304 | pat = re.compile(pat)
305 | if pat.search(matchstr):
306 | return False
307 | return True
308 |
309 | def discover_modules(self):
310 | ''' Return module sequence discovered from ``self.package_name``
311 |
312 |
313 | Parameters
314 | ----------
315 | None
316 |
317 | Returns
318 | -------
319 | mods : sequence
320 | Sequence of module names within ``self.package_name``
321 |
322 | Examples
323 | --------
324 | >>> dw = ApiDocWriter('sphinx')
325 | >>> mods = dw.discover_modules()
326 | >>> 'sphinx.util' in mods
327 | True
328 | >>> dw.package_skip_patterns.append('\.util$')
329 | >>> 'sphinx.util' in dw.discover_modules()
330 | False
331 | >>>
332 | '''
333 | modules = [self.package_name]
334 | # raw directory parsing
335 | for dirpath, dirnames, filenames in os.walk(self.root_path):
336 | # Check directory names for packages
337 | root_uri = self._path2uri(os.path.join(self.root_path,
338 | dirpath))
339 | for dirname in dirnames[:]: # copy list - we modify inplace
340 | package_uri = '.'.join((root_uri, dirname))
341 | if (self._uri2path(package_uri) and
342 | self._survives_exclude(package_uri, 'package')):
343 | modules.append(package_uri)
344 | else:
345 | dirnames.remove(dirname)
346 | # Check filenames for modules
347 | for filename in filenames:
348 | module_name = filename[:-3]
349 | module_uri = '.'.join((root_uri, module_name))
350 | if (self._uri2path(module_uri) and
351 | self._survives_exclude(module_uri, 'module')):
352 | modules.append(module_uri)
353 | return sorted(modules)
354 |
355 | def write_modules_api(self, modules,outdir):
356 | # write the list
357 | written_modules = []
358 | for m in modules:
359 | api_str = self.generate_api_doc(m)
360 | if not api_str:
361 | continue
362 | # write out to file
363 | outfile = os.path.join(outdir,
364 | m + self.rst_extension)
365 | fileobj = open(outfile, 'wt')
366 | fileobj.write(api_str)
367 | fileobj.close()
368 | written_modules.append(m)
369 | self.written_modules = written_modules
370 |
371 | def write_api_docs(self, outdir):
372 | """Generate API reST files.
373 |
374 | Parameters
375 | ----------
376 | outdir : string
377 | Directory name in which to store files
378 | We create automatic filenames for each module
379 |
380 | Returns
381 | -------
382 | None
383 |
384 | Notes
385 | -----
386 | Sets self.written_modules to list of written modules
387 | """
388 | if not os.path.exists(outdir):
389 | os.mkdir(outdir)
390 | # compose list of modules
391 | modules = self.discover_modules()
392 | self.write_modules_api(modules,outdir)
393 |
394 | def write_index(self, outdir, froot='gen', relative_to=None):
395 | """Make a reST API index file from written files
396 |
397 | Parameters
398 | ----------
399 | path : string
400 | Filename to write index to
401 | outdir : string
402 | Directory to which to write generated index file
403 | froot : string, optional
404 | root (filename without extension) of filename to write to
405 | Defaults to 'gen'. We add ``self.rst_extension``.
406 | relative_to : string
407 | path to which written filenames are relative. This
408 | component of the written file path will be removed from
409 | outdir, in the generated index. Default is None, meaning,
410 | leave path as it is.
411 | """
412 | if self.written_modules is None:
413 | raise ValueError('No modules written')
414 | # Get full filename path
415 | path = os.path.join(outdir, froot+self.rst_extension)
416 | # Path written into index is relative to rootpath
417 | if relative_to is not None:
418 | relpath = outdir.replace(relative_to + os.path.sep, '')
419 | else:
420 | relpath = outdir
421 | idx = open(path,'wt')
422 | w = idx.write
423 | w('.. AUTO-GENERATED FILE -- DO NOT EDIT!\n\n')
424 | w('.. toctree::\n\n')
425 | for f in self.written_modules:
426 | w(' %s\n' % os.path.join(relpath,f))
427 | idx.close()
428 |
--------------------------------------------------------------------------------