"""
QuantFigure allows you to create a persistent object.
Annotations and Technical Studies can be added on demand.
It accepts any dataframe with a timeseries index.
Try it out:
qf=cf.QuantFig(cf.datagen.ohlc())
qf.iplot()
"""
from __future__ import absolute_import
import json
import copy
import pandas as pd
from . import tools
from . import ta
from . import utils
from . import colors
from . import auth
from . import date_tools
__QUANT_FIGURE_DATA = ['kind','showlegend','datalegend','name','slice','resample','bestfit',
'text','title','yTitle','secondary_y_title','bestfit_colors','kind',
'colorscale','xTitle','colors','secondary_y']
__QUANT_FIGURE_LAYOUT = ['annotations','showlegend','margin','rangeselector','rangeslider','shapes',
'width','height','dimensions']
__QUANT_FIGURE_THEME = ['theme','up_color','down_color']
__QUANT_FIGURE_PANELS = ['min_panel_size','spacing','top_margin','bottom_margin']
[docs]def get_layout_kwargs():
return tools.__LAYOUT_KWARGS
[docs]def get_annotation_kwargs():
return tools.__ANN_KWARGS
[docs]def get_shapes_kwargs(): return tools.__SHAPES_KWARGS
[docs]class QuantFig(object):
def __init__(self,df,kind='candlestick',columns=None,**kwargs):
self.df=df
self.studies={}
self.data={}
self.theme={}
self.panels={}
self.layout={}
self.trendlines=[]
self.kwargs={}
# Set column names
if not columns:
columns={}
for _ in ['open','high','low','close','volume']:
columns[_]=kwargs.pop(_,'')
self._d=ta._ohlc_dict(df,**columns)
# Set initial annotations
annotations={
'values':[],
'params':utils.check_kwargs(kwargs,get_annotation_kwargs(),{},clean_origin=True)
}
ann_values=kwargs.pop('annotations',None)
if ann_values:
if utils.is_list(ann_values):
annotations['values'].extend(ann_values)
else:
annotations['values'].append(ann_values)
# self.data initial values
self.data.update(datalegend=kwargs.pop('datalegend',True),name=kwargs.pop('name','Trace 1'),kind=kind)
self.data.update(slice=kwargs.pop('slice',(None,None)),resample=kwargs.pop('resample',None))
# self.layout initial values
self.layout['shapes']=utils.check_kwargs(kwargs,get_shapes_kwargs(),{},clean_origin=True)
for k,v in list(self.layout['shapes'].items()):
if not isinstance(v,list):
self.layout['shapes'][k]=[v]
self.layout['rangeselector']=kwargs.pop('rangeselector',{'visible':False})
self.layout['rangeslider']=kwargs.pop('rangeslider',False)
self.layout['margin']=kwargs.pop('margin',dict(t=30,b=30,r=30,l=30))
self.layout['annotations']=annotations
self.layout['showlegend']=kwargs.pop('showlegend',True)
self.layout.update(utils.check_kwargs(kwargs,get_layout_kwargs(),{},clean_origin=True))
# self.theme initial values
self.theme['theme']=kwargs.pop('theme',auth.get_config_file()['theme'])
self.theme['up_color']=kwargs.pop('up_color','java')
self.theme['down_color']=kwargs.pop('down_color','grey')
# self.panels initial values
self.panels['min_panel_size']=kwargs.pop('min_panel_size',.15)
self.panels['spacing']=kwargs.pop('spacing',.08)
self.panels['top_margin']=kwargs.pop('top_margin',0.9)
self.panels['bottom_margin']=kwargs.pop('top_margin',0)
self.update(**kwargs)
def _get_schema(self):
"""
Returns a dictionary with the schema for a QuantFigure
"""
d={}
layout_kwargs=dict((_,'') for _ in get_layout_kwargs())
for _ in ('data','layout','theme','panels'):
d[_]={}
for __ in eval('__QUANT_FIGURE_{0}'.format(_.upper())):
layout_kwargs.pop(__,None)
d[_][__]=None
d['layout'].update(annotations=dict(values=[],
params=utils.make_dict_from_list(get_annotation_kwargs())))
d['layout'].update(shapes=utils.make_dict_from_list(get_shapes_kwargs()))
[layout_kwargs.pop(_,None) for _ in get_annotation_kwargs()+get_shapes_kwargs()]
d['layout'].update(**layout_kwargs)
return d
def _get_sliced(self,slice,df=None):
"""
Returns a sliced DataFrame
Parameters
----------
slice : tuple(from,to)
from : str
to : str
States the 'from' and 'to' values which
will get rendered as df.ix[from:to]
df : DataFrame
If omitted then the QuantFigure.DataFrame is resampled.
"""
df=self.df.copy() if df==None else df
if type(slice) not in (list,tuple):
raise Exception('Slice must be a tuple two values')
if len(slice)!=2:
raise Exception('Slice must be a tuple two values')
a,b=slice
a=None if a in ('',None) else utils.make_string(a)
b=None if b in ('',None) else utils.make_string(b)
return df.ix[a:b]
def _get_resampled(self,rule,how={'ohlc':'last','volume':'sum'},df=None,**kwargs):
"""
Returns a resampled DataFrame
Parameters
----------
rule : str
the offset string or object representing target conversion
for all aliases available see http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases
how : str or dict
states the form in which the resampling will be done.
Examples:
how={'volume':'sum'}
how='count'
df : DataFrame
If omitted then the QuantFigure.DataFrame is resampled.
kwargs
For more information see http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.resample.html
"""
df=self.df.copy() if df is None else df
if rule==None:
return df
else:
if isinstance(how,dict):
if 'ohlc' in how:
v=how.pop('ohlc')
for _ in ['open','high','low','close']:
how[_]=v
_how=how.copy()
for _ in _how:
if _ not in self._d:
del how[_]
return df.resample(rule=rule,**kwargs).apply(how)
[docs] def update(self,**kwargs):
"""
Updates the values for a QuantFigure
The key-values are automatically assigned to the correct
section of the QuantFigure
"""
if 'columns' in kwargs:
self._d=ta._ohlc_dict(self.df,columns=kwargs.pop('columns',None))
schema=self._get_schema()
annotations=kwargs.pop('annotations',None)
if annotations:
self.layout['annotations']['values']=utils.make_list(annotations)
for k,v in list(kwargs.items()):
try:
utils.dict_update(self.__dict__,k,v,schema)
except:
self.kwargs.update({k:v})
[docs] def delete(self,*args):
"""
Deletes the values for a QuantFigure
The key-values are automatically deleted from the correct
section of the QuantFigure
"""
if args:
args=args[0] if utils.is_list(args[0]) else args
path=utils.dict_path(self.__dict__)
for _ in args:
if _ in self.__dict__.keys():
raise Exception('"{0}" cannot be deleted'.format(_))
for a in args:
try:
if a in ('shapes'):
self.layout[a].clear()
elif a=='annotations':
self.layout['annotations']={'values':[],'params':{}}
else:
del reduce(dict.get, path[a],self.__dict__)[a]
except:
raise Exception('Key: {0} not found'.format(a))
def _panel_domains(self,n=2,min_panel_size=.15,spacing=0.08,top_margin=1,bottom_margin=0):
"""
Returns the panel domains for each axis
"""
d={}
for _ in range(n+1,1,-1):
lower=round(bottom_margin+(min_panel_size+spacing)*(n+1-_),2)
d['yaxis{0}'.format(_)]=dict(domain=(lower,lower+min_panel_size))
top=d['yaxis2']['domain']
d['yaxis2']['domain']=(top[0],top_margin)
return d
def _get_trendline(self,date0=None,date1=None,on=None,kind='trend',to_strfmt='%Y-%m-%d',from_strfmt='%d%b%y',**kwargs):
"""
Returns a trendline (line), support or resistance
Parameters:
date0 : string
Trendline starting date
date1 : string
Trendline end date
on : string
Indicate the data series in which the
trendline should be based.
'close'
'high'
'low'
'open'
kind : string
Defines de kind of trendline
'trend'
'resistance'
'support'
mode : string
Defines how the support/resistance will
be drawn
'starttoened' : (x0,x1)
'fromstart' : (x0,date0)
'toend' : (date0,x1)
text : string
If passed, then an annotation will be added
to the trendline (at mid point)
from_strfmt : string
Defines the date formating in which
date0 and date1 are stated.
default: '%d%b%y'
to_strfmt : string
Defines the date formatting
to which it should be converted.
This should match the same format as the timeseries index.
default : '%Y-%m-%d'
"""
ann_values=copy.deepcopy(get_annotation_kwargs())
ann_values.extend(['x','y'])
ann_kwargs=utils.check_kwargs(kwargs,ann_values,{},clean_origin=True)
def position(d0,d1):
return d0+(d1-d0)/2
date0=kwargs.pop('date',date0)
date0=date_tools.stringToString(date0,from_strfmt,to_strfmt) if '-' not in date0 else date0
if kind=='trend':
date1=date_tools.stringToString(date1,from_strfmt,to_strfmt) if '-' not in date1 else date1
on='close' if not on else on
df=pd.DataFrame(self.df[self._d[on]])
y0=kwargs.get('y0',df.ix[date0].values[0])
y1=kwargs.get('y1',df.ix[date1].values[0])
if kind in ('support','resistance'):
mode=kwargs.pop('mode','starttoend')
if not on:
on='low' if kind=='support' else 'high'
df=pd.DataFrame(self.df[self._d[on]])
y0=kwargs.get('y0',df.ix[date0].values[0])
y1=kwargs.get('y1',y0)
if mode=='starttoend':
date0=df.index[0]
date1=df.index[-1]
elif mode=='toend':
date1=df.index[-1]
elif mode=='fromstart':
date1=date0
date0=df.index[0]
if isinstance(date0,pd.Timestamp):
date0=date_tools.dateToString(date0,to_strfmt)
if isinstance(date1,pd.Timestamp):
date1=date_tools.dateToString(date1,to_strfmt)
d={'x0':date0,'x1':date1,'y0':y0,'y1':y1}
d.update(**kwargs)
shape=tools.get_shape(**d)
if ann_kwargs.get('text',False):
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))
ann_kwargs['y']=ann_kwargs.get('y',position(shape['y0'],shape['y1']))
else:
ann_kwargs={}
return {'shape':shape,'annotation':ann_kwargs}
[docs] def add_trendline(self,date0,date1,on='close',text=None,**kwargs):
"""
Adds a trendline to the QuantFigure.
Given 2 dates, the trendline is connected on the data points
that correspond to those dates.
Parameters:
date0 : string
Trendline starting date
date1 : string
Trendline end date
on : string
Indicate the data series in which the
trendline should be based.
'close'
'high'
'low'
'open'
text : string
If passed, then an annotation will be added
to the trendline (at mid point)
kwargs:
from_strfmt : string
Defines the date formating in which
date0 and date1 are stated.
default: '%d%b%y'
to_strfmt : string
Defines the date formatting
to which it should be converted.
This should match the same format as the timeseries index.
default : '%Y-%m-%d'
"""
d={'kind':'trend','date0':date0,'date1':date1,'on':on,'text':text}
d.update(**kwargs)
self.trendlines.append(d)
[docs] def add_support(self,date,on='low',mode='starttoend',text=None,**kwargs):
"""
Adds a support line to the QuantFigure
Parameters:
date0 : string
The support line will be drawn at the 'y' level
value that corresponds to this date.
on : string
Indicate the data series in which the
support line should be based.
'close'
'high'
'low'
'open'
mode : string
Defines how the support/resistance will
be drawn
'starttoened' : (x0,x1)
'fromstart' : (x0,date)
'toend' : (date,x1)
text : string
If passed, then an annotation will be added
to the support line (at mid point)
kwargs:
from_strfmt : string
Defines the date formating in which
date0 and date1 are stated.
default: '%d%b%y'
to_strfmt : string
Defines the date formatting
to which it should be converted.
This should match the same format as the timeseries index.
default : '%Y-%m-%d'
"""
d={'kind':'support','date':date,'mode':mode,'on':on,'text':text}
d.update(**kwargs)
self.trendlines.append(d)
[docs] def add_resistance(self,date,on='high',mode='starttoend',text=None,**kwargs):
"""
Adds a resistance line to the QuantFigure
Parameters:
date0 : string
The resistance line will be drawn at the 'y' level
value that corresponds to this date.
on : string
Indicate the data series in which the
resistance should be based.
'close'
'high'
'low'
'open'
mode : string
Defines how the support/resistance will
be drawn
'starttoened' : (x0,x1)
'fromstart' : (x0,date)
'toend' : (date,x1)
text : string
If passed, then an annotation will be added
to the resistance (at mid point)
kwargs:
from_strfmt : string
Defines the date formating in which
date0 and date1 are stated.
default: '%d%b%y'
to_strfmt : string
Defines the date formatting
to which it should be converted.
This should match the same format as the timeseries index.
default : '%Y-%m-%d'
"""
d={'kind':'resistance','date':date,'mode':mode,'on':on,'text':text}
d.update(**kwargs)
self.trendlines.append(d)
[docs] def add_annotations(self,annotations,**kwargs):
"""
Add an annotation to the QuantFigure.
Parameters:
annotations : dict or list(dict,)
Annotations can be on the form form of
{'date' : 'text'}
and the text will automatically be placed at the
right level on the chart
or
A Plotly fully defined annotation
kwargs :
fontcolor : str
Text color for annotations
fontsize : int
Text size for annotations
textangle : int
Textt angle
See https://plot.ly/python/reference/#layout-annotations
for a complete list of valid parameters.
"""
ann_kwargs=utils.check_kwargs(kwargs,get_annotation_kwargs(),{},clean_origin=True)
if type(annotations)==list:
self.layout['annotations']['values'].extend(annotations)
else:
self.layout['annotations']['values'].append(annotations)
if ann_kwargs:
self.layout['annotations']['params'].update(**ann_kwargs)
[docs] def add_shapes(self,**kwargs):
"""
Add a shape to the QuantFigure.
kwargs :
hline : int, list or dict
Draws a horizontal line at the
indicated y position(s)
Extra parameters can be passed in
the form of a dictionary (see shapes)
vline : int, list or dict
Draws a vertical line at the
indicated x position(s)
Extra parameters can be passed in
the form of a dictionary (see shapes)
hspan : (y0,y1)
Draws a horizontal rectangle at the
indicated (y0,y1) positions.
Extra parameters can be passed in
the form of a dictionary (see shapes)
vspan : (x0,x1)
Draws a vertical rectangle at the
indicated (x0,x1) positions.
Extra parameters can be passed in
the form of a dictionary (see shapes)
shapes : dict or list(dict)
List of dictionaries with the
specifications of a given shape.
See help(cufflinks.tools.get_shape)
for more information
"""
kwargs=utils.check_kwargs(kwargs,get_shapes_kwargs(),{},clean_origin=True)
for k,v in list(kwargs.items()):
if k in self.layout['shapes']:
if utils.is_list(v):
self.layout['shapes'][k].extend(v)
else:
self.layout['shapes'][k].append(v)
else:
self.layout['shapes'][k]=utils.make_list(v)
# def add_study(self,name,params={}):
# if 'kind' in params:
# if params['kind'] in self._valid_studies:
# self.studies[name]=params
# else:
# raise Exception('Invalid study: {0}'.format(params['kind']))
# else:
# raise Exception('Study kind required')
def _add_study(self,study):
"""
Adds a study to QuantFigure.studies
Parameters:
study : dict
{'kind':study_kind,
'params':study_parameters,
'display':display_parameters}
"""
str='{study} {name}({period})' if study['params'].get('str',None)==None else study['params']['str']
study['params']['str']=str
if not study['name']:
study['name']=ta.get_column_name(study['kind'].upper(),study=study['kind'],
str=str,
period=study['params'].get('periods',None),
column=study['params'].get('column',None))
restore=study['display'].pop('restore',False)
if restore:
_=self.studies.pop(study['kind'],None)
if study['kind'] in self.studies:
try:
id='{0} ({1})'.format(study['kind'],study['params']['periods'])
except:
id='{0} ({1})'.format(study['kind'],'(2)')
else:
id=study['kind']
_id=id
n=1
while id in self.studies:
id='{0} ({1})'.format(_id,n)
n+=1
self.studies[id]=study
[docs] def add_volume(self,colorchange=True,column=None,name='',str='{name}',**kwargs):
"""
Add 'volume' study to QuantFigure.studies
Parameters:
colorchange : bool
If true then each volume bar will have a fill color
depending on if 'base' had a positive or negative
change compared to the previous value
column :string
Defines the data column name that contains the volume data.
Default: 'volume'
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs :
base : string
Defines the column which will define the
positive/negative changes (if colorchange=True).
Default = 'close'
up_color : string
Color for positive bars
down_color : string
Color for negative bars
"""
if not column:
column=self._d['volume']
up_color=kwargs.pop('up_color',self.theme['up_color'])
down_color=kwargs.pop('down_color',self.theme['down_color'])
study={'kind':'volume',
'name':name,
'params':{'changecolor':True,'base':'close','column':column,
'str':None},
'display':utils.merge_dict({'up_color':up_color,'down_color':down_color},kwargs)}
self._add_study(study)
[docs] def add_macd(self,fast_period=12,slow_period=26,signal_period=9,column=None,
name='',str=None,**kwargs):
"""
Add Moving Average Convergence Divergence (MACD) study to QuantFigure.studies
Parameters:
fast_period : int
MACD Fast Period
slow_period : int
MACD Slow Period
signal_period : int
MACD Signal Period
column :string
Defines the data column name that contains the
data over which the study will be applied.
Default: 'close'
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
if not column:
column=self._d['close']
study={'kind':'macd',
'name':name,
'params':{'fast_period':fast_period,'slow_period':slow_period,
'signal_period':signal_period,'column':column,
'str':str},
'display':utils.merge_dict({'legendgroup':False,'colors':['blue','red']},kwargs)}
study['params']['periods']='[{0},{1},{2}]'.format(fast_period,slow_period,signal_period)
self._add_study(study)
[docs] def add_sma(self,periods=20,column=None,name='',
str=None,**kwargs):
"""
Add Simple Moving Average (SMA) study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
column :string
Defines the data column name that contains the
data over which the study will be applied.
Default: 'close'
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
if not column:
column=self._d['close']
study={'kind':'sma',
'name':name,
'params':{'periods':periods,'column':column,
'str':str},
'display':utils.merge_dict({'legendgroup':False},kwargs)}
self._add_study(study)
[docs] def add_rsi(self,periods=20,rsi_upper=70,rsi_lower=30,showbands=True,column=None,
name='',str=None,**kwargs):
"""
Add Relative Strength Indicator (RSI) study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
rsi_upper : int
bounds [0,100]
Upper (overbought) level
rsi_lower : int
bounds [0,100]
Lower (oversold) level
showbands : boolean
If True, then the rsi_upper and
rsi_lower levels are displayed
column :string
Defines the data column name that contains the
data over which the study will be applied.
Default: 'close'
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
if not column:
column=self._d['close']
str=str if str else '{name}({column},{period})'
study={'kind':'rsi',
'name':name,
'params':{'periods':periods,'column':column,
'str':str},
'display':utils.merge_dict({'legendgroup':True,'rsi_upper':rsi_upper,
'rsi_lower':rsi_lower,'showbands':showbands},kwargs)}
self._add_study(study)
[docs] def add_bollinger_bands(self,periods=20,boll_std=2,fill=True,column=None,name='',
str='{name}({column},{period})',**kwargs):
"""
Add Bollinger Bands (BOLL) study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
boll_std : int
Number of standard deviations for
the bollinger upper and lower bands
fill : boolean
If True, then the innner area of the
bands will filled
column :string
Defines the data column name that contains the
data over which the study will be applied.
Default: 'close'
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
fillcolor : string
Color to be used for the fill color.
Example:
'rgba(62, 111, 176, .4)'
All formatting values available on iplot()
"""
if not column:
column=self._d['close']
study={'kind':'boll',
'name':name,
'params':{'periods':periods,'boll_std':boll_std,'column':column,
'str':str},
'display':utils.merge_dict({'legendgroup':True,'fill':fill},kwargs)}
self._add_study(study)
[docs] def add_ema(self,periods=20,column=None,str=None,
name='',**kwargs):
"""
Add Exponential Moving Average (EMA) study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
column :string
Defines the data column name that contains the
data over which the study will be applied.
Default: 'close'
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
if not column:
column=self._d['close']
study={'kind':'ema',
'name':name,
'params':{'periods':periods,'column':column,
'str':str},
'display':utils.merge_dict({'legendgroup':False},kwargs)}
self._add_study(study)
[docs] def add_cci(self,periods=14,cci_upper=100,cci_lower=-100,
showbands=True,str=None,name='',**kwargs):
"""
Commodity Channel Indicator study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
cci_upper : int
Upper bands level
default : 100
cci_lower : int
Lower band level
default : -100
showbands : boolean
If True, then the cci_upper and
cci_lower levels are displayed
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
study={'kind':'cci',
'name':name,
'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'close':self._d['close'],
'str':str},
'display':utils.merge_dict({'legendgroup':True,'cci_upper':cci_upper,
'cci_lower':cci_lower,'showbands':showbands},kwargs)}
self._add_study(study)
[docs] def add_adx(self,periods=14,str=None,name='',**kwargs):
"""
Add Average Directional Index (ADX) study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
study={'kind':'adx',
'name':name,
'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'close':self._d['close'],
'str':str},
'display':utils.merge_dict({'legendgroup':False},kwargs)}
self._add_study(study)
[docs] def add_ptps(self,periods=14,af=0.2,initial='long',str=None,name='',**kwargs):
"""
Add Parabolic SAR (PTPS) study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
af : float
acceleration factor
initial : 'long' or 'short'
Iniital position
default: long
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
study={'kind':'ptps',
'name':name,
'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'af':af,'initial':initial,
'str':str},
'display':utils.merge_dict({'legendgroup':False},kwargs)}
self._add_study(study)
[docs] def add_atr(self,periods=14,str=None,name='',**kwargs):
"""
Add Average True Range (ATR) study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
study={'kind':'atr',
'name':name,
'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'close':self._d['close'],
'str':str},
'display':utils.merge_dict({'legendgroup':False},kwargs)}
self._add_study(study)
[docs] def add_dmi(self,periods=14,str='{name}({period})',
name='',**kwargs):
"""
Add Directional Movement Index (DMI) study to QuantFigure.studies
Parameters:
periods : int or list(int)
Number of periods
name : string
Name given to the study
str : string
Label factory for studies
The following wildcards can be used:
{name} : Name of the column
{study} : Name of the study
{period} : Period used
Examples:
'study: {study} - period: {period}'
kwargs:
legendgroup : bool
If true, all legend items are grouped into a
single one
All formatting values available on iplot()
"""
study={'kind':'dmi',
'name':name,
'params':{'periods':periods,'high':self._d['high'],'low':self._d['low'],'close':self._d['close'],
'str':str},
'display':utils.merge_dict({'legendgroup':False},kwargs)}
self._add_study(study)
def _get_study_figure(self,study_id,**kwargs):
study=copy.deepcopy(self.studies[study_id])
kind=study['kind']
display=study['display']
display['theme']=display.get('theme',self.theme['theme'])
params=study['params']
name=study['name']
params.update(include=False)
local_kwargs={}
_slice=kwargs.pop('slice',self.data.get('slice',(None,None)))
_resample=kwargs.pop('resample',self.data.get('resample',None))
df=self._get_sliced(_slice).copy()
if _resample:
if utils.is_list(_resample):
df=self._get_resampled(*_resample,df=df)
elif utils.is_dict(_resample):
_resample.update(df=df)
df=self._get_resampled(**_resample)
else:
df=self._get_resampled(_resample,df=df)
def get_params(locals_list,params,display,append_study=True):
locals_list.append('legendgroup')
local_kwargs=utils.check_kwargs(display,locals_list,{},True)
display.update(kwargs)
if append_study:
display=dict([('study_'+k,v) for k,v in display.items()])
params.update(display)
return local_kwargs,params
if kind=='volume':
bar_colors=[]
local_kwargs,params=get_params([],params,display,False)
base=df[self._d[params['base']]]
up_color=colors.normalize(display['up_color']) if 'rgba' not in display['up_color'] else display['up_color']
down_color=colors.normalize(display['down_color']) if 'rgba' not in display['down_color'] else display['down_color']
study_kwargs=utils.kwargs_from_keyword(kwargs,{},'study')
for i in range(len(base)):
if i != 0:
if base[i] > base[i-1]:
bar_colors.append(up_color)
else:
bar_colors.append(down_color)
else:
bar_colors.append(down_color)
fig=df[params['column']].figure(kind='bar',theme=params['theme'],**kwargs)
fig.data[0].update(marker=dict(color=bar_colors,line=dict(color=bar_colors)),
opacity=0.8)
if kind in ('sma','ema','atr','adx','dmi','ptps'):
local_kwargs,params=get_params([],params,display)
fig=df.ta_figure(study=kind,**params)
if kind=='boll':
local_kwargs,params=get_params(['fill','fillcolor'],params,display)
fig=df.ta_figure(study=kind,**params)
if local_kwargs['fill']:
fillcolor=local_kwargs.pop('fillcolor',fig.data[2].line.get('color','rgba(200,200,200,.1)'))
fillcolor=colors.to_rgba(fillcolor,.1)
fig.data[2].update(fill='tonexty',fillcolor=fillcolor)
if kind=='rsi':
locals_list=['rsi_lower','rsi_upper','showbands']
local_kwargs,params=get_params(locals_list,params,display)
fig=df.ta_figure(study=kind,**params)
# del fig.layout['shapes']
# if local_kwargs['showbands']:
# up_color=kwargs.get('up_color',self.theme['up_color'])
# down_color=kwargs.get('down_color',self.theme['down_color'])
# for _ in ('rsi_lower','rsi_upper'):
# trace=fig.data[0].copy()
# trace.update(y=[local_kwargs[_] for x in trace['x']])
# trace.update(name='')
# color=down_color if 'lower' in _ else up_color
# trace.update(line=dict(color=color,width=1))
# fig.data.append(trace)
if kind=='cci':
locals_list=['cci_lower','cci_upper','showbands']
local_kwargs,params=get_params(locals_list,params,display)
fig=df.ta_figure(study=kind,**params)
# del fig.layout['shapes']
# if local_kwargs['showbands']:
# up_color=kwargs.get('up_color',self.theme['up_color'])
# down_color=kwargs.get('down_color',self.theme['down_color'])
# for _ in ('cci_lower','cci_upper'):
# trace=fig.data[0].copy()
# trace.update(y=[local_kwargs[_] for x in trace['x']])
# trace.update(name='')
# color=down_color if 'lower' in _ else up_color
# trace.update(line=dict(color=color,width=1))
# fig.data.append(trace)
if kind=='macd':
local_kwargs,params=get_params([],params,display)
fig=df.ta_figure(study=kind,**params)
if local_kwargs.get('legendgroup',False):
fig.update_traces(legendgroup=name,showlegend=False)
fig.data[0].update(showlegend=True,name=name)
## Has Bands
if kind in ('rsi','cci'):
_upper='{0}_upper'.format(kind)
_lower='{0}_lower'.format(kind)
del fig.layout['shapes']
if local_kwargs['showbands']:
up_color=kwargs.get('up_color',self.theme['up_color'])
down_color=kwargs.get('down_color',self.theme['down_color'])
for _ in (_lower,_upper):
trace=fig.data[0].copy()
trace.update(y=[local_kwargs[_] for x in trace['x']])
trace.update(name='')
color=down_color if 'lower' in _ else up_color
trace.update(line=dict(color=color,width=1))
fig.data.append(trace)
return fig
[docs] def iplot(self,**kwargs):
__QUANT_FIGURE_EXPORT = ['asFigure','asUrl','asImage','asPlot','display_image','validate',
'sharing','online','filename','dimensions']
layout=copy.deepcopy(self.layout)
data=copy.deepcopy(self.data)
self_kwargs=copy.deepcopy(self.kwargs)
data['slice']=kwargs.pop('slice',data.pop('slice',(None,None)))
data['resample']=kwargs.pop('resample',data.pop('resample',None))
asFigure=kwargs.pop('asFigure',False)
showstudies=kwargs.pop('showstudies',True)
study_kwargs=utils.kwargs_from_keyword(kwargs,{},'study',True)
datalegend=kwargs.pop('datalegend',data.pop('datalegend',data.pop('showlegend',True)))
export_kwargs = utils.check_kwargs(kwargs,__QUANT_FIGURE_EXPORT)
_slice=data.pop('slice')
_resample=data.pop('resample')
panel_data={}
for k in ['min_panel_size','spacing','top_margin','bottom_margin']:
panel_data[k]=kwargs.pop(k,self.panels[k])
d=self_kwargs
df=self._get_sliced(_slice).copy()
if _resample:
if utils.is_list(_resample):
df=self._get_resampled(*_resample,df=df)
elif utils.is_dict(_resample):
_resample.update(df=df)
df=self._get_resampled(**_resample)
else:
df=self._get_resampled(_resample,df=df)
annotations=layout.pop('annotations')
shapes=layout.pop('shapes')
if not 'shapes' in shapes:
shapes['shapes']=[]
for trend in self.trendlines:
_trend=self._get_trendline(**trend)
shapes['shapes'].append(_trend['shape'])
if 'text' in _trend['annotation']:
annotations['values'].append(_trend['annotation'])
shape_kwargs=utils.check_kwargs(kwargs,get_shapes_kwargs(),{},clean_origin=True)
for k,v in list(shape_kwargs.items()):
if k in shapes:
shapes[k].append(v)
else:
shapes[k]=[v]
for _ in [data,layout, self._d,
self.theme,{'annotations':annotations['values']},
annotations['params'],shapes]:
if _:
d=utils.merge_dict(d,_)
d=utils.deep_update(d,kwargs)
d=tools.updateColors(d)
fig=df.figure(**d)
if d['kind'] not in ('candle','candlestick','ohlc'):
fig.move_axis(yaxis='y2')
else:
if not datalegend:
fig.data[0]['decreasing'].update(showlegend=False)
fig.data[0]['increasing'].update(showlegend=False)
panel_data['n']=1
which=fig.axis['which']['y']
which.sort()
max_panel=int(which[-1][1:])
figures=[]
if showstudies:
kwargs=utils.check_kwargs(kwargs,['theme','up_color','down_color'],{},False)
kwargs.update(**study_kwargs)
kwargs.update(slice=_slice,resample=_resample)
for k,v in list(self.studies.items()):
study_fig=self._get_study_figure(k,**kwargs)
if v['kind'] in ('boll','sma','ema','ptps'):
study_fig.move_axis(yaxis='y2')
if v['kind'] in ('rsi','volume','macd','atr','adx','cci','dmi'):
max_panel+=1
panel_data['n']+=1
study_fig.move_axis(yaxis='y{0}'.format(max_panel))
figures.append(study_fig)
figures.append(fig)
fig=tools.merge_figures(figures)
fig['layout']['xaxis1']['anchor']='y2'
domains=self._panel_domains(**panel_data)
fig.layout.update(**domains)
if not d.get('rangeslider',False):
try:
del fig['layout']['yaxis1']
except:
pass
if asFigure:
return fig
else:
return fig.iplot(**export_kwargs)
def __getitem__(self,key):
return self.__dict__[key]
def __repr__(self):
_d=self.__dict__.copy()
del _d['df']
print(json.dumps(_d,sort_keys=True, indent=4))
return ''