├── .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 | ![3D Charts](img/ukswaps.gif) 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 | ![QuantFigure](img/qf1.png) 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 | ![Technical Analysis](img/qf2.png) 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 '' 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 += '
{0}
{1}
'.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 | --------------------------------------------------------------------------------