├── .circleci
└── config.yml
├── .gitignore
├── Cufflinks Tutorial - Chart Gallery.ipynb
├── Cufflinks Tutorial - Colors.ipynb
├── Cufflinks Tutorial - Offline.ipynb
├── Cufflinks Tutorial - Pandas Like.ipynb
├── Cufflinks Tutorial - Plotly.ipynb
├── Cufflinks Tutorial - Quantfig.ipynb
├── MANIFEST.in
├── README.md
├── cufflinks
├── __init__.py
├── auth.py
├── colors.py
├── datagen.py
├── date_tools.py
├── exceptions.py
├── extract.py
├── helper.py
├── offline.py
├── pandastools.py
├── plotlytools.py
├── quant_figure.py
├── ta.py
├── themes.py
├── tools.py
├── utils.py
└── version.py
├── data
├── choropleth.csv
└── scattergeo.csv
├── helper
└── params.json
├── img
├── Tutorial Image.png
├── qf1.png
├── qf2.png
└── ukswaps.gif
├── license.txt
├── requirements.txt
├── setup.cfg
├── setup.py
└── tests.py
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | python-2.7-core:
4 | docker:
5 | - image: circleci/python:2.7-stretch-node-browsers
6 | environment:
7 | PLOTLY_TOX_PYTHON_34: python2.7
8 |
9 | steps:
10 | - checkout
11 | - run:
12 | name: Install core requirements
13 | command: 'sudo pip install -r ~/project/requirements.txt'
14 | - run:
15 | name: Install nose
16 | command: 'sudo pip install nose coverage'
17 | - run:
18 | name: Run tests
19 | command: 'nosetests -xv tests.py --with-coverage --cover-package=cufflinks'
20 |
21 | python-3.5-core:
22 | docker:
23 | - image: circleci/python:3.5-stretch-node-browsers
24 | environment:
25 | PLOTLY_TOX_PYTHON_34: python3.5
26 |
27 | steps:
28 | - checkout
29 | - run:
30 | name: Install core requirements
31 | command: 'sudo pip install -r ~/project/requirements.txt'
32 | - run:
33 | name: Install nose
34 | command: 'sudo pip install nose coverage'
35 | - run:
36 | name: Run tests
37 | command: 'nosetests -xv tests.py --with-coverage --cover-package=cufflinks'
38 |
39 | workflows:
40 | version: 2
41 | build:
42 | jobs:
43 | - python-2.7-core
44 | - python-3.5-core
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # PyInstaller
26 | # Usually these files are written by a python script from a template
27 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
28 | *.manifest
29 | *.spec
30 |
31 | # Installer logs
32 | pip-log.txt
33 | pip-delete-this-directory.txt
34 |
35 | # Unit test / coverage reports
36 | htmlcov/
37 | .tox/
38 | .coverage
39 | .cache
40 | nosetests.xml
41 | coverage.xml
42 |
43 | # Translations
44 | *.mo
45 | *.pot
46 |
47 | # Django stuff:
48 | *.log
49 |
50 | # Sphinx documentation
51 | docs/_build/
52 |
53 | # PyBuilder
54 | target/
55 |
56 | # iPython Checkpoints
57 | .ipynb_checkpoints/
58 |
59 | # vscode
60 | .vscode/
61 |
62 | .DS_Store
63 |
64 | vv/*
65 |
66 | Untitled.ipynb
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include helper/*.json
2 | include license.txt
3 | include requirements.txt
4 | include tests.py
5 | graft data
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Cufflinks
3 |
4 | This library binds the power of [plotly](http://www.plot.ly) with the flexibility of [pandas](http://pandas.pydata.org/) for easy plotting.
5 |
6 | This library is available on [https://github.com/santosjorge/cufflinks](https://github.com/santosjorge/cufflinks)
7 |
8 | This tutorial assumes that the plotly user credentials have already been configured as stated on the [getting started](https://plot.ly/python/getting-started/) guide.
9 |
10 | ### Tutorials:
11 |
12 | * [Chart Gallery](http://nbviewer.ipython.org/gist/santosjorge/b278ce0ae2448f47c31d)
13 | * [Pandas Like Visualization](http://nbviewer.ipython.org/gist/santosjorge/aba934a0d20023a136c2)
14 | * [The Basics](http://nbviewer.ipython.org/gist/santosjorge/f3b07b2be8094deea8c6)
15 | * [Color Management](http://nbviewer.ipython.org/gist/santosjorge/00ca17b121fa2463e18b)
16 | * [Offline Mode](http://nbviewer.ipython.org/gist/santosjorge/5fdbe947496faf7af5e6)
17 |
18 | 
19 |
20 | ### Release Notes
21 |
22 | ### v0.17.0
23 | Support for Plotly 4.x
24 | Cufflinks is no longer compatible with Plotly 3.x
25 |
26 | ### v0.14.0
27 | Support for Plotly 3.0
28 |
29 | ### v0.13.0
30 | New `iplot` helper.
31 | To see a comprehensive list of parameters
32 | **cf.help()**
33 |
34 | ```python
35 | # For a list of supported figures
36 | cf.help()
37 | # Or to see the parameters supported that apply to a given figure try
38 | cf.help('scatter')
39 | cf.help('candle') #etc
40 | ```
41 |
42 | ### v0.12.0
43 |
44 | Removed dependecies on ta-lib.
45 | This library is no longer required.
46 | All studies have be rewritten in Python.
47 |
48 | ### v0.11.0
49 |
50 | * `QuantFigure` is a new class that will generate a graph object with persistence.
51 | Parameters can be added/modified at any given point.
52 |
53 | This can be as easy as:
54 |
55 | ```python
56 | df=cf.datagen.ohlc()
57 | qf=cf.QuantFig(df,title='First Quant Figure',legend='top',name='GS')
58 | qf.add_bollinger_bands()
59 | qf.iplot()
60 |
61 | ```
62 |
63 | 
64 |
65 | * **Technical Analysis Studies** can be added on demand.
66 |
67 | ```python
68 | qf.add_sma([10,20],width=2,color=['green','lightgreen'],legendgroup=True)
69 | qf.add_rsi(periods=20,color='java')
70 | qf.add_bollinger_bands(periods=20,boll_std=2,colors=['magenta','grey'],fill=True)
71 | qf.add_volume()
72 | qf.add_macd()
73 | qf.iplot()
74 | ```
75 |
76 | 
77 |
78 |
79 | ### v0.10.0
80 |
81 | * `rangeslider` to display a date range slider at the bottom
82 | * `cf.datagen.ohlc().iplot(kind='candle',rangeslider=True)`
83 | * `rangeselector` to display buttons to change the date range displayed
84 | * `cf.datagen.ohlc(500).iplot(kind='candle', rangeselector={ 'steps':['1y','2 months','5 weeks','ytd','2mtd','reset'],
85 | 'bgcolor' : ('grey',.3), 'x': 0.3 , 'y' : 0.95})`
86 | * Customise annotions, with `fontsize`,`fontcolor`,`textangle`
87 | * Label mode
88 | * `cf.datagen.lines(1,mode='stocks').iplot(kind='line',
89 | annotations={'2015-02-02':'Market Crash',
90 | '2015-03-01':'Recovery'},
91 | textangle=-70,fontsize=13,fontcolor='grey')`
92 | * Explicit mode
93 | * `cf.datagen.lines(1,mode='stocks').iplot(kind='line',
94 | annotations=[{'text':'exactly here','x':'0.2',
95 | 'xref':'paper','arrowhead':2,
96 | 'textangle':-10,'ay':150,'arrowcolor':'red'}])`
97 |
98 | ### v0.9.0
99 |
100 | * `Figure.iplot()` to plot figures
101 | * New high performing **candle** and **ohlc** plots
102 | * `cf.datagen.ohlc().iplot(kind='candle')`
103 |
104 |
105 | ### v0.8.0
106 |
107 | * 'cf.datagen.choropleth()' to for sample choropleth data.
108 | * 'cf.datagen.scattergeo()' to for sample scattergeo data.
109 | * Support for choropleth and scattergeo figures in `iplot`
110 | * 'cf.get_colorscale' for maps and plotly objects that support colorscales
111 |
112 | ### v0.7.1
113 |
114 | * `xrange`, `yrange` and `zrange` can be specified in `iplot` and `getLayout`
115 | * `cf.datagen.lines(1).iplot(yrange=[5,15])`
116 | * `layout_update` can be set in `iplot` and `getLayout` to explicitly update any `Layout` value
117 |
118 | ### v0.7
119 |
120 | * Support for Python 3
121 |
122 | ### v0.6
123 | [See the IPython Notebook](http://nbviewer.ipython.org/gist/santosjorge/72665839a6f05a0567e0?flush_cache=true)
124 |
125 | * Support for **pie** charts
126 | * `cf.datagen.pie().iplot(kind='pie',labels='labels',values='values')`
127 | * Generate Open, High, Low, Close data
128 | * `datagen.ohlc()`
129 | * Candle Charts support
130 | * `ohlc=cf.datagen.ohlc()`
131 | `ohlc.iplot(kind='candle',up_color='blue',down_color='red')`
132 | * OHLC (Bar) Charts support
133 | * `ohlc=cf.datagen.ohlc()`
134 | `ohlc.iplot(kind='ohlc',up_color='blue',down_color='red')`
135 | * Support for logarithmic charts ( logx | logy )
136 | * `df=pd.DataFrame([x**2] for x in range(100))`
137 | `df.iplot(kind='lines',logy=True)`
138 | * Support for MulitIndex DataFrames
139 | * Support for Error Bars ( error_x | error_y )
140 | * `cf.datagen.lines(1,5).iplot(kind='bar',error_y=[1,2,3.5,2,2])`
141 | * `cf.datagen.lines(1,5).iplot(kind='bar',error_y=20, error_type='percent')`
142 | * Support for continuous error bars
143 | * `cf.datagen.lines(1).iplot(kind='lines',error_y=20,error_type='continuous_percent')`
144 | * `cf.datagen.lines(1).iplot(kind='lines',error_y=10,error_type='continuous',color='blue')`
145 | * **Technical Analysis Studies for Timeseries** *(beta)*
146 | * Simple Moving Averages (SMA)
147 | * `cf.datagen.lines(1,500).ta_plot(study='sma',periods=[13,21,55])`
148 | * Relative Strength Indicator (RSI)
149 | * `cf.datagen.lines(1,200).ta_plot(study='boll',periods=14)`
150 | * Bollinger Bands (BOLL)
151 | * `cf.datagen.lines(1,200).ta_plot(study='rsi',periods=14)`
152 | * Moving Average Convergence Divergence (MACD)
153 | * `cf.datagen.lines(1,200).ta_plot(study='macd',fast_period=12,slow_period=26,
154 | signal_period=9)`
155 |
156 |
157 |
158 | ### v0.5
159 |
160 | * Support of offline charts
161 | * `cf.go_offline()`
162 | * `cf.go_online()`
163 | * `cf.iplot(figure,online=True)` (To force online whilst on offline mode)
164 | * Support for secondary axis
165 | * `fig=cf.datagen.lines(3,columns=['a','b','c']).figure()`
166 | `fig=fig.set_axis('b',side='right')`
167 | `cf.iplot(fig)`
168 |
169 |
170 | ### v0.4
171 |
172 | * Support for global theme setting
173 | * `cufflinks.set_config_file(theme='pearl')`
174 | * New theme *ggplot*
175 | * `cufflinks.datagen.lines(5).iplot(theme='ggplot')`
176 | * Support for horizontal bar charts *barh*
177 | * `cufflinks.datagen.lines(2).iplot(kind='barh',barmode='stack',bargap=.1)`
178 | * Support for histogram orientation and normalization
179 | * `cufflinks.datagen.histogram().iplot(kind='histogram',orientation='h',norm='probability')`
180 | * Support for *area* plots
181 | * `cufflinks.datagen.lines(4).iplot(kind='area',fill=True,opacity=1)`
182 | * Support for *subplots*
183 | * `cufflinks.datagen.histogram(4).iplot(kind='histogram',subplots=True,bins=50)`
184 | * `cufflinks.datagen.lines(4).iplot(subplots=True,shape=(4,1),shared_xaxes=True,vertical_spacing=.02,fill=True)`
185 | * Support for *scatter matrix* to display the distribution amongst every series in the DataFrame
186 | * `cufflinks.datagen.lines(4,1000).scatter_matrix()`
187 | * Support for *vline* and *hline* for horizontal and vertical lines
188 | * `cufflinks.datagen.lines(3).iplot(hline=[2,3])`
189 | * `cufflinks.datagen.lines(3).iplot(hline=dict(y=2,color='blue',width=3))`
190 | * Support for *vspan* and *hspan* for horizontal and vertical areas
191 | * `cufflinks.datagen.lines(3).iplot(hspan=(-1,2))`
192 | * `cufflinks.datagen.lines(3).iplot(hspan=dict(y0=-1,y1=2,color='orange',fill=True,opacity=.4))`
193 |
194 |
195 | ### v0.3.2
196 |
197 | * Global setting for public charts
198 | * `cufflinks.set_config_file(world_readable=True)`
199 |
200 | #### v0.3
201 |
202 | * Enhanced Spread charts
203 | * `cufflinks.datagen.lines(2).iplot(kind='spread')`
204 | * Support for Heatmap charts
205 | * `cufflinks.datagen.heatmap().iplot(kind='heatmap')`
206 | * Support for Bubble charts
207 | * `cufflinks.datagen.bubble(4).iplot(kind='bubble',x='x',y='y',text='text',size='size',categories='categories')`
208 | * Support for Bubble3d charts
209 | * `cufflinks.datagen.bubble3d(4).iplot(kind='bubble3d',x='x',y='y',z='z',text='text',size='size',categories='categories')`
210 | * Support for Box charts
211 | * `cufflinks.datagen.box().iplot(kind='box')`
212 | * Support for Surface charts
213 | * `cufflinks.datagen.surface().iplot(kind='surface')`
214 | * Support for Scatter3d charts
215 | * `cufflinks.datagen.scatter3d().iplot(kind='scatter3d',x='x',y='y',z='z',text='text',categories='categories')`
216 | * Support for Histograms
217 | * `cufflinks.datagen.histogram(2).iplot(kind='histogram')`
218 | * Data generation for most common plot types
219 | * `cufflinks.datagen`
220 | * Data extraction: Extract data from any Plotly chart. Data is delivered in DataFrame
221 | * `cufflinks.to_df(Figure)`
222 | * Integration with [colorlover](https://github.com/jackparmer/colorlover/)
223 | * Support for scales `iplot(colorscale='accent')` to plot a chart using an *accent* color scale
224 | * cufflinks.scales() to see all available scales
225 | * Support for named colors
226 | * `iplot(colors=['pink','red','yellow'])`
227 |
228 |
--------------------------------------------------------------------------------
/cufflinks/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 |
3 | A productivity tool that binds pandas and plotly.
4 | Kudos to the Plotly team!
5 |
6 | Author: @jorgesantos
7 |
8 | """
9 | from __future__ import absolute_import
10 |
11 | from . import date_tools
12 | from . import utils
13 | from . import datagen
14 | from . import helper
15 | from . import tools
16 | from . import colors
17 | from . import pandastools
18 | from . import ta
19 | from . import themes
20 |
21 | from .helper import _printer as help
22 | from .plotlytools import *
23 | from plotly.graph_objs import *
24 | from .colors import cnames, get_colorscale
25 | from .utils import pp
26 | from .tools import subplots,scatter_matrix,figures,getLayout,getThemes,getTheme
27 | from .extract import to_df
28 | from .auth import set_config_file,get_config_file
29 | from .quant_figure import QuantFig
30 | from .offline import is_offline,go_offline,go_online
31 | from .version import __version__
32 |
33 | try:
34 | if get_config_file()['offline']:
35 | go_offline()
36 | else:
37 | go_online()
38 | except:
39 | pass
40 |
41 | # User Defined Colors
42 |
43 | try:
44 | colors.cnames = utils.merge_dict(colors.cnames,auth.get_user_colors())
45 | colors._scales_names = utils.merge_dict(colors._scales_names,auth.get_user_scales())
46 | themes.THEMES = utils.merge_dict(themes.THEMES,auth.get_user_themes())
47 | except:
48 | pass
--------------------------------------------------------------------------------
/cufflinks/auth.py:
--------------------------------------------------------------------------------
1 | """
2 | Based in Plotly's tools module
3 | """
4 |
5 |
6 | import os
7 | import json
8 | import warnings
9 | from .offline import go_offline
10 |
11 | package='cufflinks'
12 |
13 | if os.path.exists(os.path.join(os.path.expanduser('~'),os.path.join('Dropbox','AppData'))):
14 | AUTH_DIR=os.path.join(os.path.join(os.path.expanduser('~'),os.path.join('Dropbox','AppData')),'.'+package)
15 | else:
16 | AUTH_DIR = os.path.join(os.path.expanduser("~"), "."+package)
17 |
18 | CREDENTIALS_FILE = os.path.join(AUTH_DIR, ".credentials")
19 | CONFIG_FILE = os.path.join(AUTH_DIR, ".config")
20 | TEST_DIR = os.path.join(os.path.expanduser("~"), ".test")
21 | PICKLE_FILE=os.path.join(AUTH_DIR,".pickle")
22 | TEST_FILE = os.path.join(AUTH_DIR, ".permission_test")
23 | COLORS_FILE = os.path.join(AUTH_DIR, "colors.json")
24 | SCALES_FILE = os.path.join(AUTH_DIR, "scales.json")
25 | THEMES_FILE = os.path.join(AUTH_DIR, "themes.json")
26 |
27 | _FILE_CONTENT = {
28 | CONFIG_FILE: {
29 | "sharing" : "public",
30 | "theme" : "pearl",
31 | "colorscale" : "dflt",
32 | "offline" : False,
33 | "offline_connected" : True,
34 | "offline_url":'',
35 | "offline_show_link" : True,
36 | "offline_link_text" : 'Export to plot.ly',
37 | "datagen_mode" : 'stocks',
38 | "dimensions" : None,
39 | "margin" : None,
40 | "offline_config" : None
41 | }
42 | }
43 |
44 | try:
45 | os.mkdir(TEST_DIR)
46 | os.rmdir(TEST_DIR)
47 | if not os.path.exists(AUTH_DIR):
48 | os.mkdir(AUTH_DIR)
49 | f = open(TEST_FILE, 'w')
50 | f.write('testing\n')
51 | f.close()
52 | os.remove(TEST_FILE)
53 | _file_permissions = True
54 | except:
55 | _file_permissions = False
56 |
57 |
58 | def get_path():
59 | return AUTH_DIR
60 |
61 | def get_pickle_path():
62 | return PICKLE_FILE
63 |
64 | def check_file_permissions():
65 | return _file_permissions
66 |
67 | def ensure_local_files():
68 | """
69 | Ensure that filesystem is setup/filled out in a valid way
70 | """
71 | if _file_permissions:
72 | if not os.path.isdir(AUTH_DIR):
73 | os.mkdir(AUTH_DIR)
74 | for fn in [CONFIG_FILE]:
75 | contents = load_json_dict(fn)
76 | for key, val in list(_FILE_CONTENT[fn].items()):
77 | if key not in contents:
78 | contents[key] = val
79 | contents_keys = list(contents.keys())
80 | for key in contents_keys:
81 | if key not in _FILE_CONTENT[fn]:
82 | del contents[key]
83 | save_json_dict(fn, contents)
84 | else:
85 | warnings.warn("Looks like you don't have 'read-write' permission to "
86 | "your 'home' ('~') directory")
87 |
88 |
89 | def set_config_file(sharing=None,theme=None,colorscale=None,offline=None,offline_connected=None,
90 | offline_url=None,offline_show_link=None,offline_link_text=None,
91 | offline_config=None,
92 | datagen_mode=None,**kwargs):
93 | """
94 | Set the keyword-value pairs in `~/.config`.
95 |
96 | sharing : string
97 | Sets the sharing level permission
98 | public - anyone can see this chart
99 | private - only you can see this chart
100 | secret - only people with the link can see the chart
101 | theme : string
102 | Sets the default theme
103 | See cufflinks.getThemes() for available themes
104 | colorscale : string
105 | Sets the default colorscale
106 | See cufflinks.scales()
107 | offline : bool
108 | If true then the charts are rendered
109 | locally.
110 | offline_connected : bool
111 | If True, the plotly.js library will be loaded
112 | from an online CDN. If False, the plotly.js library will be loaded locally
113 | from the plotly python package
114 | offline_show_link : bool
115 | If true then the chart will show a link to
116 | plot.ly at the bottom right of the chart
117 | offline_link_text : string
118 | Text to display as link at the bottom
119 | right of the chart
120 | offline_config : dict
121 | Additional configuration options
122 | For the complete list of config options check out:
123 | https://github.com/plotly/plotly.js/blob/master/src/plot_api/plot_config.js
124 | datagen_mode : string
125 | Mode in which the data is generated
126 | by the datagen module
127 | stocks : random stock names are used for the index
128 | abc : alphabet values are used for the index
129 | dimensions : tuple
130 | Sets the default (width,height) of the chart
131 | margin : dict or tuple
132 | Dictionary (l,r,b,t) or
133 | Tuple containing the left,
134 | right, bottom and top margins
135 | """
136 | if not _file_permissions:
137 | raise Exception("You don't have proper file permissions "
138 | "to run this function.")
139 | valid_kwargs=['world_readable','dimensions','margin','offline_config']
140 | for key in list(kwargs.keys()):
141 | if key not in valid_kwargs:
142 | raise Exception("Invalid keyword : '{0}'".format(key))
143 | if all(['world_readable' in kwargs,sharing is None]):
144 | sharing=kwargs['world_readable']
145 | if isinstance(sharing,bool):
146 | if sharing:
147 | sharing='public'
148 | else:
149 | sharing='private'
150 | config = get_config_file()
151 | if sharing is not None:
152 | config['sharing'] = sharing
153 | if theme:
154 | config['theme']=theme
155 | if colorscale:
156 | config['colorscale']=colorscale
157 | if offline_connected is not None:
158 | config['offline_connected']=offline_connected
159 | if offline is not None:
160 | config['offline']=offline
161 | if offline:
162 | go_offline()
163 | if datagen_mode:
164 | config['datagen_mode']=datagen_mode
165 | if offline_url:
166 | config['offline_url']=offline_url
167 | if offline_show_link is not None:
168 | config['offline_show_link']=offline_show_link
169 | if offline_link_text:
170 | config['offline_link_text']=offline_link_text
171 | if offline_config:
172 | config['offline_config']=offline_config
173 | for _ in valid_kwargs:
174 | if _ in kwargs:
175 | config[_]=kwargs[_]
176 | save_json_dict(CONFIG_FILE, config)
177 | ensure_local_files()
178 |
179 |
180 | def get_config_file(*args):
181 | """
182 | Return specified args from `~/.config`. as dict.
183 | Returns all if no arguments are specified.
184 |
185 | Example:
186 | get_config_file('sharing')
187 |
188 | """
189 | if _file_permissions:
190 | ensure_local_files()
191 | return load_json_dict(CONFIG_FILE, *args)
192 | else:
193 | return _FILE_CONTENT[CONFIG_FILE]
194 |
195 | def get_user_colors(*args):
196 | """
197 | Return specified args from `~/.colors`. as dict.
198 | Returns all if no arguments are specified.
199 |
200 | Example:
201 | get_colors_file('blue')
202 |
203 | """
204 | if _file_permissions:
205 | ensure_local_files()
206 | return load_json_dict(COLORS_FILE, *args)
207 | else:
208 | return _FILE_CONTENT[COLORS_FILE]
209 |
210 | def get_user_scales(*args):
211 | """
212 | Return specified args from `~/.scales`. as dict.
213 | Returns all if no arguments are specified.
214 |
215 | Example:
216 | get_scales_file('blues')
217 |
218 | """
219 | if _file_permissions:
220 | ensure_local_files()
221 | return load_json_dict(SCALES_FILE, *args)
222 | else:
223 | return _FILE_CONTENT[SCALES_FILE]
224 |
225 | def get_user_themes(*args):
226 | """
227 | Return specified args from `~/.themes`. as dict.
228 | Returns all if no arguments are specified.
229 |
230 | Example:
231 | get_themes_file('solar')
232 |
233 | """
234 | if _file_permissions:
235 | ensure_local_files()
236 | return load_json_dict(THEMES_FILE, *args)
237 | else:
238 | return _FILE_CONTENT[THEMES_FILE]
239 |
240 | def load_json_dict(filename, *args):
241 | """Checks if file exists. Returns {} if something fails."""
242 | data = {}
243 | if os.path.exists(filename):
244 | with open(filename, "r") as f:
245 | try:
246 | data = json.load(f)
247 | if not isinstance(data, dict):
248 | data = {}
249 | except:
250 | pass
251 | if args:
252 | return {key: data[key] for key in args if key in data}
253 | return data
254 |
255 |
256 | def save_json_dict(filename, json_dict):
257 | """Will error if filename is not appropriate, but it's checked elsewhere.
258 | """
259 | if isinstance(json_dict, dict):
260 | with open(filename, "w") as f:
261 | f.write(json.dumps(json_dict, indent=4))
262 | else:
263 | raise TypeError("json_dict was not a dictionary. couldn't save.")
--------------------------------------------------------------------------------
/cufflinks/colors.py:
--------------------------------------------------------------------------------
1 | ##
2 | # Special thanks to @krey for the python3 support
3 | ##
4 |
5 | import numpy as np
6 | import colorsys
7 | import colorlover as cl
8 | import operator
9 | import copy
10 |
11 | from collections import deque
12 | from six import string_types
13 | from IPython.display import HTML, display
14 |
15 | from .utils import inverseDict
16 | from .auth import get_config_file
17 |
18 |
19 | class CufflinksError(Exception):
20 | pass
21 |
22 |
23 | def to_rgba(color, alpha):
24 | """
25 | Converts from hex|rgb to rgba
26 |
27 | Parameters:
28 | -----------
29 | color : string
30 | Color representation on hex or rgb
31 | alpha : float
32 | Value from 0 to 1.0 that represents the
33 | alpha value.
34 |
35 | Example:
36 | to_rgba('#E1E5ED',0.6)
37 | to_rgba('#f03',0.7)
38 | to_rgba('rgb(23,23,23)',.5)
39 | """
40 | if type(color) == tuple:
41 | color, alpha = color
42 | color = color.lower()
43 | if 'rgba' in color:
44 | cl = list(eval(color.replace('rgba', '')))
45 | if alpha:
46 | cl[3] = alpha
47 | return 'rgba' + str(tuple(cl))
48 | elif 'rgb' in color:
49 | r, g, b = eval(color.replace('rgb', ''))
50 | return 'rgba' + str((r, g, b, alpha))
51 | else:
52 | return to_rgba(hex_to_rgb(color), alpha)
53 |
54 |
55 | def hex_to_rgb(color):
56 | """
57 | Converts from hex to rgb
58 |
59 | Parameters:
60 | -----------
61 | color : string
62 | Color representation on hex or rgb
63 |
64 | Example:
65 | hex_to_rgb('#E1E5ED')
66 | hex_to_rgb('#f03')
67 | """
68 | color = normalize(color)
69 | color = color[1:]
70 | # return 'rgb'+str(tuple(ord(c) for c in color.decode('hex')))
71 | return 'rgb' + str((int(color[0:2], base=16), int(color[2:4], base=16), int(color[4:6], base=16)))
72 |
73 |
74 | def normalize(color):
75 | """
76 | Returns an hex color
77 |
78 | Parameters:
79 | -----------
80 | color : string
81 | Color representation in rgba|rgb|hex
82 |
83 | Example:
84 | normalize('#f03')
85 | """
86 | if type(color) == tuple:
87 | color = to_rgba(*color)
88 | if 'rgba' in color:
89 | return rgb_to_hex(rgba_to_rgb(color))
90 | elif 'rgb' in color:
91 | return rgb_to_hex(color)
92 | elif '#' in color:
93 | if len(color) == 7:
94 | return color
95 | else:
96 | color = color[1:]
97 | return '#' + ''.join([x * 2 for x in list(color)])
98 | else:
99 | try:
100 | return normalize(cnames[color.lower()])
101 | except:
102 | raise CufflinksError('Not a valid color: ' + color)
103 |
104 |
105 | def rgb_to_hex(color):
106 | """
107 | Converts from rgb to hex
108 |
109 | Parameters:
110 | -----------
111 | color : string
112 | Color representation on hex or rgb
113 |
114 | Example:
115 | rgb_to_hex('rgb(23,25,24)')
116 | """
117 | rgb = eval(color.replace('rgb', ''))
118 | # return '#'+''.join(map(chr, rgb)).encode('hex')
119 | return '#' + ''.join(['{0:02x}'.format(x).upper() for x in rgb])
120 |
121 |
122 | def rgba_to_rgb(color, bg='rgb(255,255,255)'):
123 | """
124 | Converts from rgba to rgb
125 |
126 | Parameters:
127 | -----------
128 | color : string
129 | Color representation in rgba
130 | bg : string
131 | Color representation in rgb
132 |
133 | Example:
134 | rgba_to_rgb('rgb(23,25,24,.4)''
135 | """
136 | def c_tup(c):
137 | return eval(c[c.find('('):])
138 | color = c_tup(color)
139 | bg = hex_to_rgb(normalize(bg))
140 | bg = c_tup(bg)
141 | a = color[3]
142 | r = [int((1 - a) * bg[i] + a * color[i]) for i in range(3)]
143 | return 'rgb' + str(tuple(r))
144 |
145 |
146 | def hex_to_hsv(color):
147 | """
148 | Converts from hex to hsv
149 |
150 | Parameters:
151 | -----------
152 | color : string
153 | Color representation on color
154 |
155 | Example:
156 | hex_to_hsv('#ff9933')
157 | """
158 | color = normalize(color)
159 | color = color[1:]
160 | # color=tuple(ord(c)/255.0 for c in color.decode('hex'))
161 | color = (int(color[0:2], base=16) / 255.0, int(color[2:4],
162 | base=16) / 255.0, int(color[4:6], base=16) / 255.0)
163 | return colorsys.rgb_to_hsv(*color)
164 |
165 |
166 | def color_range(color, N=20):
167 | """
168 | Generates a scale of colours from a base colour
169 |
170 | Parameters:
171 | -----------
172 | color : string
173 | Color representation in hex
174 | N : int
175 | number of colours to generate
176 |
177 | Example:
178 | color_range('#ff9933',20)
179 | """
180 | color = normalize(color)
181 | org = color
182 | color = hex_to_hsv(color)
183 | HSV_tuples = [(color[0], x, color[2]) for x in np.arange(0, 1, 2.0 / N)]
184 | HSV_tuples.extend([(color[0], color[1], x)
185 | for x in np.arange(0, 1, 2.0 / N)])
186 | hex_out = []
187 | for c in HSV_tuples:
188 | c = colorsys.hsv_to_rgb(*c)
189 | c = [int(_ * 255) for _ in c]
190 | # hex_out.append("#"+"".join([chr(x).encode('hex') for x in c]))
191 | hex_out.append("#" + "".join(['{0:02x}'.format(x) for x in c]))
192 | if org not in hex_out:
193 | hex_out.append(org)
194 | hex_out.sort()
195 | return hex_out
196 |
197 |
198 | def color_table(color, N=1, sort=False, sort_values=False, inline=False, as_html=False):
199 | """
200 | Generates a colour table
201 |
202 | Parameters:
203 | -----------
204 | color : string | list | dict
205 | Color representation in rgba|rgb|hex
206 | If a list of colors is passed then these
207 | are displayed in a table
208 | N : int
209 | number of colours to generate
210 | When color is not a list then it generaes
211 | a range of N colors
212 | sort : bool
213 | if True then items are sorted
214 | sort_values : bool
215 | if True then items are sorted by color values.
216 | Only applies if color is a dictionary
217 | inline : bool
218 | if True it returns single line color blocks
219 | as_html : bool
220 | if True it returns the HTML code
221 |
222 | Example:
223 | color_table('#ff9933')
224 | color_table(cufflinks.cnames)
225 | color_table(['pink','salmon','yellow'])
226 | Note:
227 | This function only works in iPython Notebook
228 | """
229 | if isinstance(color, list):
230 | c_ = ''
231 | rgb_tup = [normalize(c) for c in color]
232 | if sort:
233 | rgb_tup.sort()
234 | elif isinstance(color, dict):
235 | c_ = ''
236 | items = [(k, normalize(v), hex_to_hsv(normalize(v)))
237 | for k, v in list(color.items())]
238 | if sort_values:
239 | items = sorted(items, key=operator.itemgetter(2))
240 | elif sort:
241 | items = sorted(items, key=operator.itemgetter(0))
242 | rgb_tup = [(k, v) for k, v, _ in items]
243 | else:
244 | c_ = normalize(color)
245 | if N > 1:
246 | rgb_tup = np.array(color_range(c_, N))[::-1]
247 | else:
248 | rgb_tup = [c_]
249 |
250 | def _color(c):
251 | if hex_to_hsv(c)[2] < .5:
252 | color = "#ffffff"
253 | shadow = '0 1px 0 #000'
254 | else:
255 | color = "#000000"
256 | shadow = '0 1px 0 rgba(255,255,255,0.6)'
257 | if c == c_:
258 | border = " border: 1px solid #ffffff;"
259 | else:
260 | border = ''
261 | return color, shadow, border
262 |
263 | s = '
' if not inline else ''
264 | for c in rgb_tup:
265 | if isinstance(c, tuple):
266 | k, c = c
267 | k += ' : '
268 | else:
269 | k = ''
270 | if inline:
271 | s += ''.format(
272 | c)
273 | else:
274 | color, shadow, border = _color(c)
275 | s += """-
276 | """ + k + c.upper() + """
277 |
"""
278 | s += '
' if not inline else ''
279 | if as_html:
280 | return s
281 | return display(HTML(s))
282 |
283 |
284 | def colorgen(colors=None, n=None, scale=None, theme=None):
285 | """
286 | Returns a generator with a list of colors
287 | and gradients of those colors
288 |
289 | Parameters:
290 | -----------
291 | colors : list(colors)
292 | List of colors to use
293 |
294 | Example:
295 | colorgen()
296 | colorgen(['blue','red','pink'])
297 | colorgen(['#f03','rgb(23,25,25)'])
298 | """
299 | from .themes import THEMES
300 | step = .1
301 | if not colors:
302 | if not scale:
303 | if not theme:
304 | scale = get_config_file()['colorscale']
305 | else:
306 | scale = THEMES[theme]['colorscale']
307 | colors = get_scales(scale)
308 | dq = deque(colors)
309 | if len(dq) == 0:
310 | dq = deque(get_scales('ggplot'))
311 | if n:
312 | step = len(dq) * 0.8 / n if len(dq) * 8 < n else .1
313 | for i in np.arange(.2, 1, step):
314 | for y in dq:
315 | yield to_rgba(y, 1 - i + .2)
316 | dq.rotate(1)
317 |
318 | # NEW STUFF
319 |
320 |
321 | # Color Names
322 | # ---------------------------------
323 |
324 | cnames = {'aliceblue': '#F0F8FF',
325 | 'antiquewhite': '#FAEBD7',
326 | 'aqua': '#00FFFF',
327 | 'aquamarine': '#7FFFD4',
328 | 'azure': '#F0FFFF',
329 | 'beige': '#F5F5DC',
330 | 'bisque': '#FFE4C4',
331 | 'black': '#000000',
332 | 'blanchedalmond': '#FFEBCD',
333 | 'blue': '#3780bf',
334 | 'bluegray': '#565656',
335 | 'bluepurple': '#6432AB',
336 | 'blueviolet': '#8A2BE2',
337 | 'brick': '#E24A33',
338 | 'brightblue': '#0000FF',
339 | 'brightred': '#FF0000',
340 | 'brown': '#A52A2A',
341 | 'burlywood': '#DEB887',
342 | 'cadetblue': '#5F9EA0',
343 | 'charcoal': '#151516',
344 | 'chartreuse': '#7FFF00',
345 | 'chocolate': '#D2691E',
346 | 'coral': '#FF7F50',
347 | 'cornflowerblue': '#6495ED',
348 | 'cornsilk': '#FFF8DC',
349 | 'crimson': '#DC143C',
350 | 'cyan': '#00FFFF',
351 | 'darkblue': '#00008B',
352 | 'darkcyan': '#008B8B',
353 | 'darkgoldenrod': '#B8860B',
354 | 'darkgray': '#A9A9A9',
355 | 'darkgreen': '#006400',
356 | 'darkgrey': '#A9A9A9',
357 | 'darkkhaki': '#BDB76B',
358 | 'darkmagenta': '#8B008B',
359 | 'darkolivegreen': '#556B2F',
360 | 'darkorange': '#FF8C00',
361 | 'darkorchid': '#9932CC',
362 | 'darkred': '#8B0000',
363 | 'darksalmon': '#E9967A',
364 | 'darkseagreen': '#8FBC8F',
365 | 'darkslateblue': '#483D8B',
366 | 'darkslategray': '#2F4F4F',
367 | 'darkslategrey': '#2F4F4F',
368 | 'darkturquoise': '#00CED1',
369 | 'darkviolet': '#9400D3',
370 | 'deeppink': '#FF1493',
371 | 'deepskyblue': '#00BFFF',
372 | 'dimgray': '#696969',
373 | 'dimgrey': '#696969',
374 | 'dodgerblue': '#1E90FF',
375 | 'firebrick': '#B22222',
376 | 'floralwhite': '#FFFAF0',
377 | 'forestgreen': '#228B22',
378 | 'fuchsia': '#FF00FF',
379 | 'gainsboro': '#DCDCDC',
380 | 'ghostwhite': '#F8F8FF',
381 | 'gold': '#FFD700',
382 | 'goldenrod': '#DAA520',
383 | 'grassgreen': '#32ab60',
384 | 'gray': '#808080',
385 | 'green': '#008000',
386 | 'greenyellow': '#ADFF2F',
387 | 'grey': '#808080',
388 | 'grey01': '#0A0A0A',
389 | 'grey02': '#151516',
390 | 'grey03': '#1A1A1C',
391 | 'grey04': '#1E1E21',
392 | 'grey05': '#252529',
393 | 'grey06': '#36363C',
394 | 'grey07': '#3C3C42',
395 | 'grey08': '#434343',
396 | 'grey09': '#666570',
397 | 'grey10': '#666666',
398 | 'grey11': '#8C8C8C',
399 | 'grey12': '#C2C2C2',
400 | 'grey13': '#E2E2E2',
401 | 'grey14': '#E5E5E5',
402 | 'honeydew': '#F0FFF0',
403 | 'hotpink': '#FF69B4',
404 | 'indianred': '#CD5C5C',
405 | 'indigo': '#4B0082',
406 | 'ivory': '#FFFFF0',
407 | 'java': '#17BECF',
408 | 'khaki': '#F0E68C',
409 | 'lavender': '#E6E6FA',
410 | 'lavenderblush': '#FFF0F5',
411 | 'lawngreen': '#7CFC00',
412 | 'lemonchiffon': '#FFFACD',
413 | 'lightpink2': '#fccde5',
414 | 'lightpurple': '#bc80bd',
415 | 'lightblue': '#ADD8E6',
416 | 'lightcoral': '#F08080',
417 | 'lightcyan': '#E0FFFF',
418 | 'lightgoldenrodyellow':'#FAFAD2',
419 | 'lightgray': '#D3D3D3',
420 | 'lightgreen': '#90EE90',
421 | 'lightgrey': '#D3D3D3',
422 | 'lightivory': '#F6F6F6',
423 | 'lightpink': '#FFB6C1',
424 | 'lightsalmon': '#FFA07A',
425 | 'lightseagreen': '#20B2AA',
426 | 'lightskyblue': '#87CEFA',
427 | 'lightslategray': '#778899',
428 | 'lightslategrey': '#778899',
429 | 'lightsteelblue': '#B0C4DE',
430 | 'lightteal': '#8dd3c7',
431 | 'lightyellow': '#FFFFE0',
432 | 'lightblue2': '#80b1d3',
433 | 'lightviolet': '#8476CA',
434 | 'lime': '#00FF00',
435 | 'lime2': '#8EBA42',
436 | 'limegreen': '#32CD32',
437 | 'linen': '#FAF0E6',
438 | 'magenta': '#FF00FF',
439 | 'maroon': '#800000',
440 | 'mediumaquamarine':'#66CDAA',
441 | 'mediumblue': '#0000CD',
442 | 'mediumgray': '#656565',
443 | 'mediumorchid': '#BA55D3',
444 | 'mediumpurple': '#9370DB',
445 | 'mediumseagreen': '#3CB371',
446 | 'mediumslateblue': '#7B68EE',
447 | 'mediumspringgreen':'#00FA9A',
448 | 'mediumturquoise': '#48D1CC',
449 | 'mediumvioletred': '#C71585',
450 | 'midnightblue': '#191970',
451 | 'mintcream': '#F5FFFA',
452 | 'mistyrose': '#FFE4E1',
453 | 'moccasin': '#FFE4B5',
454 | 'mustard': '#FBC15E',
455 | 'navajowhite': '#FFDEAD',
456 | 'navy': '#000080',
457 | 'oldlace': '#FDF5E6',
458 | 'olive': '#808000',
459 | 'olivedrab': '#6B8E23',
460 | 'orange': '#ff9933',
461 | 'orangered': '#FF4500',
462 | 'orchid': '#DA70D6',
463 | 'palegoldenrod': '#EEE8AA',
464 | 'palegreen': '#98FB98',
465 | 'paleolive': '#b3de69',
466 | 'paleturquoise': '#AFEEEE',
467 | 'palevioletred': '#DB7093',
468 | 'papayawhip': '#FFEFD5',
469 | 'peachpuff': '#FFDAB9',
470 | 'pearl': '#D9D9D9',
471 | 'pearl02': '#F5F6F9',
472 | 'pearl03': '#E1E5ED',
473 | 'pearl04': '#9499A3',
474 | 'pearl05': '#6F7B8B',
475 | 'pearl06': '#4D5663',
476 | 'peru': '#CD853F',
477 | 'pink': '#ff0088',
478 | 'pinksalmon': '#FFB5B8',
479 | 'plum': '#DDA0DD',
480 | 'polar': '#ACAFB5',
481 | 'polarblue': '#0080F0',
482 | 'polarbluelight': '#46A0F0',
483 | 'polarcyan': '#ADFCFC',
484 | 'polardark': '#484848',
485 | 'polardiv': '#D5D8DB',
486 | 'polardust': '#F2F3F7',
487 | 'polargrey': '#505050',
488 | 'polargreen': '#309054',
489 | 'polarorange': '#EE7600',
490 | 'polarpurple': '#6262DE',
491 | 'polarred': '#D94255',
492 | 'powderblue': '#B0E0E6',
493 | 'purple': '#800080',
494 | 'red': '#db4052',
495 | 'rose': '#FFC0CB',
496 | 'rosybrown': '#BC8F8F',
497 | 'royalblue': '#4169E1',
498 | 'saddlebrown': '#8B4513',
499 | 'salmon': '#fb8072',
500 | 'sandybrown': '#FAA460',
501 | 'seaborn': '#EAE7E4',
502 | 'seagreen': '#2E8B57',
503 | 'seashell': '#FFF5EE',
504 | 'sienna': '#A0522D',
505 | 'silver': '#C0C0C0',
506 | 'skyblue': '#87CEEB',
507 | 'slateblue': '#6A5ACD',
508 | 'slategray': '#708090',
509 | 'slategrey': '#708090',
510 | 'smurf': '#3E6FB0',
511 | 'snow': '#FFFAFA',
512 | 'springgreen': '#00FF7F',
513 | 'steelblue': '#4682B4',
514 | 'tan': '#D2B48C',
515 | 'teal': '#008080',
516 | 'thistle': '#D8BFD8',
517 | 'tomato': '#FF6347',
518 | 'turquoise': '#40E0D0',
519 | 'violet': '#EE82EE',
520 | 'wheat': '#F5DEB3',
521 | 'white': '#FFFFFF',
522 | 'whitesmoke': '#F5F5F5',
523 | 'yellow': '#ffff33',
524 | 'yellowgreen': '#9ACD32',
525 | "henanigans_bg": "#242424",
526 | "henanigans_blue1": "#5F95DE",
527 | "henanigans_blue2": "#93B6E6",
528 | "henanigans_cyan1": "#7EC4CF",
529 | "henanigans_cyan2": "#B6ECF3",
530 | "henanigans_dark1": "#040404",
531 | "henanigans_dark2": "#141414",
532 | "henanigans_dialog1": "#444459",
533 | "henanigans_dialog2": "#5D5D7A",
534 | "henanigans_green1": "#8BD155",
535 | "henanigans_green2": "#A0D17B",
536 | "henanigans_grey1": "#343434",
537 | "henanigans_grey2": "#444444",
538 | "henanigans_light1": "#A4A4A4",
539 | "henanigans_light2": "#F4F4F4",
540 | "henanigans_orange1": "#EB9E58",
541 | "henanigans_orange2": "#EBB483",
542 | "henanigans_purple1": "#C98FDE",
543 | "henanigans_purple2": "#AC92DE",
544 | "henanigans_red1": "#F77E70",
545 | "henanigans_red2": "#DE958E",
546 | "henanigans_yellow1": "#E8EA7E",
547 | "henanigans_yellow2": "#E9EABE"
548 | }
549 |
550 | # Custom Color Scales
551 | # ---------------------------------
552 |
553 | _custom_scales = {
554 | 'qual': {
555 | # dflt only exists to keep backward compatibility after issue 91
556 | 'dflt': ['orange', 'blue', 'grassgreen', 'purple', 'red', 'teal', 'yellow', 'olive', 'salmon', 'lightblue2'],
557 | 'original': ['orange', 'blue', 'grassgreen', 'purple', 'red', 'teal', 'yellow', 'olive', 'salmon', 'lightblue2'],
558 | 'ggplot': ['brick', 'smurf', 'lightviolet', 'mediumgray', 'mustard', 'lime2', 'pinksalmon'],
559 | 'polar': ['polarblue', 'polarorange', 'polargreen', 'polarpurple', 'polarred', 'polarcyan', 'polarbluelight'],
560 | 'plotly' : ['rgb(31, 119, 180)', 'rgb(255, 127, 14)', 'rgb(44, 160, 44)', 'rgb(214, 39, 40)',
561 | 'rgb(148, 103, 189)', 'rgb(140, 86, 75)', 'rgb(227, 119, 194)', 'rgb(127, 127, 127)', 'rgb(188, 189, 34)', 'rgb(23, 190, 207)'],
562 | 'henanigans': ['henanigans_cyan2', 'henanigans_red2', 'henanigans_green2', 'henanigans_blue2', 'henanigans_orange2',
563 | 'henanigans_purple2', 'henanigans_yellow2', 'henanigans_light2', 'henanigans_cyan1', 'henanigans_red1',
564 | 'henanigans_green1', 'henanigans_blue1']
565 | },
566 | 'div': {
567 |
568 | },
569 | 'seq': {
570 |
571 | }
572 |
573 | }
574 |
575 |
576 | # ---------------------------------------------------------------
577 | # The below functions are based in colorlover by Jack Parmer
578 | # https://github.com/jackparmer/colorlover/
579 | # ---------------------------------------------------------------
580 |
581 |
582 | _scales = None
583 | _scales_names = None
584 |
585 |
586 | def interp(colors, N):
587 | def _interp(colors, N):
588 | try:
589 | return cl.interp(colors, N)
590 | except:
591 | return _interp(colors, N + 1)
592 | c = _interp(colors, N)
593 | return list(map(rgb_to_hex, cl.to_rgb(c)))
594 |
595 |
596 | def scales(scale=None):
597 | """
598 | Displays a color scale (HTML)
599 |
600 | Parameters:
601 | -----------
602 | scale : str
603 | Color scale name
604 | If no scale name is provided then all scales are returned
605 | (max number for each scale)
606 | If scale='all' then all scale combinations available
607 | will be returned
608 |
609 | Example:
610 | scales('accent')
611 | scales('all')
612 | scales()
613 | """
614 | if scale:
615 | if scale == 'all':
616 | display(HTML(cl.to_html(_scales)))
617 | else:
618 | display(HTML(cl.to_html(get_scales(scale))))
619 | else:
620 | s = ''
621 | keys = list(_scales_names.keys())
622 | keys.sort()
623 | for k in keys:
624 | scale = get_scales(k)
625 | s += ''.format(
626 | k, cl.to_html(scale))
627 | display(HTML(s))
628 |
629 | # Scales Dictionary
630 | # ---------------------------------
631 |
632 |
633 | def reset_scales():
634 | global _scales
635 | global _scales_names
636 | scale_cpy = cl.scales.copy()
637 |
638 | # Add custom scales
639 | for k, v in list(_custom_scales.items()):
640 | if v:
641 | for k_, v_ in list(v.items()):
642 | if str(len(v_)) not in scale_cpy:
643 | scale_cpy[str(len(v_))] = {}
644 | scale_cpy[str(len(v_))][k][k_] = [
645 | hex_to_rgb(normalize(_)) for _ in v_]
646 |
647 | # Dictionary by Type > Name > N
648 | _scales = {}
649 | for k, v in list(scale_cpy.items()):
650 | for k_, v_ in list(v.items()):
651 | if k_ not in _scales:
652 | _scales[k_] = {}
653 | for k__, v__ in list(v_.items()):
654 | if k__ not in _scales[k_]:
655 | _scales[k_][k__] = {}
656 | _scales[k_][k__][k] = v__
657 |
658 | # Dictionary by Name > N
659 | _scales_names = {}
660 | for k, v in list(scale_cpy.items()):
661 | for k_, v_ in list(v.items()):
662 | for k__, v__ in list(v_.items()):
663 | k__ = k__.lower()
664 | if k__ not in _scales_names:
665 | _scales_names[k__] = {}
666 | _scales_names[k__][k] = v__
667 |
668 |
669 | def get_scales(scale=None, n=None):
670 | """
671 | Returns a color scale
672 |
673 | Parameters:
674 | -----------
675 | scale : str
676 | Color scale name
677 | If the color name is preceded by a minus (-)
678 | then the scale is inversed
679 | n : int
680 | Number of colors
681 | If n < number of colors available for a given scale then
682 | the minimum number will be returned
683 | If n > number of colors available for a given scale then
684 | the maximum number will be returned
685 |
686 | Example:
687 | get_scales('accent',8)
688 | get_scales('pastel1')
689 | """
690 | if scale:
691 | is_reverse = False
692 | if scale[0] == '-':
693 | scale = scale[1:]
694 | is_reverse = True
695 | d = copy.deepcopy(_scales_names[scale.lower()])
696 | keys = list(map(int, list(d.keys())))
697 | cs = None
698 | if n:
699 | if n in keys:
700 | cs = d[str(n)]
701 | elif n < min(keys):
702 | cs = d[str(min(keys))]
703 | if cs is None:
704 | cs = d[str(max(keys))]
705 | if is_reverse:
706 | cs.reverse()
707 | return cs
708 | else:
709 | d = {}
710 | for k, v in list(_scales_names.items()):
711 | if isinstance(v, dict):
712 | keys = list(map(int, list(v.keys())))
713 | d[k] = v[str(max(keys))]
714 | else:
715 | d[k] = v
716 | return d
717 |
718 |
719 | def get_colorscale(scale):
720 | """
721 | Returns a color scale to be used for a plotly figure
722 |
723 | Parameters:
724 | -----------
725 | scale : str or list
726 | Color scale name
727 | If the color name is preceded by a minus (-)
728 | then the scale is inversed.
729 | Also accepts a list of colors (rgb,rgba,hex)
730 |
731 | Example:
732 | get_colorscale('accent')
733 | get_colorscale(['rgb(127,201,127)','rgb(190,174,212)','rgb(253,192,134)'])
734 | """
735 |
736 | if type(scale) in string_types:
737 | scale = get_scales(scale)
738 | else:
739 | if type(scale) != list:
740 | raise Exception(
741 | "scale needs to be either a scale name or list of colors")
742 |
743 | cs = [[1.0 * c / (len(scale) - 1), scale[c]] for c in range(len(scale))]
744 | cs.sort()
745 | return cs
746 |
747 |
748 | reset_scales()
749 |
--------------------------------------------------------------------------------
/cufflinks/datagen.py:
--------------------------------------------------------------------------------
1 | import os
2 | import string
3 |
4 | import numpy as np
5 | import pandas as pd
6 |
7 | from .auth import get_config_file
8 | from .exceptions import CufflinksError
9 |
10 |
11 | def scattergeo():
12 | """
13 | Returns
14 | """
15 | path=os.path.join(os.path.dirname(__file__), '../data/scattergeo.csv')
16 | df=pd.read_csv(path)
17 | del df['Unnamed: 0']
18 | df['text'] = df['airport'] + ' ' + df['city'] + ', ' + df['state'] + ' ' + 'Arrivals: ' + df['cnt'].astype(str)
19 | df=df.rename(columns={'cnt':'z','long':'lon'})
20 | return df
21 |
22 | def choropleth():
23 | """
24 | Returns
25 | """
26 | path=os.path.join(os.path.dirname(__file__), '../data/choropleth.csv')
27 | df=pd.read_csv(path)
28 | del df['Unnamed: 0']
29 | df['z']=[np.random.randint(0,100) for _ in range(len(df))]
30 | return df
31 |
32 | def scatter3d(n_categories=5,n=10,prefix='category',mode=None):
33 | """
34 | Returns a DataFrame with the required format for
35 | a scatter3d plot
36 |
37 | Parameters:
38 | -----------
39 | n_categories : int
40 | Number of categories
41 | n : int
42 | Number of points for each trace
43 | prefix : string
44 | Name for each trace
45 | mode : string
46 | Format for each item
47 | 'abc' for alphabet columns
48 | 'stocks' for random stock names
49 | """
50 | categories=[]
51 | for i in range(n_categories):
52 | categories.extend([prefix+str(i+1)]*n)
53 | return pd.DataFrame({'x':np.random.randn(n*n_categories),
54 | 'y':np.random.randn(n*n_categories),
55 | 'z':np.random.randn(n*n_categories),
56 | 'text':getName(n*n_categories,mode=mode),
57 | 'categories':categories})
58 |
59 | def bubble3d(n_categories=5,n=10,prefix='category',mode=None):
60 | """
61 | Returns a DataFrame with the required format for
62 | a bubble3d plot
63 |
64 | Parameters:
65 | -----------
66 | n_categories : int
67 | Number of categories
68 | n : int
69 | Number of points for each trace
70 | prefix : string
71 | Name for each trace
72 | mode : string
73 | Format for each item
74 | 'abc' for alphabet columns
75 | 'stocks' for random stock names
76 | """
77 | categories=[]
78 | for i in range(n_categories):
79 | categories.extend([prefix+str(i+1)]*n)
80 | return pd.DataFrame({'x':np.random.randn(n*n_categories),
81 | 'y':np.random.randn(n*n_categories),
82 | 'z':np.random.randn(n*n_categories),
83 | 'size':np.random.randint(1,100,n*n_categories),
84 | 'text':getName(n*n_categories,mode=mode),
85 | 'categories':categories})
86 |
87 | def bubble(n_categories=5,n=10,prefix='category',mode=None):
88 | """
89 | Returns a DataFrame with the required format for
90 | a bubble plot
91 |
92 | Parameters:
93 | -----------
94 | n_categories : int
95 | Number of categories
96 | n : int
97 | Number of points for each category
98 | prefix : string
99 | Name for each category
100 | mode : string
101 | Format for each item
102 | 'abc' for alphabet columns
103 | 'stocks' for random stock names
104 | """
105 | categories=[]
106 | for i in range(n_categories):
107 | categories.extend([prefix+str(i+1)]*n)
108 | return pd.DataFrame({'x':np.random.randn(n*n_categories),
109 | 'y':np.random.randn(n*n_categories),
110 | 'size':np.random.randint(1,100,n*n_categories),
111 | 'text':getName(n*n_categories,mode=mode),
112 | 'categories':categories})
113 |
114 | def pie(n_labels=5,mode=None):
115 | """
116 | Returns a DataFrame with the required format for
117 | a pie plot
118 |
119 | Parameters:
120 | -----------
121 | n_labels : int
122 | Number of labels
123 | mode : string
124 | Format for each item
125 | 'abc' for alphabet columns
126 | 'stocks' for random stock names
127 | """
128 | return pd.DataFrame({'values':np.random.randint(1,100,n_labels),
129 | 'labels':getName(n_labels,mode=mode)})
130 |
131 | def scatter(n_categories=5,n=10,prefix='category',mode=None):
132 | """
133 | Returns a DataFrame with the required format for
134 | a scatter plot
135 |
136 | Parameters:
137 | -----------
138 | n_categories : int
139 | Number of categories
140 | n : int
141 | Number of points for each category
142 | prefix : string
143 | Name for each category
144 | mode : string
145 | Format for each item
146 | 'abc' for alphabet columns
147 | 'stocks' for random stock names
148 | """
149 | categories=[]
150 | for i in range(n_categories):
151 | categories.extend([prefix+str(i+1)]*n)
152 | return pd.DataFrame({'x':np.random.randn(n*n_categories),
153 | 'y':np.random.randn(n*n_categories),
154 | 'text':getName(n*n_categories,mode=mode),
155 | 'categories':categories})
156 |
157 | def heatmap(n_x=5,n_y=10):
158 | """
159 | Returns a DataFrame with the required format for
160 | a heatmap plot
161 |
162 | Parameters:
163 | -----------
164 | n_x : int
165 | Number of x categories
166 | n_y : int
167 | Number of y categories
168 | """
169 | x=['x_'+str(_) for _ in range(n_x)]
170 | y=['y_'+str(_) for _ in range(n_y)]
171 | return pd.DataFrame(surface(n_x-1,n_y-1).values,index=x,columns=y)
172 |
173 | def lines(n_traces=5,n=100,columns=None,dateIndex=True,mode=None):
174 | """
175 | Returns a DataFrame with the required format for
176 | a scatter (lines) plot
177 |
178 | Parameters:
179 | -----------
180 | n_traces : int
181 | Number of traces
182 | n : int
183 | Number of points for each trace
184 | columns : [str]
185 | List of column names
186 | dateIndex : bool
187 | If True it will return a datetime index
188 | if False it will return a enumerated index
189 | mode : string
190 | Format for each item
191 | 'abc' for alphabet columns
192 | 'stocks' for random stock names
193 | """
194 | index=pd.date_range('1/1/15',periods=n) if dateIndex else list(range(n))
195 | df=pd.DataFrame(np.random.randn(n,n_traces),index=index,
196 | columns=getName(n_traces,columns=columns,mode=mode))
197 | return df.cumsum()
198 |
199 | def bars(n=3,n_categories=3,prefix='category',columns=None,mode='abc'):
200 | """
201 | Returns a DataFrame with the required format for
202 | a bar plot
203 |
204 | Parameters:
205 | -----------
206 | n : int
207 | Number of points for each trace
208 | n_categories : int
209 | Number of categories for each point
210 | prefix : string
211 | Name for each category
212 | columns : [str]
213 | List of column names
214 | mode : string
215 | Format for each item
216 | 'abc' for alphabet columns
217 | 'stocks' for random stock names
218 | """
219 | categories=[]
220 | if not columns:
221 | columns=getName(n,mode=mode)
222 | for i in range(n_categories):
223 | categories.extend([prefix+str(i+1)])
224 | data=dict([(x,np.random.randint(1,100,n_categories)) for x in columns])
225 | return pd.DataFrame(data,index=categories)
226 |
227 | def ohlc(n=100):
228 | """
229 | Returns a DataFrame with the required format for
230 | a candlestick or ohlc plot
231 | df[['open','high','low','close']]
232 |
233 | Parameters:
234 | -----------
235 | n : int
236 | Number of ohlc points
237 |
238 | """
239 | index=pd.date_range('1/1/15',periods=n*288,freq='5min',tz='utc')
240 | data=np.random.randn(n*288)
241 | data[0]=np.array([100])
242 | df=pd.DataFrame(data,index=index,
243 | columns=['a'])
244 | df=df.cumsum()
245 | df=df.resample('1d').ohlc()
246 | df.index=df.index.date
247 | df.index=pd.to_datetime(df.index)
248 | return df['a']
249 |
250 | def ohlcv(n=100):
251 | """
252 | Returns a DataFrame with the required format for
253 | a candlestick or ohlc plot
254 | df[['open','high','low','close','volume']
255 |
256 | Parameters:
257 | -----------
258 | n : int
259 | Number of ohlc points
260 |
261 | """
262 | df=ohlc(n=n)
263 | df['volume']=[np.random.randint(1000,10000) for _ in range(len(df))]
264 | return df
265 |
266 | def box(n_traces=5,n=100,mode=None):
267 | """
268 | Returns a DataFrame with the required format for
269 | a box plot
270 |
271 | Parameters:
272 | -----------
273 | n_traces : int
274 | Number of traces
275 | n : int
276 | Number of points for each trace
277 | mode : string
278 | Format for each item
279 | 'abc' for alphabet columns
280 | 'stocks' for random stock names
281 | """
282 | df=pd.DataFrame([np.random.chisquare(np.random.randint(2,10),n_traces) for _ in range(n)],
283 | columns=getName(n_traces,mode=mode))
284 | return df
285 |
286 | def histogram(n_traces=1,n=500,dispersion=2,mode=None):
287 | """
288 | Returns a DataFrame with the required format for
289 | a histogram plot
290 |
291 | Parameters:
292 | -----------
293 | n_traces : int
294 | Number of traces
295 | n : int
296 | Number of points for each trace
297 | mode : string
298 | Format for each item
299 | 'abc' for alphabet columns
300 | 'stocks' for random stock names
301 | """
302 | df=pd.DataFrame(np.transpose([np.random.randn(n)+np.random.randint(-1*dispersion,dispersion) for _ in range(n_traces)]),
303 | columns=getName(n_traces,mode=mode))
304 | return df
305 |
306 | def distplot(n_traces=1,n=500,dispersion=3,mode=None):
307 | """
308 | Returns a DataFrame with the required format for
309 | a distribution plot (distplot)
310 |
311 | Parameters:
312 | -----------
313 | n_traces : int
314 | Number of traces
315 | n : int
316 | Number of points for each trace
317 | mode : string
318 | Format for each item
319 | 'abc' for alphabet columns
320 | 'stocks' for random stock names
321 | """
322 | return histogram(n_traces,n,dispersion,mode)
323 |
324 | def violin(n=500,dispersion=3,categories=True,n_categories=5):
325 | """
326 | Returns a DataFrame with the required format for
327 | a distribution plot (distplot)
328 |
329 | Parameters:
330 | -----------
331 | n : int
332 | Number of points
333 | categories : bool or int
334 | If True, then a column with categories is added
335 | n_categories : int
336 | Number of categories
337 | """
338 | df = histogram(1,n,dispersion,'abc')
339 | df=df.rename(columns={'a':'data'})
340 | if categories:
341 | df['categories']=['category_{0}'.format(np.random.randint(n_categories)) for _ in range(n)]
342 | return df
343 |
344 | def surface(n_x=20,n_y=20):
345 | """
346 | Returns a DataFrame with the required format for
347 | a surface plot
348 |
349 | Parameters:
350 | -----------
351 | n_x : int
352 | Number of points along the X axis
353 | n_y : int
354 | Number of points along the Y axis
355 | """
356 | x=[float(np.random.randint(0,100))]
357 | for i in range(n_x):
358 | x.append(x[:1][0]+np.random.randn()*np.random.randint(1,10))
359 | df=pd.DataFrame(x)
360 | for i in range(n_y):
361 | df[i+1]=df[i].map(lambda x:x+np.random.randn()*np.random.randint(1,10))
362 | return df
363 |
364 | def sinwave(n=4,inc=.25):
365 | """
366 | Returns a DataFrame with the required format for
367 | a surface (sine wave) plot
368 |
369 | Parameters:
370 | -----------
371 | n : int
372 | Ranges for X and Y axis (-n,n)
373 | n_y : int
374 | Size of increment along the axis
375 | """
376 | x=np.arange(-n,n,inc)
377 | y=np.arange(-n,n,inc)
378 | X,Y=np.meshgrid(x,y)
379 | R = np.sqrt(X**2 + Y**2)
380 | Z = np.sin(R)/(.5*R)
381 | return pd.DataFrame(Z,index=x,columns=y)
382 |
383 | def getName(n=1,name=3,exchange=2,columns=None,mode='abc'):
384 | if columns:
385 | if isinstance(columns,str):
386 | columns=[columns]
387 | if n != len(columns):
388 | raise CufflinksError("Length of column names needs to be the \n"
389 | "same length of traces")
390 | else:
391 | if mode is None:
392 | mode=get_config_file()['datagen_mode']
393 | if mode=='abc':
394 | def get_abc(n):
395 | def _w(n,base=2):
396 | _n=1
397 | st=[]
398 | while base**_n<=n:
399 | _n+=1
400 | for _ in range(_n-1,0,-1):
401 | n_st=n//(base**_)
402 | st.append(n_st)
403 | n=n-n_st*(base**_)
404 | st.append(n+1)
405 | return st
406 | st=_w(n,len(string.ascii_lowercase))
407 | _st=''
408 | for _ in st:
409 | _st+=string.ascii_lowercase[_-1]
410 | return _st
411 | columns=[get_abc(_) for _ in range(n)]
412 | elif mode=='stocks':
413 | columns=[''.join(np.random.choice(list(string.ascii_uppercase),name)) + '.' + ''.join(np.random.choice(list(string.ascii_uppercase),exchange)) for _ in range(n)]
414 | else:
415 | raise CufflinksError("Unknown mode: {0}".format(mode))
416 | return columns
417 |
--------------------------------------------------------------------------------
/cufflinks/date_tools.py:
--------------------------------------------------------------------------------
1 | import datetime as dt
2 |
3 |
4 | def getDatefromDate(date,delta,strfmt='%Y%m%d'):
5 | if type(date)==str:
6 | date=stringToDate(date,strfmt)
7 | return (date + dt.timedelta(delta)).strftime(strfmt)
8 |
9 | def getDateFromToday(delta,strfmt='%Y%m%d'):
10 | """ Returns a string that represents a date n numbers of days from today.
11 | Parameters:
12 | -----------
13 | delta : int
14 | number of days
15 | strfmt : string
16 | format in which the date will be represented
17 | """
18 | return (dt.date.today() + dt.timedelta(delta)).strftime(strfmt)
19 |
20 | def stringToDate(stringDate,strfmt='%Y%m%d'):
21 | """ Converts a string format date into datetime
22 | Parameters:
23 | -----------
24 | stringDate : string
25 | date in string format
26 | strfmt : string
27 | format in which the input date is represented
28 | """
29 | return dt.datetime.strptime(stringDate,strfmt).date()
30 |
31 | def intToDate(intDate):
32 | """ Converts an int format date into datetime
33 | Parameters:
34 | -----------
35 | intDate : int
36 | date in int format
37 | Example:
38 | intDate(20151023)
39 | """
40 | return stringToDate(str(intDate))
41 |
42 | def dateToInt(date,strfmt='%Y%m%d'):
43 | """ Converts a datetime date into int
44 | Parameters:
45 | -----------
46 | date : datetime
47 | date in datetime format
48 | strfmt : string
49 | format in which the int date will be generated
50 | Example:
51 | dateToInt(dt.date(2015,10,23),'%Y')
52 | """
53 | return int(date.strftime(strfmt))
54 |
55 | def dateToString(date,strfmt='%Y%m%d'):
56 | return dt.datetime.strftime(date,strfmt)
57 |
58 | def stringToString(date,from_strfmt='%d%b%y',to_strfmt='%Y%m%d'):
59 | return dt.datetime.strftime(stringToDate(date,from_strfmt),to_strfmt)
60 |
61 |
62 |
--------------------------------------------------------------------------------
/cufflinks/exceptions.py:
--------------------------------------------------------------------------------
1 | class CufflinksError(Exception):
2 | pass
--------------------------------------------------------------------------------
/cufflinks/extract.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 |
3 |
4 | def to_df(figure):
5 | """
6 | Extracts the data from a Plotly Figure
7 |
8 | Parameters
9 | ----------
10 | figure : plotly_figure
11 | Figure from which data will be
12 | extracted
13 |
14 | Returns a DataFrame or list of DataFrame
15 | """
16 | dfs=[]
17 | for trace in figure['data']:
18 | if 'scatter' in trace['type']:
19 | try:
20 | if type(trace['x'][0])==float:
21 | index=trace['x']
22 | else:
23 | index=pd.to_datetime(trace['x'])
24 | except:
25 | index=trace['x']
26 | if 'marker' in trace:
27 | d={}
28 | if 'size' in trace['marker']:
29 | size=trace['marker']['size']
30 | if type(size)!=list:
31 | size=[size]*len(index)
32 | d['size']=size
33 | if 'text' in trace:
34 | d['text']=trace['text']
35 | if 'name' in trace:
36 | name=trace['name']
37 | if type(name)!=list:
38 | name=[name]*len(index)
39 | d['categories']=name
40 | d['y']=trace['y']
41 | d['x']=trace['x']
42 | if 'z' in trace:
43 | d['z']=trace['z']
44 | df=pd.DataFrame(d)
45 | else:
46 | df=pd.Series(trace['y'],index=index,name=trace['name'])
47 | dfs.append(df)
48 | elif trace['type'] in ('heatmap','surface'):
49 | df=pd.DataFrame(trace['z'].transpose(),index=trace['x'],columns=trace['y'])
50 | dfs.append(df)
51 | elif trace['type'] in ('box','histogram'):
52 | vals=trace['x'] if 'x' in trace else trace['y']
53 | df=pd.DataFrame({trace['name']:vals})
54 | dfs.append(df)
55 | if max(list(map(len,dfs)))==min(list(map(len,dfs))):
56 | if len(dfs)==1:
57 | return dfs[0]
58 | else:
59 | if type(dfs[0])==pd.core.series.Series:
60 | return pd.concat(dfs,axis=1)
61 | if all(dfs[0].columns==dfs[1].columns):
62 | return pd.concat(dfs,axis=0)
63 | else:
64 | return pd.concat(dfs,axis=1)
65 | else:
66 | try:
67 | return pd.concat(dfs)
68 | except:
69 | return dfs
--------------------------------------------------------------------------------
/cufflinks/helper.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | from . import utils
4 |
5 |
6 | def _get_params():
7 | path=path=os.path.join(os.path.dirname(__file__), '../helper/params.json')
8 | f=open(path)
9 | d=json.loads(f.read())
10 | f.close()
11 | return d
12 |
13 | def _help(figure):
14 | d=_get_params()
15 | figure=figure.lower()
16 | if figure in d['figures']:
17 | fig={}
18 | fig['description']=d['figures'][figure].get('description','')
19 | fig['examples']=d['figures'][figure].get('examples','')
20 | fig['parameters']={figure:{'params':{}},'all':{'params':{}}}
21 | for k,v in d['parameters'].items():
22 | for _ in [figure,'all']:
23 | if _ in v['applies']:
24 | param={}
25 | if figure in v['exceptions']:
26 | if v['exceptions'][figure]:
27 | param['description']=v['exceptions'][figure]
28 | else:
29 | break
30 | else:
31 | param['description']=v['description']
32 | param['type']=v.get('type','')
33 | param['position']=v.get('position','')
34 | if 'group' in v:
35 | if v['group'] not in fig['parameters'][_]:
36 | fig['parameters'][_][v['group']]={}
37 | fig['parameters'][_][v['group']][k]=param
38 | else:
39 | fig['parameters'][_]['params'][k]=param
40 | return fig
41 | else:
42 | raise Exception('Figure {0} was not found'.format(figure.upper()))
43 |
44 | def _get_aka(figure):
45 | d=_get_params()
46 | if figure in d['figures']:
47 | return figure
48 | else:
49 | aka={}
50 | for k,v in d['figures'].items():
51 | if 'aka' in v:
52 | for _ in d['figures'][k]['aka']:
53 | aka[_]=k
54 | if figure in aka.keys():
55 | print('Did you mean: "{0}"?\n'.format(aka[figure]))
56 | return aka[figure]
57 | else:
58 | raise Exception('Figure {0} was not found'.format(figure.upper()))
59 |
60 |
61 | def _printer(figure=None):
62 | if not figure:
63 | d=_get_params()
64 | keys=list(d['figures'].keys())
65 | keys.sort()
66 | print("Use 'cufflinks.help(figure)' to see the list of available parameters for the given figure.")
67 | print("Use 'DataFrame.iplot(kind=figure)' to plot the respective figure")
68 | print('Figures:')
69 | for k in keys:
70 | print('\t{0}'.format(k))
71 | else:
72 | figure=_get_aka(figure)
73 | fig=_help(figure)
74 | print(figure.upper())
75 | if type(fig['description'])==str:
76 | print(fig['description'])
77 | else:
78 | for _ in fig['description']:
79 | print(_)
80 | print('\n')
81 | print('Parameters:\n{0}'.format('='*11))
82 | def get_tabs(val):
83 | return ' '*4*val
84 | def print_params(params,tabs=0):
85 | _keys = list(params.keys())
86 | _keys.sort()
87 | positions={}
88 | for k,v in params.items():
89 | if v.get('position',False):
90 | positions[k]=(int(v['position']))
91 | for k,v in positions.items():
92 | if v==-1:
93 | _keys.append(_keys.pop(_keys.index(k)))
94 | else:
95 | _keys.insert(v,_keys.pop(_keys.index(k)))
96 | for k in _keys:
97 | v=params[k]
98 | print("{0}{1} : {2}".format(get_tabs(tabs),k,v['type']))
99 | if type(v['description'])==str:
100 | print("{0}{1}".format(get_tabs(tabs+1),v['description']))
101 | else:
102 | for __ in v['description']:
103 | print("{0}{1}".format(get_tabs(tabs+1),__))
104 |
105 | # Params
106 | print_params(fig['parameters'][figure].pop('params'),1)
107 | print('\n')
108 | print_params(fig['parameters']['all'].pop('params'),1)
109 | d=utils.deep_update(fig['parameters'][figure],fig['parameters']['all'])
110 |
111 | for k,v in d.items():
112 | print('\n{0}{1}'.format(get_tabs(1),k.upper()))
113 | print_params(v,2)
114 |
115 |
116 | if 'examples' in fig:
117 | print('\nEXAMPLES')
118 | for _ in fig['examples']:
119 | print('{0}>> {1}'.format(get_tabs(1),_))
--------------------------------------------------------------------------------
/cufflinks/offline.py:
--------------------------------------------------------------------------------
1 | import plotly.offline as py_offline
2 |
3 | ### Offline Mode
4 |
5 | def run_from_ipython():
6 | try:
7 | __IPYTHON__
8 | return True
9 | except NameError:
10 | return False
11 |
12 |
13 | def go_offline(connected=None):
14 | """
15 | connected : bool
16 | If True, the plotly.js library will be loaded
17 | from an online CDN. If False, the plotly.js library will be loaded locally
18 | from the plotly python package
19 | """
20 | from .auth import get_config_file
21 | if connected is None:
22 | try:
23 | connected=True if get_config_file()['offline_connected'] is None else get_config_file()['offline_connected']
24 | except:
25 | connected=True
26 | if run_from_ipython():
27 | try:
28 | py_offline.init_notebook_mode(connected)
29 | except TypeError:
30 | #For older versions of plotly
31 | py_offline.init_notebook_mode()
32 | py_offline.__PLOTLY_OFFLINE_INITIALIZED=True
33 |
34 | def go_online():
35 | py_offline.__PLOTLY_OFFLINE_INITIALIZED=False
36 |
37 | def is_offline():
38 | return py_offline.__PLOTLY_OFFLINE_INITIALIZED
39 |
40 | def upgrade(url=None):
41 | from .auth import get_config_file
42 | if not url:
43 | if 'http' not in get_config_file()['offline_url']:
44 | raise Exception("No default offline URL set \n"
45 | "Please run cf.set_config_file(offline_url=xx) to set \n"
46 | "the default offline URL.")
47 | else:
48 | url=get_config_file()['offline_url']
49 | py_offline.download_plotlyjs(url)
50 |
--------------------------------------------------------------------------------
/cufflinks/pandastools.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import re
3 |
4 |
5 |
6 | def _screen(self,include=True,**kwargs):
7 | """
8 | Filters a DataFrame for columns that contain the given strings.
9 | Parameters:
10 | -----------
11 | include : bool
12 | If False then it will exclude items that match
13 | the given filters.
14 | This is the same as passing a regex ^keyword
15 | kwargs :
16 | Key value pairs that indicate the column and
17 | value to screen for
18 |
19 | Example:
20 | df.screen(col1='string_to_match',col2=['string1','string2'])
21 | """
22 | df=self.copy()
23 | for k,v in list(kwargs.items()):
24 | v=[v] if type(v)!=list else v
25 | if include:
26 | df=df[df[k].str.contains('|'.join(v),flags=re.IGNORECASE).fillna(False)]
27 | else:
28 | df=df[df[k].str.contains('|'.join(v),flags=re.IGNORECASE).fillna(False)==False]
29 | return df
30 |
31 | def _swapcolumns(self):
32 | """
33 | Swaps first and second columns.
34 | Useful for inverting axis when plotting.
35 |
36 | Example:
37 | df.swapcolumns()
38 |
39 | Returns : DataFrame
40 |
41 | """
42 | return self.reindex_axis([self.columns[1],self.columns[0]],axis=1)
43 |
44 | def bestfit(self):
45 | """
46 | Returns a series with the bestfit values.
47 |
48 | Example:
49 | Series.bestfit()
50 |
51 | Returns: series
52 | The returned series contains a parameter
53 | called 'formula' which includes the string representation
54 | of the bestfit line.
55 | """
56 | # statsmodel cannot be included on requirements.txt
57 | # see https://github.com/scikit-learn/scikit-learn/issues/4164
58 | # which shares the same issue as statsmodel
59 | try:
60 | import statsmodels.api as sm
61 | except:
62 | raise Exception("statsmodels is required: " \
63 | "please run " \
64 | "pip install statsmodels" )
65 |
66 | if isinstance(self.index, pd.DatetimeIndex):
67 | x=pd.Series(list(range(1,len(self)+1)),index=self.index)
68 | else:
69 | x=self.index.values
70 |
71 | x=sm.add_constant(x)
72 | model=sm.OLS(self,x)
73 | fit=model.fit()
74 | vals=fit.params.values
75 | best_fit=fit.fittedvalues
76 | # the below methods have been deprecated in Pandas
77 | # model=pd.ols(x=x,y=self,intercept=True)
78 | # best_fit=model.y_fitted
79 | best_fit.formula='%.2f*x+%.2f' % (vals[1],vals[0])
80 | return best_fit
81 |
82 | def normalize(self,asOf=None,multiplier=100):
83 | """
84 | Returns a normalized series or DataFrame
85 |
86 | Example:
87 | Series.normalize()
88 |
89 | Returns: series of DataFrame
90 |
91 | Parameters:
92 | -----------
93 | asOf : string
94 | Date format
95 | '2015-02-29'
96 | multiplier : int
97 | Factor by which the results will be adjusted
98 | """
99 | if not asOf:
100 | x0=self.iloc[0]
101 | else:
102 | x0=self.loc[asOf]
103 | return self/x0*multiplier
104 |
105 | def read_google(url,**kwargs):
106 | """
107 | Reads a google sheet
108 | """
109 | if url[-1]!='/':
110 | url+='/'
111 | return pd.read_csv(url+'export?gid=0&format=csv',**kwargs)
112 |
113 | pd.DataFrame.screen=_screen
114 | pd.DataFrame.swapcolumns=_swapcolumns
115 | pd.DataFrame.normalize=normalize
116 | pd.read_google=read_google
117 | pd.Series.normalize=normalize
118 | pd.Series.bestfit=bestfit
119 |
120 |
--------------------------------------------------------------------------------
/cufflinks/quant_figure.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 |
4 | QuantFigure allows you to create a persistent object.
5 | Annotations and Technical Studies can be added on demand.
6 |
7 | It accepts any dataframe with a timeseries index.
8 |
9 | Try it out:
10 | qf=cf.QuantFig(cf.datagen.ohlc())
11 | qf.iplot()
12 |
13 | """
14 |
15 | from __future__ import absolute_import
16 |
17 | import plotly.graph_objs as go
18 | import json
19 | import copy
20 | import pandas as pd
21 |
22 | from .plotlytools import iplot as pt_iplot
23 | from . import tools
24 | from . import ta
25 | from . import utils
26 | from . import colors
27 | from . import auth
28 | from . import date_tools
29 |
30 | __QUANT_FIGURE_DATA = ['kind','showlegend','datalegend','name','slice','resample','bestfit',
31 | 'text','title','yTitle','secondary_y_title','bestfit_colors','kind',
32 | 'colorscale','xTitle','colors','secondary_y']
33 | __QUANT_FIGURE_LAYOUT = ['annotations','showlegend','margin','rangeselector','rangeslider','shapes',
34 | 'width','height','dimensions']
35 | __QUANT_FIGURE_THEME = ['theme','up_color','down_color']
36 | __QUANT_FIGURE_PANELS = ['min_panel_size','spacing','top_margin','bottom_margin']
37 |
38 |
39 | def get_layout_kwargs():
40 | return tools.__LAYOUT_KWARGS
41 | def get_annotation_kwargs():
42 | return tools.__ANN_KWARGS
43 | def get_shapes_kwargs(): return tools.__SHAPES_KWARGS
44 |
45 | class QuantFig(object):
46 |
47 | def __init__(self,df,kind='candlestick',columns=None,**kwargs):
48 | self.df=df
49 | self.studies={}
50 | self.data={}
51 | self.theme={}
52 | self.panels={}
53 | self.layout={}
54 | self.trendlines=[]
55 | self.kwargs={}
56 |
57 | # Set column names
58 | if not columns:
59 | columns={}
60 | for _ in ['open','high','low','close','volume']:
61 | columns[_]=kwargs.pop(_,'')
62 | self._d=ta._ohlc_dict(df,**columns)
63 |
64 | # Set initial annotations
65 | annotations={
66 | 'values':[],
67 | 'params':utils.check_kwargs(kwargs,get_annotation_kwargs(),{},clean_origin=True)
68 | }
69 |
70 | ann_values=kwargs.pop('annotations',None)
71 |
72 | if ann_values:
73 | if utils.is_list(ann_values):
74 | annotations['values'].extend(ann_values)
75 | else:
76 | annotations['values'].append(ann_values)
77 |
78 | # self.data initial values
79 | self.data.update(datalegend=kwargs.pop('datalegend',True),name=kwargs.pop('name','Trace 1'),kind=kind)
80 | self.data.update(slice=kwargs.pop('slice',(None,None)),resample=kwargs.pop('resample',None))
81 |
82 | # self.layout initial values
83 | self.layout['shapes']=utils.check_kwargs(kwargs,get_shapes_kwargs(),{},clean_origin=True)
84 | for k,v in list(self.layout['shapes'].items()):
85 | if not isinstance(v,list):
86 | self.layout['shapes'][k]=[v]
87 | self.layout['rangeselector']=kwargs.pop('rangeselector',{'visible':False})
88 | self.layout['rangeslider']=kwargs.pop('rangeslider',False)
89 | self.layout['margin']=kwargs.pop('margin',dict(t=30,b=30,r=30,l=30))
90 | self.layout['annotations']=annotations
91 | self.layout['showlegend']=kwargs.pop('showlegend',True)
92 | self.layout.update(utils.check_kwargs(kwargs,get_layout_kwargs(),{},clean_origin=True))
93 |
94 | # self.theme initial values
95 | self.theme['theme']=kwargs.pop('theme',auth.get_config_file()['theme'])
96 | self.theme['up_color']=kwargs.pop('up_color','#17BECF') # java
97 | self.theme['down_color']=kwargs.pop('down_color','grey')
98 |
99 | # self.panels initial values
100 | self.panels['min_panel_size']=kwargs.pop('min_panel_size',.15)
101 | self.panels['spacing']=kwargs.pop('spacing',.08)
102 | self.panels['top_margin']=kwargs.pop('top_margin',0.9)
103 | self.panels['bottom_margin']=kwargs.pop('top_margin',0)
104 | self.update(**kwargs)
105 |
106 |
107 | def _get_schema(self):
108 | """
109 | Returns a dictionary with the schema for a QuantFigure
110 |
111 | """
112 | d={}
113 | layout_kwargs=dict((_,'') for _ in get_layout_kwargs())
114 | for _ in ('data','layout','theme','panels'):
115 | d[_]={}
116 | for __ in eval('__QUANT_FIGURE_{0}'.format(_.upper())):
117 | layout_kwargs.pop(__,None)
118 | d[_][__]=None
119 | d['layout'].update(annotations=dict(values=[],
120 | params=utils.make_dict_from_list(get_annotation_kwargs())))
121 | d['layout'].update(shapes=utils.make_dict_from_list(get_shapes_kwargs()))
122 | [layout_kwargs.pop(_,None) for _ in get_annotation_kwargs()+get_shapes_kwargs()]
123 | d['layout'].update(**layout_kwargs)
124 | return d
125 |
126 | def _get_sliced(self,slice,df=None,to_strfmt='%Y-%m-%d',from_strfmt='%d%b%y'):
127 | """
128 | Returns a sliced DataFrame
129 |
130 | Parameters
131 | ----------
132 | slice : tuple(from,to)
133 | from : str - ie ('01Jan2019')
134 | to : str - ie ('01Jan2020')
135 | States the 'from' and 'to' values which
136 | will get rendered as df.loc[from:to]
137 | df : DataFrame
138 | If omitted then the QuantFigure.DataFrame is resampled.
139 | """
140 |
141 | df=self.df.copy() if df==None else df
142 | if type(slice) not in (list,tuple):
143 | raise Exception('Slice must be a tuple two values')
144 | if len(slice)!=2:
145 | raise Exception('Slice must be a tuple two values')
146 | a,b=slice
147 | a=None if a in ('',None) else utils.make_string(a)
148 | b=None if b in ('',None) else utils.make_string(b)
149 | if a:
150 | a=date_tools.stringToString(a,from_strfmt,to_strfmt) if '-' not in a else a
151 | if b:
152 | b=date_tools.stringToString(b,from_strfmt,to_strfmt) if '-' not in b else b
153 |
154 | return df.loc[a:b]
155 |
156 | def _get_resampled(self,rule,how={'ohlc':'last','volume':'sum'},df=None,**kwargs):
157 | """
158 | Returns a resampled DataFrame
159 |
160 | Parameters
161 | ----------
162 | rule : str
163 | the offset string or object representing target conversion
164 | for all aliases available see http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
165 | how : str or dict
166 | states the form in which the resampling will be done.
167 | Examples:
168 | how={'volume':'sum'}
169 | how='count'
170 | df : DataFrame
171 | If omitted then the QuantFigure.DataFrame is resampled.
172 | kwargs
173 | For more information see http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html
174 |
175 | """
176 | df=self.df.copy() if df is None else df
177 | if rule==None:
178 | return df
179 | else:
180 | if isinstance(how,dict):
181 | if 'ohlc' in how:
182 | v=how.pop('ohlc')
183 | for _ in ['open','high','low','close']:
184 | how[_]=v
185 | _how=how.copy()
186 | for _ in _how:
187 | if _ not in self._d:
188 | del how[_]
189 | return df.resample(rule=rule,**kwargs).apply(how)
190 |
191 | def update(self,**kwargs):
192 | """
193 | Updates the values for a QuantFigure
194 | The key-values are automatically assigned to the correct
195 | section of the QuantFigure
196 | """
197 | if 'columns' in kwargs:
198 | self._d=ta._ohlc_dict(self.df,columns=kwargs.pop('columns',None))
199 | schema=self._get_schema()
200 | annotations=kwargs.pop('annotations',None)
201 |
202 | if annotations:
203 | self.layout['annotations']['values']=utils.make_list(annotations)
204 | for k,v in list(kwargs.items()):
205 | try:
206 | utils.dict_update(self.__dict__,k,v,schema)
207 | except:
208 | self.kwargs.update({k:v})
209 |
210 | def delete(self,*args):
211 | """
212 | Deletes the values for a QuantFigure
213 | The key-values are automatically deleted from the correct
214 | section of the QuantFigure
215 | """
216 | if args:
217 | args=args[0] if utils.is_list(args[0]) else args
218 | path=utils.dict_path(self.__dict__)
219 | for _ in args:
220 | if _ in self.__dict__.keys():
221 | raise Exception('"{0}" cannot be deleted'.format(_))
222 |
223 | for a in args:
224 | try:
225 | if a in ('shapes'):
226 | self.layout[a].clear()
227 | elif a=='annotations':
228 | self.layout['annotations']={'values':[],'params':{}}
229 | else:
230 | del reduce(dict.get, path[a],self.__dict__)[a]
231 | except:
232 | raise Exception('Key: {0} not found'.format(a))
233 |
234 |
235 | def figure(self,**kwargs):
236 | """
237 |
238 | Returns a Plotly figure
239 |
240 | """
241 | kwargs['asFigure']=True
242 | return self.iplot(**kwargs)
243 |
244 | def _panel_domains(self,n=2,min_panel_size=.15,spacing=0.08,top_margin=1,bottom_margin=0):
245 | """
246 |
247 | Returns the panel domains for each axis
248 |
249 | """
250 | d={}
251 | for _ in range(n+1,1,-1):
252 | lower=round(bottom_margin+(min_panel_size+spacing)*(n+1-_),2)
253 | d['yaxis{0}'.format(_)]=dict(domain=(lower,lower+min_panel_size))
254 | top=d['yaxis2']['domain']
255 | d['yaxis2']['domain']=(top[0],top_margin)
256 | return d
257 |
258 | def _get_trendline(self,date0=None,date1=None,on=None,kind='trend',to_strfmt='%Y-%m-%d',from_strfmt='%d%b%y',**kwargs):
259 | """
260 | Returns a trendline (line), support or resistance
261 |
262 | Parameters:
263 | date0 : string
264 | Trendline starting date
265 | date1 : string
266 | Trendline end date
267 | on : string
268 | Indicate the data series in which the
269 | trendline should be based.
270 | 'close'
271 | 'high'
272 | 'low'
273 | 'open'
274 | kind : string
275 | Defines de kind of trendline
276 | 'trend'
277 | 'resistance'
278 | 'support'
279 | mode : string
280 | Defines how the support/resistance will
281 | be drawn
282 | 'starttoened' : (x0,x1)
283 | 'fromstart' : (x0,date0)
284 | 'toend' : (date0,x1)
285 | text : string
286 | If passed, then an annotation will be added
287 | to the trendline (at mid point)
288 | from_strfmt : string
289 | Defines the date formating in which
290 | date0 and date1 are stated.
291 | default: '%d%b%y'
292 | to_strfmt : string
293 | Defines the date formatting
294 | to which it should be converted.
295 | This should match the same format as the timeseries index.
296 | default : '%Y-%m-%d'
297 | """
298 | ann_values=copy.deepcopy(get_annotation_kwargs())
299 | ann_values.extend(['x','y'])
300 | ann_kwargs=utils.check_kwargs(kwargs,ann_values,{},clean_origin=True)
301 | def position(d0,d1):
302 | return d0+(d1-d0)/2
303 |
304 | date0=kwargs.pop('date',date0)
305 | date0=date_tools.stringToString(date0,from_strfmt,to_strfmt) if '-' not in date0 else date0
306 |
307 | if kind=='trend':
308 | date1=date_tools.stringToString(date1,from_strfmt,to_strfmt) if '-' not in date1 else date1
309 | on='close' if not on else on
310 | df=pd.DataFrame(self.df[self._d[on]])
311 | y0=kwargs.get('y0',df.loc[date0].values[0])
312 | y1=kwargs.get('y1',df.loc[date1].values[0])
313 |
314 |
315 | if kind in ('support','resistance'):
316 | mode=kwargs.pop('mode','starttoend')
317 | if not on:
318 | on='low' if kind=='support' else 'high'
319 | df=pd.DataFrame(self.df[self._d[on]])
320 | y0=kwargs.get('y0',df.loc[date0].values[0])
321 | y1=kwargs.get('y1',y0)
322 | if mode=='starttoend':
323 | date0=df.index[0]
324 | date1=df.index[-1]
325 | elif mode=='toend':
326 | date1=df.index[-1]
327 | elif mode=='fromstart':
328 | date1=date0
329 | date0=df.index[0]
330 |
331 | if isinstance(date0,pd.Timestamp):
332 | date0=date_tools.dateToString(date0,to_strfmt)
333 | if isinstance(date1,pd.Timestamp):
334 | date1=date_tools.dateToString(date1,to_strfmt)
335 | d={'x0':date0,'x1':date1,'y0':y0,'y1':y1}
336 | d.update(**kwargs)
337 | shape=tools.get_shape(**d)
338 |
339 |
340 | if ann_kwargs.get('text',False):
341 | ann_kwargs['x']=ann_kwargs.get('x',date_tools.dateToString(position(date_tools.stringToDate(date0,to_strfmt),date_tools.stringToDate(date1,to_strfmt)),to_strfmt))
342 | ann_kwargs['y']=ann_kwargs.get('y',position(shape['y0'],shape['y1']))
343 | else:
344 | ann_kwargs={}
345 | return {'shape':shape,'annotation':ann_kwargs}
346 |
347 | def add_trendline(self,date0,date1,on='close',text=None,**kwargs):
348 | """
349 | Adds a trendline to the QuantFigure.
350 | Given 2 dates, the trendline is connected on the data points
351 | that correspond to those dates.
352 |
353 | Parameters:
354 | date0 : string
355 | Trendline starting date
356 | date1 : string
357 | Trendline end date
358 | on : string
359 | Indicate the data series in which the
360 | trendline should be based.
361 | 'close'
362 | 'high'
363 | 'low'
364 | 'open'
365 | text : string
366 | If passed, then an annotation will be added
367 | to the trendline (at mid point)
368 |
369 | kwargs:
370 | from_strfmt : string
371 | Defines the date formating in which
372 | date0 and date1 are stated.
373 | default: '%d%b%y'
374 | to_strfmt : string
375 | Defines the date formatting
376 | to which it should be converted.
377 | This should match the same format as the timeseries index.
378 | default : '%Y-%m-%d'
379 | """
380 | d={'kind':'trend','date0':date0,'date1':date1,'on':on,'text':text}
381 | d.update(**kwargs)
382 | self.trendlines.append(d)
383 |
384 | def add_support(self,date,on='low',mode='starttoend',text=None,**kwargs):
385 | """
386 | Adds a support line to the QuantFigure
387 |
388 | Parameters:
389 | date0 : string
390 | The support line will be drawn at the 'y' level
391 | value that corresponds to this date.
392 | on : string
393 | Indicate the data series in which the
394 | support line should be based.
395 | 'close'
396 | 'high'
397 | 'low'
398 | 'open'
399 | mode : string
400 | Defines how the support/resistance will
401 | be drawn
402 | 'starttoened' : (x0,x1)
403 | 'fromstart' : (x0,date)
404 | 'toend' : (date,x1)
405 | text : string
406 | If passed, then an annotation will be added
407 | to the support line (at mid point)
408 |
409 | kwargs:
410 | from_strfmt : string
411 | Defines the date formating in which
412 | date0 and date1 are stated.
413 | default: '%d%b%y'
414 | to_strfmt : string
415 | Defines the date formatting
416 | to which it should be converted.
417 | This should match the same format as the timeseries index.
418 | default : '%Y-%m-%d'
419 | """
420 | d={'kind':'support','date':date,'mode':mode,'on':on,'text':text}
421 | d.update(**kwargs)
422 | self.trendlines.append(d)
423 |
424 | def add_resistance(self,date,on='high',mode='starttoend',text=None,**kwargs):
425 | """
426 | Adds a resistance line to the QuantFigure
427 |
428 | Parameters:
429 | date0 : string
430 | The resistance line will be drawn at the 'y' level
431 | value that corresponds to this date.
432 | on : string
433 | Indicate the data series in which the
434 | resistance should be based.
435 | 'close'
436 | 'high'
437 | 'low'
438 | 'open'
439 | mode : string
440 | Defines how the support/resistance will
441 | be drawn
442 | 'starttoened' : (x0,x1)
443 | 'fromstart' : (x0,date)
444 | 'toend' : (date,x1)
445 | text : string
446 | If passed, then an annotation will be added
447 | to the resistance (at mid point)
448 |
449 | kwargs:
450 | from_strfmt : string
451 | Defines the date formating in which
452 | date0 and date1 are stated.
453 | default: '%d%b%y'
454 | to_strfmt : string
455 | Defines the date formatting
456 | to which it should be converted.
457 | This should match the same format as the timeseries index.
458 | default : '%Y-%m-%d'
459 | """
460 | d={'kind':'resistance','date':date,'mode':mode,'on':on,'text':text}
461 | d.update(**kwargs)
462 | self.trendlines.append(d)
463 |
464 | def add_annotations(self,annotations,**kwargs):
465 | """
466 | Add an annotation to the QuantFigure.
467 |
468 | Parameters:
469 | annotations : dict or list(dict,)
470 | Annotations can be on the form form of
471 | {'date' : 'text'}
472 | and the text will automatically be placed at the
473 | right level on the chart
474 | or
475 | A Plotly fully defined annotation
476 |
477 | kwargs :
478 | fontcolor : str
479 | Text color for annotations
480 | fontsize : int
481 | Text size for annotations
482 | textangle : int
483 | Textt angle
484 | See https://plot.ly/python/reference/#layout-annotations
485 | for a complete list of valid parameters.
486 |
487 | """
488 | ann_kwargs=utils.check_kwargs(kwargs,get_annotation_kwargs(),{},clean_origin=True)
489 | if type(annotations)==list:
490 | self.layout['annotations']['values'].extend(annotations)
491 | else:
492 | self.layout['annotations']['values'].append(annotations)
493 | if ann_kwargs:
494 | self.layout['annotations']['params'].update(**ann_kwargs)
495 |
496 | def add_shapes(self,**kwargs):
497 | """
498 | Add a shape to the QuantFigure.
499 |
500 | kwargs :
501 | hline : int, list or dict
502 | Draws a horizontal line at the
503 | indicated y position(s)
504 | Extra parameters can be passed in
505 | the form of a dictionary (see shapes)
506 | vline : int, list or dict
507 | Draws a vertical line at the
508 | indicated x position(s)
509 | Extra parameters can be passed in
510 | the form of a dictionary (see shapes)
511 | hspan : (y0,y1)
512 | Draws a horizontal rectangle at the
513 | indicated (y0,y1) positions.
514 | Extra parameters can be passed in
515 | the form of a dictionary (see shapes)
516 | vspan : (x0,x1)
517 | Draws a vertical rectangle at the
518 | indicated (x0,x1) positions.
519 | Extra parameters can be passed in
520 | the form of a dictionary (see shapes)
521 | shapes : dict or list(dict)
522 | List of dictionaries with the
523 | specifications of a given shape.
524 | See help(cufflinks.tools.get_shape)
525 | for more information
526 |
527 | """
528 | kwargs=utils.check_kwargs(kwargs,get_shapes_kwargs(),{},clean_origin=True)
529 | for k,v in list(kwargs.items()):
530 | if k in self.layout['shapes']:
531 | if utils.is_list(v):
532 | self.layout['shapes'][k].extend(v)
533 | else:
534 | self.layout['shapes'][k].append(v)
535 | else:
536 | self.layout['shapes'][k]=utils.make_list(v)
537 |
538 | # def add_study(self,name,params={}):
539 | # if 'kind' in params:
540 | # if params['kind'] in self._valid_studies:
541 | # self.studies[name]=params
542 | # else:
543 | # raise Exception('Invalid study: {0}'.format(params['kind']))
544 | # else:
545 | # raise Exception('Study kind required')
546 |
547 | def _add_study(self,study):
548 | """
549 | Adds a study to QuantFigure.studies
550 |
551 | Parameters:
552 | study : dict
553 | {'kind':study_kind,
554 | 'params':study_parameters,
555 | 'display':display_parameters}
556 | """
557 | str='{study} {name}({period})' if study['params'].get('str',None)==None else study['params']['str']
558 | study['params']['str']=str
559 |
560 | if not study['name']:
561 | study['name']=ta.get_column_name(study['kind'].upper(),study=study['kind'],
562 | str=str,
563 | period=study['params'].get('periods',None),
564 | column=study['params'].get('column',None))
565 |
566 |
567 | restore=study['display'].pop('restore',False)
568 |
569 | if restore:
570 | _=self.studies.pop(study['kind'],None)
571 |
572 | if study['kind'] in self.studies:
573 | try:
574 | id='{0} ({1})'.format(study['kind'],study['params']['periods'])
575 | except:
576 | id='{0} ({1})'.format(study['kind'],'(2)')
577 | else:
578 | id=study['kind']
579 |
580 | _id=id
581 | n=1
582 | while id in self.studies:
583 | id='{0} ({1})'.format(_id,n)
584 | n+=1
585 | self.studies[id]=study
586 |
587 | def add_volume(self,colorchange=True,column=None,name='',str='{name}',**kwargs):
588 | """
589 | Add 'volume' study to QuantFigure.studies
590 |
591 | Parameters:
592 | colorchange : bool
593 | If True then each volume bar will have a fill color
594 | depending on if 'base' had a positive or negative
595 | change compared to the previous value
596 | If False then each volume bar will have a fill color
597 | depending on if the volume data itself had a positive or negative
598 | change compared to the previous value
599 | column :string
600 | Defines the data column name that contains the volume data.
601 | Default: 'volume'
602 | name : string
603 | Name given to the study
604 | str : string
605 | Label factory for studies
606 | The following wildcards can be used:
607 | {name} : Name of the column
608 | {study} : Name of the study
609 | {period} : Period used
610 | Examples:
611 | 'study: {study} - period: {period}'
612 |
613 | kwargs :
614 | base : string
615 | Defines the column which will define the
616 | positive/negative changes (if colorchange=True).
617 | Default = 'close'
618 | up_color : string
619 | Color for positive bars
620 | down_color : string
621 | Color for negative bars
622 | """
623 | if not column:
624 | column=self._d['volume']
625 | up_color=kwargs.pop('up_color',self.theme['up_color'])
626 | down_color=kwargs.pop('down_color',self.theme['down_color'])
627 | study={'kind':'volume',
628 | 'name':name,
629 | 'params':{'colorchange':colorchange,'base':'close','column':column,
630 | 'str':None},
631 | 'display':utils.merge_dict({'up_color':up_color,'down_color':down_color},kwargs)}
632 | self._add_study(study)
633 |
634 | def add_macd(self,fast_period=12,slow_period=26,signal_period=9,column=None,
635 | name='',str=None,**kwargs):
636 | """
637 | Add Moving Average Convergence Divergence (MACD) study to QuantFigure.studies
638 |
639 | Parameters:
640 | fast_period : int
641 | MACD Fast Period
642 | slow_period : int
643 | MACD Slow Period
644 | signal_period : int
645 | MACD Signal Period
646 | column :string
647 | Defines the data column name that contains the
648 | data over which the study will be applied.
649 | Default: 'close'
650 | name : string
651 | Name given to the study
652 | str : string
653 | Label factory for studies
654 | The following wildcards can be used:
655 | {name} : Name of the column
656 | {study} : Name of the study
657 | {period} : Period used
658 | Examples:
659 | 'study: {study} - period: {period}'
660 | kwargs:
661 | legendgroup : bool
662 | If true, all legend items are grouped into a
663 | single one
664 | All formatting values available on iplot()
665 | """
666 |
667 | if not column:
668 | column=self._d['close']
669 | study={'kind':'macd',
670 | 'name':name,
671 | 'params':{'fast_period':fast_period,'slow_period':slow_period,
672 | 'signal_period':signal_period,'column':column,
673 | 'str':str},
674 | 'display':utils.merge_dict({'legendgroup':False,'colors':['blue','red']},kwargs)}
675 | study['params']['periods']='[{0},{1},{2}]'.format(fast_period,slow_period,signal_period)
676 | self._add_study(study)
677 |
678 |
679 | def add_sma(self,periods=20,column=None,name='',
680 | str=None,**kwargs):
681 | """
682 | Add Simple Moving Average (SMA) study to QuantFigure.studies
683 |
684 | Parameters:
685 | periods : int or list(int)
686 | Number of periods
687 | column :string
688 | Defines the data column name that contains the
689 | data over which the study will be applied.
690 | Default: 'close'
691 | name : string
692 | Name given to the study
693 | str : string
694 | Label factory for studies
695 | The following wildcards can be used:
696 | {name} : Name of the column
697 | {study} : Name of the study
698 | {period} : Period used
699 | Examples:
700 | 'study: {study} - period: {period}'
701 | kwargs:
702 | legendgroup : bool
703 | If true, all legend items are grouped into a
704 | single one
705 | All formatting values available on iplot()
706 | """
707 | if not column:
708 | column=self._d['close']
709 | study={'kind':'sma',
710 | 'name':name,
711 | 'params':{'periods':periods,'column':column,
712 | 'str':str},
713 | 'display':utils.merge_dict({'legendgroup':False},kwargs)}
714 | self._add_study(study)
715 |
716 | def add_rsi(self,periods=20,rsi_upper=70,rsi_lower=30,showbands=True,column=None,
717 | name='',str=None,**kwargs):
718 | """
719 | Add Relative Strength Indicator (RSI) study to QuantFigure.studies
720 |
721 | Parameters:
722 | periods : int or list(int)
723 | Number of periods
724 | rsi_upper : int
725 | bounds [0,100]
726 | Upper (overbought) level
727 | rsi_lower : int
728 | bounds [0,100]
729 | Lower (oversold) level
730 | showbands : boolean
731 | If True, then the rsi_upper and
732 | rsi_lower levels are displayed
733 | column :string
734 | Defines the data column name that contains the
735 | data over which the study will be applied.
736 | Default: 'close'
737 | name : string
738 | Name given to the study
739 | str : string
740 | Label factory for studies
741 | The following wildcards can be used:
742 | {name} : Name of the column
743 | {study} : Name of the study
744 | {period} : Period used
745 | Examples:
746 | 'study: {study} - period: {period}'
747 | kwargs:
748 | legendgroup : bool
749 | If true, all legend items are grouped into a
750 | single one
751 | All formatting values available on iplot()
752 | """
753 | if not column:
754 | column=self._d['close']
755 | str=str if str else '{name}({column},{period})'
756 |
757 | study={'kind':'rsi',
758 | 'name':name,
759 | 'params':{'periods':periods,'column':column,
760 | 'str':str},
761 | 'display':utils.merge_dict({'legendgroup':True,'rsi_upper':rsi_upper,
762 | 'rsi_lower':rsi_lower,'showbands':showbands},kwargs)}
763 | self._add_study(study)
764 |
765 | def add_bollinger_bands(self,periods=20,boll_std=2,fill=True,column=None,name='',
766 | str='{name}({column},{period})',**kwargs):
767 | """
768 | Add Bollinger Bands (BOLL) study to QuantFigure.studies
769 |
770 | Parameters:
771 | periods : int or list(int)
772 | Number of periods
773 | boll_std : int
774 | Number of standard deviations for
775 | the bollinger upper and lower bands
776 | fill : boolean
777 | If True, then the innner area of the
778 | bands will filled
779 | column :string
780 | Defines the data column name that contains the
781 | data over which the study will be applied.
782 | Default: 'close'
783 | name : string
784 | Name given to the study
785 | str : string
786 | Label factory for studies
787 | The following wildcards can be used:
788 | {name} : Name of the column
789 | {study} : Name of the study
790 | {period} : Period used
791 | Examples:
792 | 'study: {study} - period: {period}'
793 | kwargs:
794 | legendgroup : bool
795 | If true, all legend items are grouped into a
796 | single one
797 | fillcolor : string
798 | Color to be used for the fill color.
799 | Example:
800 | 'rgba(62, 111, 176, .4)'
801 | All formatting values available on iplot()
802 | """
803 | if not column:
804 | column=self._d['close']
805 | study={'kind':'boll',
806 | 'name':name,
807 | 'params':{'periods':periods,'boll_std':boll_std,'column':column,
808 | 'str':str},
809 | 'display':utils.merge_dict({'legendgroup':True,'fill':fill},kwargs)}
810 | self._add_study(study)
811 |
812 | def add_ema(self,periods=20,column=None,str=None,
813 | name='',**kwargs):
814 | """
815 | Add Exponential Moving Average (EMA) study to QuantFigure.studies
816 |
817 | Parameters:
818 | periods : int or list(int)
819 | Number of periods
820 | column :string
821 | Defines the data column name that contains the
822 | data over which the study will be applied.
823 | Default: 'close'
824 | name : string
825 | Name given to the study
826 | str : string
827 | Label factory for studies
828 | The following wildcards can be used:
829 | {name} : Name of the column
830 | {study} : Name of the study
831 | {period} : Period used
832 | Examples:
833 | 'study: {study} - period: {period}'
834 | kwargs:
835 | legendgroup : bool
836 | If true, all legend items are grouped into a
837 | single one
838 | All formatting values available on iplot()
839 | """
840 | if not column:
841 | column=self._d['close']
842 | study={'kind':'ema',
843 | 'name':name,
844 | 'params':{'periods':periods,'column':column,
845 | 'str':str},
846 | 'display':utils.merge_dict({'legendgroup':False},kwargs)}
847 | self._add_study(study)
848 |
849 | def add_cci(self,periods=14,cci_upper=100,cci_lower=-100,
850 | showbands=True,str=None,name='',**kwargs):
851 | """
852 | Commodity Channel Indicator study to QuantFigure.studies
853 |
854 | Parameters:
855 | periods : int or list(int)
856 | Number of periods
857 | cci_upper : int
858 | Upper bands level
859 | default : 100
860 | cci_lower : int
861 | Lower band level
862 | default : -100
863 | showbands : boolean
864 | If True, then the cci_upper and
865 | cci_lower levels are displayed
866 | name : string
867 | Name given to the study
868 | str : string
869 | Label factory for studies
870 | The following wildcards can be used:
871 | {name} : Name of the column
872 | {study} : Name of the study
873 | {period} : Period used
874 | Examples:
875 | 'study: {study} - period: {period}'
876 | kwargs:
877 | legendgroup : bool
878 | If true, all legend items are grouped into a
879 | single one
880 | All formatting values available on iplot()
881 | """
882 | study={'kind':'cci',
883 | 'name':name,
884 | 'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'close':self._d['close'],
885 | 'str':str},
886 | 'display':utils.merge_dict({'legendgroup':True,'cci_upper':cci_upper,
887 | 'cci_lower':cci_lower,'showbands':showbands},kwargs)}
888 | self._add_study(study)
889 |
890 | def add_adx(self,periods=14,str=None,name='',**kwargs):
891 | """
892 | Add Average Directional Index (ADX) study to QuantFigure.studies
893 |
894 | Parameters:
895 | periods : int or list(int)
896 | Number of periods
897 | name : string
898 | Name given to the study
899 | str : string
900 | Label factory for studies
901 | The following wildcards can be used:
902 | {name} : Name of the column
903 | {study} : Name of the study
904 | {period} : Period used
905 | Examples:
906 | 'study: {study} - period: {period}'
907 | kwargs:
908 | legendgroup : bool
909 | If true, all legend items are grouped into a
910 | single one
911 | All formatting values available on iplot()
912 | """
913 | study={'kind':'adx',
914 | 'name':name,
915 | 'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'close':self._d['close'],
916 | 'str':str},
917 | 'display':utils.merge_dict({'legendgroup':False},kwargs)}
918 | self._add_study(study)
919 |
920 | def add_ptps(self,periods=14,af=0.2,initial='long',str=None,name='',**kwargs):
921 | """
922 | Add Parabolic SAR (PTPS) study to QuantFigure.studies
923 |
924 | Parameters:
925 | periods : int or list(int)
926 | Number of periods
927 | af : float
928 | acceleration factor
929 | initial : 'long' or 'short'
930 | Iniital position
931 | default: long
932 | name : string
933 | Name given to the study
934 | str : string
935 | Label factory for studies
936 | The following wildcards can be used:
937 | {name} : Name of the column
938 | {study} : Name of the study
939 | {period} : Period used
940 | Examples:
941 | 'study: {study} - period: {period}'
942 | kwargs:
943 | legendgroup : bool
944 | If true, all legend items are grouped into a
945 | single one
946 | All formatting values available on iplot()
947 | """
948 | study={'kind':'ptps',
949 | 'name':name,
950 | 'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'af':af,'initial':initial,
951 | 'str':str},
952 | 'display':utils.merge_dict({'legendgroup':False},kwargs)}
953 | self._add_study(study)
954 |
955 | def add_atr(self,periods=14,str=None,name='',**kwargs):
956 | """
957 | Add Average True Range (ATR) study to QuantFigure.studies
958 |
959 | Parameters:
960 | periods : int or list(int)
961 | Number of periods
962 | name : string
963 | Name given to the study
964 | str : string
965 | Label factory for studies
966 | The following wildcards can be used:
967 | {name} : Name of the column
968 | {study} : Name of the study
969 | {period} : Period used
970 | Examples:
971 | 'study: {study} - period: {period}'
972 | kwargs:
973 | legendgroup : bool
974 | If true, all legend items are grouped into a
975 | single one
976 | All formatting values available on iplot()
977 | """
978 | study={'kind':'atr',
979 | 'name':name,
980 | 'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'close':self._d['close'],
981 | 'str':str},
982 | 'display':utils.merge_dict({'legendgroup':False},kwargs)}
983 | self._add_study(study)
984 |
985 | def add_dmi(self,periods=14,str='{name}({period})',
986 | name='',**kwargs):
987 | """
988 | Add Directional Movement Index (DMI) study to QuantFigure.studies
989 |
990 | Parameters:
991 | periods : int or list(int)
992 | Number of periods
993 | name : string
994 | Name given to the study
995 | str : string
996 | Label factory for studies
997 | The following wildcards can be used:
998 | {name} : Name of the column
999 | {study} : Name of the study
1000 | {period} : Period used
1001 | Examples:
1002 | 'study: {study} - period: {period}'
1003 | kwargs:
1004 | legendgroup : bool
1005 | If true, all legend items are grouped into a
1006 | single one
1007 | All formatting values available on iplot()
1008 | """
1009 | study={'kind':'dmi',
1010 | 'name':name,
1011 | 'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'close':self._d['close'],
1012 | 'str':str},
1013 | 'display':utils.merge_dict({'legendgroup':False},kwargs)}
1014 | self._add_study(study)
1015 |
1016 |
1017 | def _get_study_figure(self,study_id,**kwargs):
1018 | study=copy.deepcopy(self.studies[study_id])
1019 | kind=study['kind']
1020 | display=study['display']
1021 | display['theme']=display.get('theme',self.theme['theme'])
1022 | params=study['params']
1023 | name=study['name']
1024 | params.update(include=False)
1025 | local_kwargs={}
1026 | _slice=kwargs.pop('slice',self.data.get('slice',(None,None)))
1027 | _resample=kwargs.pop('resample',self.data.get('resample',None))
1028 |
1029 | df=self._get_sliced(_slice).copy()
1030 | if _resample:
1031 | if utils.is_list(_resample):
1032 | df=self._get_resampled(*_resample,df=df)
1033 | elif utils.is_dict(_resample):
1034 | _resample.update(df=df)
1035 | df=self._get_resampled(**_resample)
1036 | else:
1037 | df=self._get_resampled(_resample,df=df)
1038 |
1039 | def get_params(locals_list,params,display,append_study=True):
1040 | locals_list.append('legendgroup')
1041 | local_kwargs=utils.check_kwargs(display,locals_list,{},True)
1042 | display.update(kwargs)
1043 | if append_study:
1044 | display=dict([('study_'+k,v) for k,v in display.items()])
1045 | params.update(display)
1046 | return local_kwargs,params
1047 |
1048 | if kind=='volume':
1049 | bar_colors=[]
1050 | local_kwargs,params=get_params([],params,display,False)
1051 | #Fix for 152
1052 | base_column=params['base'] if params['colorchange'] else 'volume'
1053 | base=df[self._d[base_column]]
1054 | up_color=colors.normalize(display['up_color']) if 'rgba' not in display['up_color'] else display['up_color']
1055 | down_color=colors.normalize(display['down_color']) if 'rgba' not in display['down_color'] else display['down_color']
1056 | study_kwargs=utils.kwargs_from_keyword(kwargs,{},'study')
1057 |
1058 |
1059 | for i in range(len(base)):
1060 | if i != 0:
1061 | if base[i] > base[i-1]:
1062 | bar_colors.append(up_color)
1063 | else:
1064 | bar_colors.append(down_color)
1065 | else:
1066 | bar_colors.append(down_color)
1067 | fig=df[params['column']].figure(kind='bar',theme=params['theme'],**kwargs)
1068 | fig['data'][0].update(marker=dict(color=bar_colors,line=dict(color=bar_colors)),
1069 | opacity=0.8)
1070 |
1071 | if kind in ('sma','ema','atr','adx','dmi','ptps'):
1072 | local_kwargs,params=get_params([],params,display)
1073 | fig=df.ta_figure(study=kind,**params)
1074 |
1075 | if kind=='boll':
1076 | local_kwargs,params=get_params(['fill','fillcolor'],params,display)
1077 | fig=df.ta_figure(study=kind,**params)
1078 | if local_kwargs['fill']:
1079 | fillcolor=local_kwargs.pop('fillcolor',fig['data'][2]['line']['color'] or 'rgba(200,200,200,.1)')
1080 | fillcolor=colors.to_rgba(fillcolor,.1)
1081 | fig['data'][2].update(fill='tonexty',fillcolor=fillcolor)
1082 |
1083 | if kind=='rsi':
1084 | locals_list=['rsi_lower','rsi_upper','showbands']
1085 | local_kwargs,params=get_params(locals_list,params,display)
1086 | fig=df.ta_figure(study=kind,**params)
1087 | # del fig.layout['shapes']
1088 | # if local_kwargs['showbands']:
1089 | # up_color=kwargs.get('up_color',self.theme['up_color'])
1090 | # down_color=kwargs.get('down_color',self.theme['down_color'])
1091 | # for _ in ('rsi_lower','rsi_upper'):
1092 | # trace=fig.data[0].copy()
1093 | # trace.update(y=[local_kwargs[_] for x in trace['x']])
1094 | # trace.update(name='')
1095 | # color=down_color if 'lower' in _ else up_color
1096 | # trace.update(line=dict(color=color,width=1))
1097 | # fig.data.append(trace)
1098 |
1099 | if kind=='cci':
1100 | locals_list=['cci_lower','cci_upper','showbands']
1101 | local_kwargs,params=get_params(locals_list,params,display)
1102 | fig=df.ta_figure(study=kind,**params)
1103 | # del fig.layout['shapes']
1104 | # if local_kwargs['showbands']:
1105 | # up_color=kwargs.get('up_color',self.theme['up_color'])
1106 | # down_color=kwargs.get('down_color',self.theme['down_color'])
1107 | # for _ in ('cci_lower','cci_upper'):
1108 | # trace=fig.data[0].copy()
1109 | # trace.update(y=[local_kwargs[_] for x in trace['x']])
1110 | # trace.update(name='')
1111 | # color=down_color if 'lower' in _ else up_color
1112 | # trace.update(line=dict(color=color,width=1))
1113 | # fig.data.append(trace)
1114 |
1115 | if kind=='macd':
1116 | local_kwargs,params=get_params([],params,display)
1117 | fig=df.ta_figure(study=kind,**params)
1118 |
1119 | if local_kwargs.get('legendgroup',False):
1120 | for trace in fig['data']:
1121 | trace['legendgroup'] = name
1122 | trace['showlegend'] = False
1123 | fig['data'][0].update(showlegend=True,name=name)
1124 |
1125 | ## Has Bands
1126 | if kind in ('rsi','cci'):
1127 | fig=tools.fig_to_dict(fig)
1128 | _upper='{0}_upper'.format(kind)
1129 | _lower='{0}_lower'.format(kind)
1130 | del fig['layout']['shapes']
1131 | if local_kwargs['showbands']:
1132 | up_color=kwargs.get('up_color',self.theme['up_color'])
1133 | down_color=kwargs.get('down_color',self.theme['down_color'])
1134 | for _ in (_lower,_upper):
1135 | trace=copy.deepcopy(fig['data'][0])
1136 | trace.update(y=[local_kwargs[_] for x in trace['x']])
1137 | trace.update(name='')
1138 | color=down_color if 'lower' in _ else up_color
1139 | trace.update(line=dict(color=color,width=1))
1140 | fig['data'].append(trace)
1141 |
1142 |
1143 | return fig
1144 |
1145 | def iplot(self,**kwargs):
1146 | __QUANT_FIGURE_EXPORT = ['asFigure','asUrl','asImage','asPlot','display_image','validate',
1147 | 'sharing','online','filename','dimensions']
1148 |
1149 | layout=copy.deepcopy(self.layout)
1150 | data=copy.deepcopy(self.data)
1151 | self_kwargs=copy.deepcopy(self.kwargs)
1152 |
1153 | data['slice']=kwargs.pop('slice',data.pop('slice',(None,None)))
1154 | data['resample']=kwargs.pop('resample',data.pop('resample',None))
1155 |
1156 | asFigure=kwargs.pop('asFigure',False)
1157 | showstudies=kwargs.pop('showstudies',True)
1158 | study_kwargs=utils.kwargs_from_keyword(kwargs,{},'study',True)
1159 | datalegend=kwargs.pop('datalegend',data.pop('datalegend',data.pop('showlegend',True)))
1160 | export_kwargs = utils.check_kwargs(kwargs,__QUANT_FIGURE_EXPORT)
1161 |
1162 | _slice=data.pop('slice')
1163 | _resample=data.pop('resample')
1164 |
1165 | panel_data={}
1166 | for k in ['min_panel_size','spacing','top_margin','bottom_margin']:
1167 | panel_data[k]=kwargs.pop(k,self.panels[k])
1168 |
1169 | d=self_kwargs
1170 | df=self._get_sliced(_slice).copy()
1171 | if _resample:
1172 | if utils.is_list(_resample):
1173 | df=self._get_resampled(*_resample,df=df)
1174 | elif utils.is_dict(_resample):
1175 | _resample.update(df=df)
1176 | df=self._get_resampled(**_resample)
1177 | else:
1178 | df=self._get_resampled(_resample,df=df)
1179 |
1180 | annotations=layout.pop('annotations')
1181 | shapes=layout.pop('shapes')
1182 | if not 'shapes' in shapes:
1183 | shapes['shapes']=[]
1184 | for trend in self.trendlines:
1185 | _trend=self._get_trendline(**trend)
1186 | shapes['shapes'].append(_trend['shape'])
1187 | if 'text' in _trend['annotation']:
1188 | annotations['values'].append(_trend['annotation'])
1189 | shape_kwargs=utils.check_kwargs(kwargs,get_shapes_kwargs(),{},clean_origin=True)
1190 | for k,v in list(shape_kwargs.items()):
1191 | if k in shapes:
1192 | if isinstance(v,list):
1193 | shapes[k].extend(v)
1194 | else:
1195 | shapes[k].append(v)
1196 | else:
1197 | shapes[k]=[v]
1198 | for _ in [data,layout, self._d,
1199 | self.theme,{'annotations':annotations['values']},
1200 | annotations['params'],shapes]:
1201 | if _:
1202 | d=utils.merge_dict(d,_)
1203 | d=utils.deep_update(d,kwargs)
1204 | d=tools.updateColors(d)
1205 | fig = df.figure(**d)
1206 |
1207 | if d['kind'] not in ('candle','candlestick','ohlc'):
1208 | tools._move_axis(fig, yaxis='y2') # FIXME TKP
1209 | pass
1210 | else:
1211 | if not datalegend:
1212 |
1213 | fig['data'][0]['decreasing'].update(showlegend=False)
1214 | fig['data'][0]['increasing'].update(showlegend=False)
1215 |
1216 | ## 126 Shapes in wrong axis
1217 | for shape in fig['layout']['shapes']:
1218 | if 'yref' in shape:
1219 | if len(shape['yref'])==1: #not an explicity yref
1220 | shape.update(yref='y2')
1221 |
1222 | panel_data['n']=1
1223 |
1224 | which = [x['yaxis'] for x in fig['data']]
1225 | which.sort()
1226 | max_panel=int(which[-1][1:])
1227 | figures=[]
1228 |
1229 |
1230 | if showstudies:
1231 | kwargs=utils.check_kwargs(kwargs,['theme','up_color','down_color'],{},False)
1232 | kwargs.update(**study_kwargs)
1233 | kwargs.update(slice=_slice,resample=_resample)
1234 | for k,v in list(self.studies.items()):
1235 | study_fig=self._get_study_figure(k,**kwargs)
1236 | study_fig=tools.fig_to_dict(study_fig)
1237 | if 'yaxis' in study_fig['layout']:
1238 | study_fig['layout']['yaxis1']=study_fig['layout']['yaxis'].copy()
1239 | del study_fig['layout']['yaxis']
1240 | if v['kind'] in ('boll','sma','ema','ptps'):
1241 | tools._move_axis(study_fig, yaxis='y2') # FIXME TKP
1242 | pass
1243 | if v['kind'] in ('rsi','volume','macd','atr','adx','cci','dmi'):
1244 | max_panel+=1
1245 | panel_data['n']+=1
1246 | tools._move_axis(study_fig, yaxis='y{0}'.format(max_panel)) # FIXME TKP
1247 | figures.append(study_fig)
1248 | figures.append(fig)
1249 | fig=tools.merge_figures(figures)
1250 |
1251 | try:
1252 | fig['layout']['xaxis1']['anchor']='y2'
1253 | except:
1254 | fig['layout']['xaxis']['anchor']='y2'
1255 |
1256 | domains=self._panel_domains(**panel_data)
1257 | try:
1258 | for k,v in list(domains.items()):
1259 | fig['layout'][k].update(v)
1260 | except:
1261 | fig['layout'].update(**domains)
1262 | if not d.get('rangeslider',False):
1263 | try:
1264 | del fig['layout']['yaxis1']
1265 | except:
1266 | pass
1267 | if asFigure:
1268 | return go.Figure(fig)
1269 | else:
1270 | return pt_iplot(fig, **export_kwargs)
1271 |
1272 | def __getitem__(self,key):
1273 | return self.__dict__[key]
1274 |
1275 | def __repr__(self):
1276 | _d=self.__dict__.copy()
1277 | del _d['df']
1278 | return json.dumps(_d,sort_keys=True, indent=4)
1279 |
--------------------------------------------------------------------------------
/cufflinks/ta.py:
--------------------------------------------------------------------------------
1 | ## TECHNICHAL ANALYSIS
2 | import pandas as pd
3 | import numpy as np
4 | # import talib
5 | from plotly.graph_objs import Figure
6 | from .utils import make_list
7 |
8 |
9 | class StudyError(Exception):
10 | pass
11 |
12 | def _ohlc_dict(df_or_figure,open='',high='',low='',close='',volume='',
13 | validate='',**kwargs):
14 | """
15 | Returns a dictionary with the actual column names that
16 | correspond to each of the OHLCV values.
17 |
18 | df_or_figure : DataFrame or Figure
19 | open : string
20 | Column name to be used for OPEN values
21 | high : string
22 | Column name to be used for HIGH values
23 | low : string
24 | Column name to be used for LOW values
25 | close : string
26 | Column name to be used for CLOSE values
27 | volume : string
28 | Column name to be used for VOLUME values
29 | validate : string
30 | Validates that the stated column exists
31 | Example:
32 | validate='ohv' | Will ensure Open, High
33 | and close values exist.
34 | """
35 | c_dir={}
36 | ohlcv=['open','high','low','close','volume']
37 | if type(df_or_figure)==pd.DataFrame:
38 | cnames=df_or_figure.columns
39 | elif type(df_or_figure)==Figure or type(df_or_figure) == dict:
40 | cnames=df_or_figure.axis['ref'].keys()
41 | elif type(df_or_figure)==pd.Series:
42 | cnames=[df_or_figure.name]
43 | c_min=dict([(v.lower(),v) for v in cnames])
44 | for _ in ohlcv:
45 | if _ in c_min.keys():
46 | c_dir[_]=c_min[_]
47 | else:
48 | for c in cnames:
49 | if _ in c.lower():
50 | c_dir[_]=c
51 |
52 | if open:
53 | c_dir['open']=open
54 | if high:
55 | c_dir['high']=high
56 | if low:
57 | c_dir['low']=low
58 | if close:
59 | c_dir['close']=close
60 | if volume:
61 | c_dir['volume']=volume
62 |
63 | for v in list(c_dir.values()):
64 | if v not in cnames:
65 | raise StudyError('{0} is not a valid column name'.format(v))
66 |
67 | if validate:
68 | errs=[]
69 | val=validate.lower()
70 | s_names=dict([(_[0],_) for _ in ohlcv])
71 | cols=[_[0] for _ in c_dir.keys()]
72 | for _ in val:
73 | if _ not in cols:
74 | errs.append(s_names[_])
75 | if errs:
76 | raise StudyError('Missing Columns: {0}'.format(', '.join(errs)))
77 |
78 | return c_dir
79 |
80 |
81 | def get_column_name(name,study=None,str=None,period=None,column=None,period_dict=None):
82 | if str:
83 | if period_dict:
84 | if name in period_dict:
85 | period=period_dict[name]
86 | study='' if name.lower()==study.lower() else study
87 | return str.format(study=study,period=period,column=column,name=name)
88 | else:
89 | return name
90 |
91 | def validate(df,column=None):
92 | if isinstance(df,pd.DataFrame):
93 | if column is not None:
94 | df=pd.DataFrame(df[column])
95 | _df=pd.DataFrame()
96 | elif len(df.columns)>1:
97 | raise StudyError("DataFrame needs to be a single column \n"
98 | "Or the column name needs to be specified")
99 | else:
100 | df=df.copy()
101 | _df=pd.DataFrame()
102 | column=df.columns[0]
103 | else:
104 | df=pd.DataFrame(df)
105 | _df=pd.DataFrame()
106 | column=df.columns[0]
107 | return df,_df,column
108 |
109 | def rename(df,_df,study,periods,column,include,str,detail,output=None,period_dict=None):
110 | d_name=dict([(i,get_column_name(i,study=study,str=str,
111 | period=periods,column=column,period_dict=period_dict)) for i in _df.columns])
112 |
113 | _df=_df.rename(columns=d_name)
114 | if detail:
115 | __df=_df
116 | elif output:
117 | __df=_df[[d_name[_] for _ in output]]
118 | else:
119 | __df=_df[d_name[study]]
120 |
121 | if include:
122 | return pd.concat([df,__df],axis=1)
123 | else:
124 | return __df
125 |
126 | """
127 |
128 | INIDICATORS
129 |
130 | """
131 |
132 | def rsi(df,periods=14,column=None,include=True,str='{name}({column},{period})',detail=False,**kwargs):
133 | def _rsi(df,periods,column,include,str,detail):
134 | study='RSI'
135 | df,_df,column=validate(df,column)
136 | ## === talib ====
137 | # _df['RSI']=pd.Series(talib.RSI(df[column].values,periods),index=df.index)
138 | ## === /talib ====
139 |
140 | ## === pure python ====
141 | _df['Up']=df[column].diff().apply(lambda x:x if x>0 else 0)
142 | _df['Down']=df[column].diff().apply(lambda x:-x if x<0 else 0)
143 | _df['UpAvg']=_df['Up'].rolling(window=periods).mean()
144 | _df['DownAvg']= _df['Down'].rolling(window=periods).mean()
145 | _df['RSI']=100-(100/(1+_df['UpAvg']/_df['DownAvg']))
146 | ## === /pure python ====
147 |
148 | return rename(df,_df,study,periods,column,include,str,detail)
149 | column=make_list(column)
150 | periods=make_list(periods)
151 | __df=pd.concat([_rsi(df,column=x,periods=y,include=False,str=str,detail=detail) for y in periods for x in column],axis=1)
152 | if include:
153 | return pd.concat([df,__df],axis=1)
154 | else:
155 | return __df
156 |
157 |
158 | def sma(df,periods=21,column=None,include=True,str='{name}({column},{period})',detail=False):
159 | def _sma(df,periods,column,include,str,detail=False):
160 | study='SMA'
161 | df,_df,column=validate(df,column)
162 |
163 | ## === talib ====
164 | # _df['SMA']=pd.Series(talib.MA(df[column].values,periods),index=df.index)
165 | ## === /talib ====
166 |
167 | ## === pure python ====
168 | _df['SMA']=df[column].rolling(periods).mean()
169 | ## === /pure python ====
170 |
171 | return rename(df,_df,study,periods,column,include,str,detail)
172 | column=make_list(column)
173 | periods=make_list(periods)
174 | __df=pd.concat([_sma(df,column=x,periods=y,include=False,str=str) for y in periods for x in column],axis=1)
175 | if include:
176 | return pd.concat([df,__df],axis=1)
177 | else:
178 | return __df
179 |
180 | def ema(df,periods=21,column=None,include=True,str='{name}({column},{period})',detail=False):
181 | def _ema(df,periods,column,include,str,detail=False):
182 | study='EMA'
183 | df,_df,column=validate(df,column)
184 |
185 | ## === talib ====
186 | # _df['EMA']=pd.Series(talib.EMA(df[column].values,periods),index=df.index)
187 | ## === /talib ====
188 |
189 | ## === pure python ====
190 | _df['EMA']=df[column].ewm(span=periods,min_periods=periods,adjust=False).mean()
191 | ## === /pure python ====
192 |
193 | return rename(df,_df,study,periods,column,include,str,detail)
194 | column=make_list(column)
195 | periods=make_list(periods)
196 | __df=pd.concat([_ema(df,column=x,periods=y,include=False,str=str) for y in periods for x in column],axis=1)
197 | if include:
198 | return pd.concat([df,__df],axis=1)
199 | else:
200 | return __df
201 |
202 | def dmi(df,periods=14,high='high',low='low',close='close',include=True,str='{name}({period})',**kwargs):
203 | return adx(df,periods=periods,high=high,low=low,close=close,di=True,include=include,str=str,**kwargs)
204 |
205 | def adx(df,periods=14,high='high',low='low',close='close',di=False,include=True,str='{name}({period})',**kwargs):
206 | def _adx(df,periods,high,low,close,include,str,detail):
207 | study='ADX'
208 | _df=pd.DataFrame()
209 | ## === talib ====
210 | # _df['ADX']=pd.Series(talib.ADX(df[high].values,
211 | # df[low].values,df[close].values,
212 | # periods),index=df.index)
213 | ## === /talib ====
214 |
215 | ## === pure python ====
216 | def smooth(col):
217 | sm=col.rolling(periods).sum()
218 | for _ in list(range(periods+1,len(col))):
219 | sm.iloc[_]=sm.iloc[_-1]-(1.0*sm.iloc[_-1]/periods)+col.iloc[_]
220 | return sm
221 |
222 | def smooth2(col):
223 | sm=col.rolling(periods).mean()
224 | for _ in list(range(periods*2,len(col))):
225 | sm.iloc[_]=((sm.iloc[_-1]*(periods-1))+col[_])/periods
226 | return sm
227 |
228 | _df['TR']=pd.DataFrame(dict(enumerate([df[high]-df[low],abs(df[high]-df[close].shift(1)),
229 | abs(df[low]-df[close].shift(1))]))).apply(max,axis=1)
230 | __df=pd.DataFrame(dict(enumerate([df[high]-df[high].shift(1),df[low].shift(1)-df[low]])))
231 | _df['DM+']=__df.apply(lambda x:max(x[0],0) if x[0]>x[1] else 0,axis=1)
232 | _df['DM-']=__df.apply(lambda x:max(x[1],0) if x[1]>x[0] else 0,axis=1)
233 | _df.iloc[0]=np.nan
234 | _df_smooth=_df.apply(smooth)
235 |
236 | _df['DI+']=100.0*_df_smooth['DM+']/_df_smooth['TR']
237 | _df['DI-']=100.0*_df_smooth['DM-']/_df_smooth['TR']
238 | dx=pd.DataFrame(100*abs(_df['DI+']-_df['DI-'])/(_df['DI+']+_df['DI-']))
239 |
240 | _df['ADX']=dx.apply(smooth2)[0]
241 | ## === /pure python ====
242 | return rename(df,_df,study,periods,'',include,str,detail,output=output)
243 | detail=kwargs.get('detail',False)
244 | periods=make_list(periods)
245 | output=['ADX','DI+','DI-'] if di else ['ADX']
246 | __df=pd.concat([_adx(df,periods=y,high=high,low=low,close=close,include=False,str=str,detail=detail) for y in periods],axis=1)
247 | if include:
248 | return pd.concat([df,__df],axis=1)
249 | else:
250 | return __df
251 |
252 | def atr(df,periods=14,high='high',low='low',close='close',include=True,str='{name}({period})',**kwargs):
253 | def _atr(df,periods,high,low,close,include,str,detail=False):
254 | study='ATR'
255 | _df=pd.DataFrame()
256 | ## === talib ====
257 | # _df['ATR']=pd.Series(talib.ATR(df[high].values,
258 | # df[low].values,
259 | # df[close].values,
260 | # periods),index=df.index)
261 | ## === /talib ====
262 |
263 | ## === pure python ====
264 | _df['HmL']=df[high]-df[low]
265 | _df['HmC']=abs(df[high]-df[close].shift(1))
266 | _df['LmC']=abs(df[low]-df[close].shift(1))
267 | _df['TR']=_df.apply(max,axis=1)
268 | _df['ATR']=_df['TR'].rolling(periods).mean()
269 | ## === /pure python ====
270 | return rename(df,_df,study,periods,'',include,str,detail)
271 | periods=make_list(periods)
272 | __df=pd.concat([_atr(df,periods=y,high=high,low=low,close=close,include=False,str=str) for y in periods],axis=1)
273 | if include:
274 | return pd.concat([df,__df],axis=1)
275 | else:
276 | return __df
277 |
278 | def ptps(df,periods=14,initial='long',af=.02,high='high',low='low',include=True,str='{name}({period})',**kwargs):
279 | def _ptps(df,periods,high,low,include,str,detail):
280 | study='PTPS'
281 | _df=pd.DataFrame(columns=['SAR','LorS','EP','EP+-SAR','AF','AF_Diff','T_SAR','Reversal','LONG','SHORT'],
282 | index=df.index)
283 | _df=_df.reset_index()
284 |
285 | _df.loc[0,'LorS']=initial
286 | _df.loc[0,'T_SAR']=None
287 | _df.loc[0,'EP']=df[high][0] if initial=='long' else df[low][0]
288 | _df.loc[0,'AF']=af
289 |
290 | for _ in range(1,len(df)):
291 | # LorS - Long or Short
292 | if _df['LorS'][_-1]=='long':
293 | if _df['T_SAR'][_-1]>=df[low][_]:
294 | _df.loc[_,'LorS']='short'
295 | else:
296 | _df.loc[_,'LorS']=_df.loc[_-1,'LorS']
297 | else:
298 | if _df['T_SAR'][_-1]<=df[high][_]:
299 | _df.loc[_,'LorS']='long'
300 | else:
301 | _df.loc[_,'LorS']=_df.loc[_-1,'LorS']
302 | # SAR - Stop and Reversal
303 | if _==1:
304 | _df.loc[1,'SAR']=df[low][0] if initial=='long' else df[high][0]
305 | else:
306 | _df.loc[_,'SAR']=_df['EP'][_-1] if _df['LorS'][_-1]!=_df['LorS'][_] else _df['T_SAR'][_-1]
307 |
308 | # EP - Extreme Price
309 | if _df['LorS'][_]=='long':
310 | if _df['SAR'][_]>=df[low][_]:
311 | _df.loc[_,'EP']=df[low][_]
312 | else:
313 | _df.loc[_,'EP']=df[high][_] if df[high][_]>_df['EP'][_-1] else _df['EP'][_-1]
314 | else:
315 | if _df['SAR'][_]<=df[high][_]:
316 | _df.loc[_,'EP']=df[high][_]
317 | else:
318 | _df.loc[_,'EP']=df[low][_] if df[low][_]<_df['EP'][_-1] else _df['EP'][_-1]
319 |
320 | # EP+-SAR - Extreme Price +/- Stop and Reversal
321 | _df.loc[_,'EP+-SAR']=abs(_df['EP'][_]-_df['SAR'][_])
322 |
323 | # AF - Acceleration Factor
324 | if _df['LorS'][_]!=_df['LorS'][_-1]:
325 | _df.loc[_,'AF']=af
326 | else:
327 | if _df['LorS'][_]=='long':
328 | if _df['SAR'][_]>=df[low][_]:
329 | _df.loc[_,'AF']=af
330 | else:
331 | if (df[high][_]>_df['EP'][_-1] if _df['LorS'][_]=='long' else df[low][_]<_df['EP'][_-1]):
332 | _df.loc[_,'AF']=min(0.2,af+_df['AF'][_-1])
333 | else:
334 | _df.loc[_,'AF']=_df['AF'][_-1]
335 | else:
336 | if _df['SAR'][_]<=df[high][_]:
337 | _df.loc[_,'AF']=af
338 | else:
339 | if (df[high][_]>_df['EP'][_-1] if _df['LorS'][_]=='long' else df[low][_]<_df['EP'][_-1]):
340 | _df.loc[_,'AF']=min(0.2,af+_df['AF'][_-1])
341 | else:
342 | _df.loc[_,'AF']=_df['AF'][_-1]
343 | # AF Diff
344 | _df.loc[_,'AF_Diff']=_df['EP+-SAR'][_]*_df['AF'][_]
345 |
346 | # T_SAR - Tomorrow's Stop and Reversal
347 | if _df['LorS'][_]=='long':
348 | if _df['SAR'][_]>=df[low][_]:
349 | _df.loc[_,'T_SAR']=max(_df['SAR'][_]-_df['AF_Diff'][_],df[high][_],df[high][_-1])
350 | else:
351 | _df.loc[_,'T_SAR']=min(_df['SAR'][_]+_df['AF_Diff'][_],df[low][_],df[low][_-1])
352 | else:
353 | if _df['SAR'][_]<=df[high][_]:
354 | _df.loc[_,'T_SAR']=min(_df['SAR'][_]+_df['AF_Diff'][_],df[low][_],df[low][_-1])
355 | else:
356 | _df.loc[_,'T_SAR']=max(_df['SAR'][_]-_df['AF_Diff'][_],df[low][_],df[low][_-1])
357 |
358 | # Reversal
359 | if _df['LorS'][_-1]=='long':
360 | if _df['T_SAR'][_-1]>=df[low][_]:
361 | _df.loc[_,'Reversal']=_df['T_SAR'][_-1]
362 | else:
363 | if _df['T_SAR'][_-1]<=df[high][_]:
364 | _df.loc[_,'Reversal']=_df['T_SAR'][_-1]
365 |
366 |
367 | _df['LONG']=_df.apply(lambda x:x['T_SAR'] if x['LorS']=='long' else np.nan,axis=1)
368 | _df['SHORT']=_df.apply(lambda x:x['T_SAR'] if x['LorS']=='short' else np.nan,axis=1)
369 | _df=_df.set_index('index')
370 |
371 | return rename(df,_df,study,periods,'',include,str,detail,output=output)
372 | detail=kwargs.get('detail',False)
373 | periods=make_list(periods)
374 | output=['LONG','SHORT']
375 | __df=pd.concat([_ptps(df,periods=y,high=high,low=low,include=False,str=str,detail=detail) for y in periods],axis=1)
376 | if include:
377 | return pd.concat([df,__df],axis=1)
378 | else:
379 | return __df
380 |
381 |
382 | def cci(df,periods=14,high='high',low='low',close='close',include=True,str='{name}({period})',**kwargs):
383 | def _cci(df,periods,high,low,close,include,str,detail=False):
384 | study='CCI'
385 | _df=pd.DataFrame()
386 | ## === talib ====
387 | # _df['CCI']=pd.Series(talib.CCI(df[high].values,
388 | # df[low].values,
389 | # df[close].values,
390 | # periods),index=df.index)
391 | ## === /talib ====
392 |
393 | ## === pure python ====
394 | _df['tp']=df[[low,high,close]].mean(axis=1)
395 | _df['avgTp']=_df['tp'].rolling(window=periods).mean()
396 | mad = lambda x: np.fabs(x - x.mean()).mean()
397 | _df['mad']=_df['tp'].rolling(window=periods).apply(mad)
398 | _df['CCI']=(_df['tp']-_df['avgTp'])/(0.015*_df['mad'])
399 | ## === /pure python ====
400 |
401 | return rename(df,_df,study,periods,'',include,str,detail)
402 | periods=make_list(periods)
403 | __df=pd.concat([_cci(df,periods=y,high=high,low=low,close=close,include=False,str=str) for y in periods],axis=1)
404 | if include:
405 | return pd.concat([df,__df],axis=1)
406 | else:
407 | return __df
408 |
409 | def correl(df,periods=21,columns=None,include=True,str=None,detail=False,how='value',**correl_kwargs):
410 | """
411 | how : string
412 | value
413 | pct_chg
414 | diff
415 | """
416 | def _correl(df,periods=21,columns=None,include=True,str=None,detail=False,**correl_kwargs):
417 | study='CORREL'
418 | df,_df,columns=validate(df,columns)
419 |
420 | _df['CORREL'] = df[columns[0]].rolling(window=periods,**correl_kwargs).corr(df[columns[1]])
421 |
422 | str=str if str else 'CORREL({column1},{column2},{period})'.format(column1=columns[0],column2=columns[1],period=periods)
423 | return rename(df,_df,study,periods,columns,include,str,detail)
424 | columns=df.columns if not columns else columns
425 | if len(columns) != 2:
426 | raise StudyError("2 Columns need to be specified for a correlation study")
427 | periods=make_list(periods)
428 | if how=='pct_chg':
429 | df=df[columns].pct_change()
430 | elif how=='diff':
431 | df=df[columns].diff()
432 | __df=pd.concat([_correl(df,columns=columns,periods=y,include=False,str=str) for y in periods],axis=1)
433 | if include:
434 | return pd.concat([df,__df],axis=1)
435 | else:
436 | return __df
437 |
438 | def boll(df,periods=20,boll_std=2,column=None,include=True,str='{name}({column},{period})',detail=False,**boll_kwargs):
439 | def _boll(df,periods,column):
440 | study='BOLL'
441 | df,_df,column=validate(df,column)
442 |
443 | ## === talib ====
444 | # upper,middle,lower=talib.BBANDS(df[column].values,periods,boll_std,boll_std)
445 | # _df=pd.DataFrame({'SMA':middle,'UPPER':upper,'LOWER':lower},index=df.index)
446 | ## === /talib ====
447 |
448 | ## === pure python ====
449 | _df['SMA']=df[column].rolling(window=periods).mean()
450 | _df['UPPER']=_df['SMA']+df[column].rolling(window=periods).std()*boll_std
451 | _df['LOWER']=_df['SMA']-df[column].rolling(window=periods).std()*boll_std
452 | ## === /pure python ====
453 |
454 | return rename(df,_df,study,periods,column,False,str,detail,output=output)
455 | column=make_list(column)
456 | periods=make_list(periods)
457 | output=['SMA','UPPER','LOWER']
458 | __df=pd.concat([_boll(df,column=x,periods=y) for y in periods for x in column],axis=1)
459 | if include:
460 | return pd.concat([df,__df],axis=1)
461 | else:
462 | return __df
463 |
464 |
465 | def macd(df,fast_period=12,slow_period=26,signal_period=9,column=None,include=True,str=None,detail=False,**macd_kwargs):
466 | periods='({0},{1},{2})'.format(fast_period,slow_period,signal_period)
467 | def _macd(df,column,include):
468 | study='MACD'
469 | df,_df,column=validate(df,column)
470 |
471 | ## === talib ====
472 | # macd,signal,hist=talib.MACD(df[column].values,fast_period,slow_period,signal_period)
473 | # _df=pd.DataFrame({'MACD':macd,'SIGNAL':signal},index=df.index)
474 | ## === /talib ===
475 |
476 | ## === pure python ===
477 | def __macd(s,periods):
478 | macd_f=[]
479 | factor=2.0/(periods+1)
480 | macd_f.append(s[0])
481 | for i in range(1,len(s)):
482 | macd_f.append(s[i]*factor+macd_f[i-1]*(1-factor))
483 | return pd.Series(macd_f,index=s.index)
484 | _df['FAST']=__macd(df[column],fast_period)
485 | _df['SLOW']=__macd(df[column],slow_period)
486 | _df['MACD']=_df['FAST']-_df['SLOW']
487 | _df['SIGNAL']=__macd(_df['MACD'],signal_period)
488 | ## === /pure python ===
489 |
490 | period_dict={'FAST':fast_period,
491 | 'SLOW':slow_period,
492 | 'MACD':'[{0},{1}]'.format(fast_period,slow_period),
493 | 'SIGNAL':signal_period}
494 | return rename(df,_df,study,periods,column,include,str,detail,output=output,period_dict=period_dict)
495 |
496 | if slow_period=1.9.2
2 | pandas>=0.19.2
3 | plotly>=4.1.1
4 | six>=1.9.0
5 | colorlover>=0.2.1
6 | setuptools>=34.4.1
7 | ipython>=5.3.0
8 | ipywidgets>=7.0.0
9 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | from os import path
3 | import io
4 |
5 | here = path.abspath(path.dirname(__file__))
6 | with io.open(path.join(here, 'requirements.txt'), encoding='utf-8') as f:
7 | requires = f.read().split()
8 |
9 | setup(
10 | name='cufflinks',
11 | version='0.17.4',
12 | description='Productivity Tools for Plotly + Pandas',
13 | author='Jorge Santos',
14 | author_email='santos.jorge@gmail.com',
15 | license='MIT',
16 | keywords=['pandas', 'plotly', 'plotting'],
17 | url='https://github.com/santosjorge/cufflinks',
18 | packages=['cufflinks'],
19 | package_data={'cufflinks': ['../helper/*.json']},
20 | include_package_data=True,
21 | install_requires=requires,
22 | classifiers=[
23 | 'Programming Language :: Python',
24 | 'Programming Language :: Python :: 2',
25 | 'Programming Language :: Python :: 3',
26 | ],
27 | zip_safe=False
28 | )
29 |
--------------------------------------------------------------------------------
/tests.py:
--------------------------------------------------------------------------------
1 | ###
2 | # Tests require nose 1.3.x
3 | ###
4 |
5 |
6 | import cufflinks as cf
7 | import pandas as pd
8 | import numpy as np
9 | import unittest
10 | from nose.tools import assert_equals
11 |
12 | ##
13 | ## nosetests -xv tests.py --with-coverage --cover-package=cufflinks
14 | ##
15 |
16 |
17 |
18 |
19 | class TestIPlot(unittest.TestCase):
20 | def setUp(self):
21 | self.df = pd.DataFrame(dict(x=[1, 2, 3], y=[4, 2, 1], c=[3, 1, 5]))
22 |
23 | def _iplot(self, df, **kwargs):
24 | return df.iplot(asFigure=True, **kwargs)
25 |
26 | def _ta(self, df, study, **kwargs):
27 | # print(study,kwargs)
28 | return df.ta_figure(study,**kwargs)
29 |
30 | def test_scatter_matrix(self):
31 | self.df.scatter_matrix(asFigure=True)
32 |
33 | def test_irregular_subplots():
34 | df = cf.datagen.bubble(10, 50, mode='stocks')
35 | figs = cf.figures(df, [
36 | dict(kind='histogram', keys='x', color='blue'),
37 | dict(kind='scatter', mode='markers', x='x', y='y', size=5),
38 | dict(kind='scatter', mode='markers', x='x', y='y',
39 | size=5, color='teal')],asList=True)
40 | figs.append(cf.datagen.lines(1).figure(bestfit=False, colors=['blue'],
41 | bestfit_colors=['pink']))
42 | base_layout = cf.tools.get_base_layout(figs)
43 | sp = cf.subplots(figs, shape=(3, 2), base_layout=base_layout,
44 | vertical_spacing=.15, horizontal_spacing=.03,
45 | specs=[[{'rowspan': 2}, {}], [None, {}],
46 | [{'colspan': 2}, None]],
47 | subplot_titles=['Histogram', 'Scatter 1',
48 | 'Scatter 2', 'Bestfit Line'])
49 | sp['layout'].update(showlegend=False)
50 | return sp
51 |
52 |
53 | def bar_input_argument_tests():
54 | options = {
55 | 'kind': ['bar', 'barh'],
56 | 'barmode': ['stack', 'overlay', 'group'],
57 | 'bargap': [0.1],
58 | 'subplots': [True]
59 | }
60 |
61 | def bar_test(self, **kwargs):
62 | self._iplot(self.df, **kwargs)
63 |
64 | _generate_tests(TestIPlot, bar_test, 'bar', options)
65 |
66 |
67 | def bar_row_input_argument_tests():
68 | options = {
69 | 'kind': ['bar', 'barh'],
70 | 'barmode': ['stack', 'overlay', 'group'],
71 | 'bargap': [0.1],
72 | 'subplots': [True]
73 | }
74 |
75 | def bar_row_test(self, **kwargs):
76 | self._iplot(self.df.ix[1], **kwargs)
77 |
78 | _generate_tests(TestIPlot, bar_row_test, 'bar_row', options)
79 |
80 |
81 | def histogram_input_argument_tests():
82 | options = {
83 | 'barmode': ['stack'],
84 | 'bins': [20],
85 | 'orientation': ['h', 'v'],
86 | 'histnorm': ['probability','percent','density'],
87 | 'subplots': [True],
88 | 'line_color':['blue','#fa0']
89 | }
90 |
91 | def histogram_test(self, **kwargs):
92 | self._iplot(self.df, kind='histogram', **kwargs)
93 |
94 | _generate_tests(TestIPlot, histogram_test, 'histogram', options)
95 |
96 |
97 | def heatmap_input_argument_tests():
98 | options = {}
99 |
100 | def heatmap_test(self, **kwargs):
101 | self._iplot(self.df, kind='heatmap', **kwargs)
102 | df=cf.datagen.heatmap()
103 | df.index=cf.pd.period_range('1/1/2016',periods=5)
104 | self._iplot(df,kind='heatmap', **kwargs)
105 | # df.iplot(kind='heatmap')
106 |
107 | _generate_tests(TestIPlot, heatmap_test, 'heatmap', options)
108 |
109 | def box_input_argument_tests():
110 | options = {}
111 |
112 | def box_test(self, **kwargs):
113 | self._iplot(self.df, kind='box', **kwargs)
114 |
115 | _generate_tests(TestIPlot, box_test, 'box', options)
116 |
117 |
118 | def area_plot_input_argument_tests():
119 | options = {
120 | 'fill': [True],
121 | 'opacity': [1],
122 | 'kind': ['area']
123 | }
124 |
125 | def area_test(self, **kwargs):
126 | self._iplot(self.df, **kwargs)
127 |
128 | _generate_tests(TestIPlot, area_test, 'area', options)
129 |
130 |
131 | def scatter_plot_input_argument_tests():
132 | options = {
133 | 'x': ['x'],
134 | 'y': ['y'],
135 | 'mode': ['markers'],
136 | 'symbol': ['circle-dot'],
137 | 'colors': [['orange', 'teal']],
138 | 'size': [10]
139 | }
140 |
141 | def scatter_test(self, **kwargs):
142 | self._iplot(self.df, **kwargs)
143 |
144 | _generate_tests(TestIPlot, scatter_test, 'scatter', options)
145 |
146 |
147 | def bubble_chart_argument_tests():
148 | options = {
149 | 'x': ['x'], 'y': ['y'], 'size': ['c']
150 | }
151 |
152 | def bubble_test(self, **kwargs):
153 | self._iplot(self.df, **kwargs)
154 |
155 | _generate_tests(TestIPlot, bubble_test, 'bubble', options)
156 |
157 |
158 | def subplot_input_argument_tests():
159 | options = {
160 | 'shape': [(3, 1)],
161 | 'shared_xaxes': [True],
162 | 'vertical_spacing': [0.02],
163 | 'fill': [True],
164 | 'subplot_titles': [True],
165 | 'legend': [False]
166 | }
167 |
168 | def subplot_test(self, **kwargs):
169 | self._iplot(self.df, subplots=True, **kwargs)
170 |
171 | _generate_tests(TestIPlot, subplot_test, 'subplots', options)
172 |
173 |
174 | def shape_input_argument_tests():
175 | df = cf.datagen.lines(3, columns=['a', 'b', 'c'])
176 | options = {
177 | 'hline': [
178 | [2, 4],
179 | [dict(y=-1, color='blue', width=3),
180 | dict(y=1, color='pink', dash='dash')]],
181 | 'vline': [['2015-02-10']],
182 | 'hspan': [[(-1, 1), (2, 5)]],
183 | 'vspan': [{
184 | 'x0': '2015-02-15', 'x1': '2015-03-15',
185 | 'color': 'teal', 'fill': True, 'opacity': .4}]
186 | }
187 |
188 | def shape_tests(self, **kwargs):
189 | self._iplot(df, **kwargs)
190 |
191 | _generate_tests(TestIPlot, shape_tests, 'shape', options)
192 |
193 | # colors
194 |
195 | def color_normalize_tests():
196 | c=dict([(k.lower(),v.upper()) for k,v in list(cf.cnames.items())])
197 | d={}
198 | for k,v in list(c.items()):
199 | assert_equals(v,cf.normalize(k).upper())
200 | return 2
201 |
202 | # technical analysis
203 |
204 | def ta_tests():
205 | df=cf.datagen.lines(1,500)
206 | studies=['sma']
207 | options = {
208 | 'periods' : [14]
209 | }
210 |
211 | def ta_tests(self, studies, **kwargs):
212 | for study in studies:
213 | self._ta(df, study, **kwargs)
214 |
215 | _generate_tests(TestIPlot, ta_tests, 'ta', options)
216 |
217 | def quant_figure_tests():
218 | df=cf.datagen.ohlc()
219 | qf=cf.QuantFig(df)
220 | qf.add_sma()
221 | qf.add_atr()
222 | qf.add_bollinger_bands()
223 | return qf.figure()
224 |
225 | def bestfit():
226 |
227 | df = cf.datagen.scatter()
228 | df['x'] = np.random.randint(1, 20, df.shape[0])
229 | df['y'] = df['x']
230 | df = df[['x', 'y']]
231 |
232 | options = {
233 | 'kind': ['scatter'],
234 | 'bestfit': [True],
235 | }
236 |
237 | def bestfit(self, **kwargs):
238 | self._iplot(df, **kwargs)
239 |
240 | _generate_tests(TestIPlot, bestfit, 'bestfit', options)
241 |
242 |
243 | # test generators
244 |
245 |
246 | def _generate_tests(test_class, test_func, test_name, options):
247 | from itertools import chain, combinations, product
248 |
249 | def powerset(iterable):
250 | "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
251 | s = list(iterable)
252 | return chain.from_iterable(combinations(s, r)
253 | for r in range(len(s) + 1))
254 | key_value_tuple = {}
255 | for option, values in list(options.items()):
256 | key_value_tuple[option] = [(option, i) for i in values]
257 |
258 | for option_groups in powerset(key_value_tuple.values()):
259 | for input_kwargs in product(*option_groups):
260 | kwargs = {i[0]: i[1] for i in input_kwargs}
261 | setattr(
262 | test_class,
263 | 'test_{}_{}'.format(test_name, '__'.join([
264 | '_'.join([str(s) for s in i])
265 | for i in kwargs.items()])),
266 | _generate_test(test_func, **kwargs))
267 |
268 |
269 | def _generate_test(test_func, **kwargs):
270 | def test(self):
271 | test_func(self, **kwargs)
272 |
273 | return test
274 |
275 |
276 |
277 | bar_input_argument_tests()
278 | bar_row_input_argument_tests()
279 | histogram_input_argument_tests()
280 | box_input_argument_tests()
281 | heatmap_input_argument_tests()
282 | area_plot_input_argument_tests()
283 | scatter_plot_input_argument_tests()
284 | bubble_chart_argument_tests()
285 | subplot_input_argument_tests()
286 | shape_input_argument_tests()
287 | test_irregular_subplots()
288 | color_normalize_tests()
289 | quant_figure_tests()
290 | # ta_tests()
291 | # bestfit()
292 |
293 |
294 | if __name__ == '__main__':
295 | unittest.main()
296 |
--------------------------------------------------------------------------------