ServerPortfolio  2.0
Python parsers and server
 All Classes Namespaces Files Functions Variables Properties Pages
StockTemplates.py
Go to the documentation of this file.
1 ## @package serverportfolio.StockTemplates
2 # @brief Define the global variable @ref serverportfolio.StockTemplates.StTmpl "StockTemplates.StTmpl" and dictionary templates.
3 #
4 # Last Changed $Id: StockTemplates.py 29 2015-05-05 11:09:54Z michael $
5 
6 import copy, datetime, logging
7 
9 from serverportfolio import Utils
10 
11 ## @class StockTemplates
12 # @brief Define template dictionaries to describe the data structure.
13 #
14 # Templates are named by the "action" to be performed by the parsers.\n
15 # XML output extends the action template.\n
16 #
17 # It is used by a global variable which must be accessed by StockTemplates.StTmpl\n
18 # Group templates, by use of dictionaries, to be included by Stock when they run a specific request (EAction, e.g. InstValue, HistPrice..)\n
19 # 'Static' data are a particular template which is read from the configuration file (dictstocks.txt by default) by DictionaryStocks.\n
21 
22  # logger as class member
23  _logger = logging.getLogger("SP.StockTemplate")
24  _logger.debug( "Load StockTemplate" )
25 
26  # import math
27  # x=float('nan') math.isnan
28  # YahooCSV, all entries specified
29  # YQL given entry with name
30  ## Fundamental data, used by YQL and YahooCSV
31  # @note to copy with copy.deepcopy( templ_dict[ key ] )
32 
33  ## @brief Template for fundamental data, used by YahooYQL(default) and YahooCSV
34  _templ_fundamental = {
35  # fixed fields
36  'MarketCapitalization' : float(0), #append=false, not here
37  'SharesOwned' : int(0),
38  'DividendYield' : float(0),
39  'BVPS' : float(0),
40  'PriceBook' : float(0),
41  'PER' : float(0),
42  'PEG' : float(0),
43  # variable field, only YQL may report other entries, YahooCSV query could be modified as well for test
44  'other' : {},
45  #'error' : None,
46  # add source for each action to be saved in XML. Common, to include by default by an other template ?(one_value_xml)
47  # new added, strange, can delete ?, used by parser to save the source. created on the fly
48  # 'source' : str()
49  }
50 
51  ## @brief Normally these entries should not change.
52  # Executed by YQL, quotes or quote table give the name
53  _templ_info = {
54  # Name of the company
55  'Name' : str(),
56  # sector/ industry could be an enum (maybe can extend on the fly??)
57  # YahooAPI CSV defines 9 sectors
58  'Sector' : str(),
59  # Each sector has many industries (not so clear)
60  'Industry' : str(),
61  # place of cotation (Paris, NasdaqNM)
62  'StockExchange' : str(),
63  # with YQL other entries are provided
64  'other' : {},
65  # test error
66  #'error' : None,
67  # new added, in each template, not too clear, necessary for parser
68  #'source' : str()
69  }
70 
71  # maybe better to save in dictionary after running check_list_CSV, can be separate from the parser
72  # clearer for parser also.
73 
74  ## Historical Prices, the complete list (CSV) is written to file but not saved in dictionary
75  # format of date in template, string/timestamp/datetime.datetime ?
76  # C++ always timestamp, but datetime.datetime more convenient in python. If C++ read timestamp.
77  # internal C++ datetime.datetime easier, datetime if extended later to inst ?
78  _templ_hp = {
79  'first_date' : datetime.datetime.fromtimestamp(0),
80  'last_date' : datetime.datetime.fromtimestamp(0),
81  # yahoo, correct format epoch for list_csv
82  'list_csv' : None
83  #'error' : None
84  }
85 
86  ## @brief For instantaneous value. Only Boursorama parser implemented (plan to extend to Yahoo_HTML)
87  _templ_inst_value={
88  'value':0,
89  'variation':0,
90  # format Bourso store string: "2015-02-01 12:10:45"
91  # to change to one datetime like others
92  'time':0,
93  'date':0,
94  'volume':0,
95  'ouverture':0,
96  'plushaut':0,
97  'plusbas':0,
98  # if not parsed, must be available, DEFAULT/initiliazed, ERROR, CLOSED, OPEN
99  'state' : 'CLOSED',
100  # maybe error to add, nice for checking
101  # needed to add to avoid bug with ServerPortfolio, to check where exactly !
102  #'source' : None
103  }
104 
105  ## Dividend/Split same as HP, need dates certainly
106  # maybe easier to split div and split ?
107  _templ_div = {
108  'first_date' : datetime.datetime.fromtimestamp(0),
109  'last_date' : datetime.datetime.fromtimestamp(0),
110  # maybe to keep all in one list ? easier to recompute adjusted close ? [ ['dividend',date, value],['dividend',date,value],[ SPLIT, date, value(1:4)] ]
111  'list_div' : None,
112  'list_split' : None
113  #'error' : None
114  }
115 
116 # can be merged with templ_hp, but still 2 actions...
117  ## DatesIntro, first CSV date available (in CSV), integer format (extends to string? last date?)
118  _templ_intro = {
119  'intro' : int(0),
120  'intro_str' : str()
121  }
122 
123  ## @brief Template for one XML value, stored in a list (of _templ_spec_xml)
124  # Option specify the default value in case of a new file
125  # date : str(), force each entry to provide a value. None can use default. Could be assumed date is ALWAYS required (I think it is now)
126  _templ_one_value_xml = {
127  'value' : None,
128  'attrib' : { 'append' : False, 'source' : None, 'date' : None } #date should be always present
129  }
130 
131 # really necessary ? could fill only _templ_dict['Static']['template'] (not use of get_template to avoid to have a copy)
132 # like the other, just to be sure to maintain the link between the dictionary after modification
133 # if reloaded, need clean and rewrite
134 # if copied, not problem with a link to maintain,... but problem with update.
135 
136  ## @brief Static data, keys are initialised only when reading the configuration file in DictionaryStocks::init.
137  # Must care about maintaining the link, and to not create a different dictionary
138  _templ_static = {}
139 
140 # if delete _templ_static, what to do of invalid version ?
141  ## @brief Static data for InvalidStock, all fields are set to 'invalid'.
142  _templ_static_invalid = {}
143 
144 # Choice to be created in specific function _check_new_X, or use template as well ?
145 # It does not really extend dict_data, maybe it should ? It is defined as a separate Stock._dict_interactive at the moment
146 # If bound to Stock.dict_data , dict_data only with parser. valid_xml knows about XML input, deal with xml output
147 
148 # dict_data -> dict_data_parser
149 # dict_interactive :only for validation -> dict_validation better ? certainly. If only intermediate can split: Stock.Validation.dict_validation ?
150 # _templ_valid_xml (dict_xml) -> dict_data_saved, dict_data_file, dict_xml restrictive, but not needed when csv. Stock.Serialize.dict_xml ?
151 
152 # for xml dict_interactive/ dict_xml are very close
153 # for csv dict_interactive/ no equivalent, (never read and) written by c++ from dict_data (through dict_interactive)
154 # for interactive need new_value, previous_value
155 # not limited to xml, to change to _templ_valid_value
156 # used only in ValidXML
157 
158  # introduced for dist_interactive, maybe not needed in fact, all in dict_xml..?
159  # maybe to extend other class
160  # describe only one data for list_interactive, not sure how to assign the key here(without explicitly copy all...)
161  _templ_valid_xml_value = {
162  'new_value' : None,
163  'xml_value' : None,
164  'source' : None,
165  # important to save ! can store the old date ? previous date if interactive ?
166  'date' : datetime.datetime.fromtimestamp(0), #if timestamp all float/int
167  # to replace/add/append in dictionary, done partly automatic - partly by user input
168  # keep status after check. 2:accept, 1:accept or user validation, 0: reject or user validation, -1: reject/discard directly(None,empty...)
169  'repl_add' : int(0)
170  }
171 
172  # similar
173  # _templ_valid_csv =
174 
175  ## @brief Skeleton for the dictionaries of each action.
176  # Store:
177  # -a specific "template" for the parser
178  # - a config entry for the management of the sauvegarde (save : 'xml'/'csv')
179  # - optional xml attributes for the update of data in case of 'save: : 'xml'
180  #
181  # Set default values, save_xml, append, date, ...
182  # could add:
183  # - date of the last modification (avoid to rerun web queries)
184  _templ_action = {
185  # not clear it is needed here, it is independent of the other actions.
186  # It is included by default in Stock._dict_data . Could keep in DictStocks(easier for update).
187  # Make apart in Stock._dict_static in creation (need its own get_ or combine for parser query)
188  'Static' : { 'template' : _templ_static,
189  # Static, not saved the same way, set to False at the moment
190  #'config' : { 'save_xml' : False } # 'save' : 'xml'/'file'/'both' or ['xml','file'],['xml']
191  'config' : { 'save' : False } # can add option, can add config option in dictstocks as well
192  # option for csv as well, for dcsv DAY : 365 days, Inst : 15 days...
193  # error would be fine here, updated with add_dict_parser, error code from parser optional
194  #'error' : None
195  },
196 
197  # default value, source == str() : force each one_value to provide its source (Info, 2 sources)
198  # append False, for parent action default, for one_value optional, only if different
199  'Info' : { 'template' : _templ_info,
200  #'config' : {'save' : ['xml'] }, #can add option, can add in dictstoc
201  # make test, all available from stock.get_config
202  'config' : {'save' : ['xml'], 'attrib': {'append' : False, 'source' : None } }
203  #
204  #'attrib' : {'append' : False, 'source' : None }, # 'attrib_xml', 'attrib_sql'
205  },
206 
207  # append equivalent to date ! to differentiate
208  # default append True, source None (not str()), may use the default values in action attributes
209  'Fundamental' : { 'template' : _templ_fundamental,
210  #'source' : None,
211  # only internal use
212  'config' : { 'save' : ['xml'], 'attrib':{'append' : True, 'source' : None} }, #'append' : True
213  # default attributes for all values
214  #'attrib' : {'append' : True, 'source' : None}
215  # source not accounted
216  },
217 
218  'HistPrice' : { 'template' : _templ_hp,
219  'config' : {'save' : ['csv']}
220  },
221 
222  'InstValue' : { 'template' : _templ_inst_value,
223  'config' : {'save' : ['csv']} # gui_c++ , to postpone the writing to file ?
224  #'config' : {'save_xml' : False }
225  },
226  # belongs to both xml/csv, C++ needs fast access for one specific action only(adjust close) not saved at the moment.
227  # convenient if stored in both format.
228  'DivSplit' : { 'template' : _templ_div,
229  'config' : { 'save' : ['xml','csv'] } # both or a list ['xml','csv','sql'] easier to extend, 'gui_c++' ?
230  #'attrib' : {'append' : True, 'date' : True }
231  },
232  # used to retrieve all previous data in yahoo, not (yet) saved. need to save ? or just used transiently by gui/or update python
233  'DatesIntro' : { 'template' : _templ_intro,
234  'config' : {'save' : False}
235  }
236 
237 # for sure unrelated
238  # ###### to move, unrelated to Action to save, Validation class, still can use templates, more specific.
239  #
240  # only used by dict_interactive, which is now used if interactive or save is present, default save=True is present
241  # each action could import ValidXML/ValidCSV template
242  ## unrelated to EAction, to valid new data in XML
243 # 'ValidXML' : { 'template' : _templ_valid_xml_value, #no really need template
244 # 'config' : { 'save' : False } #query dict_data if 'xml' _templ_valid_xml, 'csv' _templ_valid_csv
245 # }
246  }
247 
248  ## @brief Define a generic template to be included by all parsers, for each stock.
249  # symbol allows the parser to associate the results to each stock (case of multiple stocks query)\n
250  # action_templ stores templates of the actions performed\n
251  # @todo errors may be common or included by each, only for transient storage, destroyed after parsing is done
252  # see get_template_parser()
253  _templ_parser = {
254  ## @brief If parsing multiple stocks, associate the symbol
255  'symbol' : str(),
256  ## @brief One of action template: _templ_inst_value, _templ_hp.
257  'action_templ' : {}
258 
259  # error could be common as well or used to report all errors
260  # indicates the action, e.g. 'HistPrice' or a list
261  # here not the best place, used only in dict_return_data,
262  # possible check in UpdateStock before clean parser, in add_dict_data is best
263 
264  #'global_error' : None , not global, one templ_dict_data for each Stock symbol
265  # global_error can only be UpdateStock, local in Stock (stock_error) can check in add_dict_data
266 
267  # source ?? best place, this template only used by parser, need variable to store in dict_data also
268  # done actually, send with add_dict_parser
269  #'source' : str()
270  }
271 
272  #_templ_valid_xml = {
273  #'template' : _templ_valid_xml_value
274  #}
275 
276  ## @brief Reset the state of templates to the original.
277  # @warning Should not be used in the application, only for unit_test at this point, but may need later for reload.
278  # to add dynamically in unit-test ?
279  @classmethod
280  def _reset(cls):
281  cls._logger.debug("_reset static template")
282  #print "id(self._templ_static)", id(cls._templ_static)
283  #print "id(self._templ_dict['Static']['template'] ", id(cls._templ_dict['Static']['template'])
284  assert id(cls._templ_static) == id(cls._templ_action['Static']['template']), \
285  "Id templ_static differ before clear"
286  # maintain links
287  cls._templ_static.clear()
288  cls._templ_static_invalid.clear()
289  #print "id(self._templ_static)", id(cls._templ_static)
290  #print "id(self._templ_dict['Static']['template'] ", id(cls._templ_dict['Static']['template'])
291  assert id(cls._templ_static) == id(cls._templ_action['Static']['template']), \
292  "Id templ_static differ after clear"
293 
294 # get_template_stock: with access to config, to save or not, which format, pre/post-process available
295 # no need of 'config': 'attrib'
296 
297  ## @brief Return a copy of an action template for stocks, include 'template' and 'config', delete 'attrib' entry
298  # @param action EAction or EAction.name
299  @classmethod
300  def get_template_stock( cls, action, for_xml=False ):
301  cls._logger.debug("Entry get_template, action: %s" % action)
302  # correctly handle exceptions in Utils
303  e_action = Utils.stringToEAction( action )
304 
305  # check argument, needed ? not anymore ! could provide option with the template
306  if e_action.name in cls._templ_action:
307  tmp_dict = copy.deepcopy( cls._templ_action[e_action.name] )
308  # 'attrib' entry not needed for stock, but needed for xml
309  # new added
310  if not for_xml:
311  if 'attrib' in tmp_dict['config']:
312  del tmp_dict['config']['attrib']
313  # else send back everything if called by get_template_xml
314  return tmp_dict
315  # original version
316  # return copy.deepcopy( cls._templ_action[e_action.name] )
317 
318  # error if wrong action
319  raise PortfolioError("e_action %s has no template in StockTemplates")
320 
321  ## @brief Return the template for parser to be included by all parsers.
322  # Include the symbol of the stock (used for multiple stocks query),\n
323  # and the template specific an action.\n
324  # In multiple stock query, these template are stored in a list (AbstractParser.list_return_data)\n
325  # @note Must be included before any other templates (usually in the parse() function of the parser)
326  @classmethod
327  def get_template_parser( cls, action = None ):
328  # Possible to test if already existing, for not overwriting
329  # template parser includes 'symbol'
330  tmp_dict = copy.deepcopy( cls._templ_parser)
331 
332  # a priori, always called with one action
333  assert action is not None,"get_template_parser called with action None"
334  if action is None:
335  return tmp_dict
336 
337  e_action = Utils.stringToEAction( action )
338  # could almost avoid string and keep enum for the rest of the parsing
339  tmp_dict['action_templ'][e_action.name] = copy.deepcopy(cls._templ_action[ e_action.name ]['template'])
340  return tmp_dict
341 
342 # xml need access to attribute (in config) and accept list, not only one value
343 # new added, call by Stock_fill_dict_xml(read_xml) , rename ext_xml, extended xml ?
344 # and create_default_template_xml when creating a new empty XML file
345 
346  ## @brief Specific for xml dictionary, replace each value by a list.
347  # @return dictionary template action for xml data
348  @classmethod
349  def get_template_xml(cls, action):
350  cls._logger.debug("Entry get_template_xml %s" % action)
351  # get 'template' and 'config', config:'save' not needed a priori
352  # miss attrib, has been deleted from templ_action, added again
353  # new added option for_xml, keep originam (with attrib)
354  tmp_xml = cls.get_template_stock( action, for_xml=True )
355  # no need of config, only attributes
356  #tmp_xml['attrib'] = tmp_xml['config']['attrib']
357  del tmp_xml['config']
358 
359  cls._logger.debug("template_xml: %s" % tmp_xml)
360 
361  # replace values by an empty list on the fly
362  for name in tmp_xml['template'].keys():
363  print "name", name
364  # always discard other,error
365  # if name in StTmpl().discard_entries
366  if (name == "other") | (name == "error" ):
367  continue
368  tmp_xml['template'][name] = list()
369  cls._logger.debug("return template_xml %s" % tmp_xml)
370  return tmp_xml
371 
372  ## @brief Return a template for each xml value, include 'value' and 'atrib' for each one
373  # Or/And, with action, name can set default append/date
374  @classmethod
376  return copy.deepcopy(cls._templ_one_value_xml)
377 
378  ## @brief Retrun a template for the validation template, include 'new_value','xml_value','repl_add','date'
379  @classmethod
381  return copy.deepcopy(cls._templ_valid_xml_value)
382 
383  ## @brief Set the internal template for the static data, also for invalid stock.
384  #
385  # Set cls._templ_static with keys read from dictstocks.txt\n
386  # It should be executed only once when reading the configuration file.\n
387  #
388  # @post cls._tmpl_dict['Static'] should be link to the same object (to allow to use get_template('Static') safely)
389  #
390  # @param list_key keys to insert in the template for static data. Set default as empty string.
391  @classmethod
392  def set_tmpl_static(cls, list_key ):
393 
394  if any(cls._templ_static):
395  cls._logger.warning("_tmpl_static already loaded, do nothing, should not happen !")
396  return
397 
398  # here create a new dictionary, 2 different id's, before working ?? not really !!
399  #cls._templ_static = {}
400  #cls._templ_static_invalid = {}
401  # was a bad idea
402  #cls._templ_dict['Static']['template'] = {}
403 
404  # ok, cls._templ_dict['Static']['template'] updated at the same time
405  for item in list_key:
406  cls._templ_static[item] = str()
407  cls._templ_static_invalid[item] = 'invalid'
408 
409  #print "set _tmpl_static ",cls._templ_static
410  #print "cls._tmpl_dict['Static'] ", cls._templ_dict['Static']
411 
412  assert id(cls._templ_static) == id(cls._templ_action['Static']['template']), \
413  "templ_static not pointing to the same object"
414 
415  ## @brief Return the invalid static template.
416  # Called at the creation of InvalidStock
417  @classmethod
419  return cls._templ_static_invalid
420 
421 # to delete saved_as replace this function
422 # now return 'xml'/'csv', to move to Stock, can modify the XML manually
423  ## @brief Query to know if this template must be saved in XML file.
424  #
425  # @return bool
426  @classmethod
427  def to_save_xml(cls, action):
428  cls._logger.debug("Entry to_save_xml")
429  cls._logger.debug("to save action %s, %s" % (action, cls._templ_action[ action]) )
430  return cls._templ_action[ action ]['config']['save']
431 
432  ## @brief Query the format in which the data must be saved.
433  # @param action
434  # @param specific
435  @classmethod
436  def saved_as(cls, action, specific=None):
437  cls._logger.debug("Entry saved_as")
438 
439  if specific == None:
440  return cls._templ_action[ action ]['config']['save']
441  # if wrong entry return False
442  else:
443  # case of Static 'save': False
444  #if specific in cls._templ_action[ action ]['config']['save'] == False:
445  if cls._templ_action[ action ]['config']['save'] == False:
446  return False
447  if specific in cls._templ_action[ action ]['config']['save']:
448  return True
449  return False
450 
451  self.logger.error("saved_as Should not arrive here")
452  assert 1==1, "Should not arrive here"
453 
454  ## @brief Create a default xml dictionary, called when creating a new XML file.
455  # Assign same templates as stock, with value replaced by a list (one_value_xml)\n
456  # Apply only to action with a save option 'xml' in 'config'
457  # @post All one_values_xml template have the default attributes of the action
458  @classmethod
460  cls._logger.debug("Entry create_default_template_xml")
461  default_xml = {}
462  for action in cls._templ_action.keys():
463  print "call to_save_xml action ", action
464  #if cls.to_save_xml(action): #to save (action, 'xml'/'csv'/'sql'
465  if cls.saved_as(action,'xml'):
466 
467  # strange logic here, get_template_xml already replace value by list
468  # here copy also config, not needed
469  default_xml[action] = cls.get_template_xml(action)
470  print "get_template_xml ", default_xml
471  # in check new xml, access to xml_data['attrib']['source']
472 
473  # replace all values in template by a default one_value_xml, replace value by a list
474  for key_value in default_xml[action]['template'].keys():
475  # skip entry other and error
476  if (key_value == 'other') | (key_value == 'error'):
477  continue
478 
479  # append 'value' / 'attrib' to each key_value
480  #print default_xml[action]['template'][key_value]
481  default_xml[action]['template'][key_value].append(cls.get_template_one_value_xml())
482 
483  # here best way to override default attr in one_value_xml ? Yes, this way could read data from _templ_action : attrib
484  # attrib has been deleted from stock, add default in one_value_xml
485  #for key_attr_action in default_xml[action]['template'][key_value][0]['attrib']:
486  # print "key_attr ", key_attr_action, default_xml[action]['attrib'][key_attr_action]
487  # default_xml[action]['template'][key_value][0]['attrib'][key_attr_action] = default_xml[action]['attrib'][key_attr_action]
488  # seems correct now
489  print key_value, default_xml[action]['template'][key_value][0]['attrib']
490  # default_xml:
491  # {'Info': {'config': {'save': ['xml']}, 'template': {'Sector': [{'attrib': {'date': None, 'source': None, 'append': False}, 'value': None}], 'StockExchange': [], 'Industry': [], 'other': {}, 'Name': []}}}
492 
493  cls._logger.debug("default xml %s" % default_xml)
494  return default_xml
495 
496 # here or in Stock, static method, maybe more logical with the template
497  def make_header(self, action_name):
498 
499  if (action_name == 'InstValue'):
500  return "#name value variation time date volume ouverture +haut +bas state\n"
501  elif (action_name == 'HistPrice'):
502  return "#name first_date last_date\n"
503  elif (action_name == 'Info'):
504  return "# symbol name sector industry\n"
505  elif (action_name == 'DatesIntro'):
506  return "# symbol first available data in Yahoo\n"
507  elif (action_name == 'Fundamental'):
508  return "# symbol ....\n"
509  ## Div Split
510 
511  else :
512  return "# header to write in StokTemplates for action %s\n" % action_name
513 
514 ## @brief Initialise a global variable when the module is loaded.
515 # if executed in main, logger can be loaded, class can be derived
516 # or executed in DictStocks, used by Parsers/Stocks...StockXML
517 StTmpl = StockTemplates()
518 
519 # ################ For test, certainly to delete
520 if __name__ == "__main__":
521 
522  print "Main StockTemplates"
523 
524  print 'load default'
525  d_static = StTmpl.get_dict_static()
526  print "d_static ", d_static
527 
528  d_static = StTmpl.get_dict_static( "CAC40" )
529  print "d_static", d_static
530 
Base class of the custom exceptions.
def set_tmpl_static
Set the internal template for the static data, also for invalid stock.
def to_save_xml
Query to know if this template must be saved in XML file.
def get_template_xml
Specific for xml dictionary, replace each value by a list.
def saved_as
Query the format in which the data must be saved.
def get_template_stock
Return a copy of an action template for stocks, include 'template' and 'config', delete 'attrib' entr...
def create_default_template_xml
Create a default xml dictionary, called when creating a new XML file.
Define template dictionaries to describe the data structure.
def get_tmpl_static_invalid
Return the invalid static template.
Define custom and specific exceptions for the complete package.
def _reset
Reset the state of templates to the original.
def get_template_one_value_xml
Return a template for each xml value, include 'value' and 'atrib' for each one Or/And, with action, name can set default append/date.
def get_template_parser
Return the template for parser to be included by all parsers.
def get_template_valid_xml_value
Retrun a template for the validation template, include 'new_value','xml_value','repl_add','date'.