Ver código fonte

fix bubbule chart failed to load

mchan927@home 8 anos atrás
pai
commit
4fa82696a4

+ 0 - 0
__init__.py


+ 1 - 1
config/app.cfg

@@ -100,7 +100,7 @@ option.underlying.tick_size = 200
 
 # refer to this link for rate and div
 # https://www.hkex.com.hk/eng/sorc/tools/calculator_index_option.aspx
-option.greeks.recal = "{'use_last_if_no_bidask': True, 'rate':0.0009, 'div':0.328, 'vol':0.2}"
+option.greeks.recal = "{'use_last_if_no_bidask': True, 'rate':0.0009, 'div':0.0328, 'vol':0.2}"
 option.chain_range = 0.08
 option.bid_ask_spread_tolerance = 0.90
 

+ 522 - 0
finopt/opt_serve (copy).py

@@ -0,0 +1,522 @@
+# -*- coding: utf-8 -*-
+import sys, traceback
+import logging
+import os
+import ast
+import urllib, urllib2, cookielib
+import datetime
+import re
+import json
+import cherrypy
+import hashlib
+import uuid
+import redis
+import json
+import optcal
+import ConfigParser
+import portfolio
+from comms.alert_bot import AlertHelper
+
+
+class QServer(object):
+    
+    config = None
+    r_conn = None
+    
+    
+    def __init__(self, r_conn, config):
+        super(QServer, self).__init__()
+        QServer.r_conn = r_conn
+        QServer.config = config
+        #print QServer.r_conn
+        
+
+    
+    
+    @cherrypy.expose
+    def index(self):
+        
+        s_line = 'welcome!'
+        #r_host = cherrypy.request.app.config['redis']['redis.server']
+        #r_port = cherrypy.request.app.config['redis']['redis.port']
+        #r_db = cherrypy.request.app.config['redis']['redis.db']
+        #r_sleep = cherrypy.request.app.config['redis']['redis.sleep']
+    
+        #rs = redis.Redis(r_host, r_port, r_db)
+        rs = QServer.r_conn
+        s_line = rs.info()
+        html =''
+        for k, v in cherrypy.request.app.config.iteritems():
+            html = html + '<dt>%s</dt><dd>%s</dd>' % (k, v)
+       
+        impl_link = "<a href='./opt_implv'>options implied vol curves</a>"
+        pos_link = "<a href=./ws_position_chart>Positions</a>" 
+        bubble_link = "<a href=./port_bubble_chart>Risk Distributions</a>"
+        return """<html><body><li>%s</li><li>%s</li><li>%s</li><br><dl>%s</dl></br>%s</body></html>""" % (bubble_link, impl_link, pos_link, html, s_line)
+ 
+ 
+    @cherrypy.expose
+    def opt_chains(self): 
+        r_host = cherrypy.request.app.config['redis']['redis.server']
+        r_port = cherrypy.request.app.config['redis']['redis.port']
+        r_db = cherrypy.request.app.config['redis']['redis.db']
+        r_sleep = cherrypy.request.app.config['redis']['redis.sleep']
+        opt_chains = cherrypy.request.app.config['redis']['redis.datastore.key.option_chains']
+        rs = redis.Redis(r_host, r_port, r_db)        
+        opt_chain_tmpl = '%s%s/opt-chains-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+ 
+        f = open(opt_chain_tmpl)
+        html_tmpl = f.read()
+
+        s_dict = rs.get(opt_chains)
+        matrix = json.loads(s_dict)
+        
+        strike = matrix.keys()[0]
+        print matrix
+        num_months = len(matrix[strike])
+        s = '["strike",'
+#         for i in range(num_months):          
+#             s = s + "'P-%s', 'C-%s', " % (matrix[strike].keys()[i], matrix[strike].keys()[i])
+        s = s + '],'
+        for month, strikes in sorted(matrix.iteritems()):
+            l = ''
+            for strike, cp in sorted(strikes.iteritems()):
+                l = l + '[%s,%s,%s,%s,%s],' % (strike, cp['P']['0'], cp['P']['1'], cp['P']['2'], cp['P']['3'])
+                
+                                
+            s = s + l + '],\n'
+        
+        print s
+        html_tmpl = html_tmpl.replace('{{{data}}}', s)
+        
+        return html_tmpl 
+ 
+    @cherrypy.expose
+    def opt_implv(self):
+        #r_host = cherrypy.request.app.config['redis']['redis.server']
+        #r_port = cherrypy.request.app.config['redis']['redis.port']
+        #r_db = cherrypy.request.app.config['redis']['redis.db']
+        #r_sleep = cherrypy.request.app.config['redis']['redis.sleep']
+        
+        opt_implv = cherrypy.request.app.config['redis']['redis.datastore.key.option_implv']
+        #rs = redis.Redis(r_host, r_port, r_db)
+        
+        rs = QServer.r_conn
+                
+        opt_implv_tmpl = '%s%s/opt-chains-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(opt_implv_tmpl)
+        html_tmpl = f.read()
+
+        s_dict = rs.get(opt_implv)
+        
+        # sample value
+        # {u'25400': {u'20150828': {u'P': [u'null', 1400.0], u'C': [0.21410911336791702, 29.0]}, u'20150929': {u'P': [u'null', u'null'], u'C': [0.1934532406732742, 170.0]}}, ...
+        matrix = json.loads(s_dict)
+        
+
+        
+        strike = matrix.keys()[0]
+        print matrix
+        num_months = len(matrix[strike])
+        s = '["strike",'
+        
+        sorted_months = sorted(matrix[strike].keys())
+        for i in range(num_months):          
+            s = s + "'P-%s', 'C-%s', " % (sorted_months[i], sorted_months[i])
+        s = s + '],'
+        for strike, items in sorted(matrix.iteritems()):
+            s = s + '[%s,' % str(strike)
+            l = ''
+            for month, cp in sorted(items.iteritems()):
+                print month, cp
+                l = l + ''.join('%s,%s,' % (cp['P'][0], cp['C'][0]))
+                                
+            s = s + l + '],\n'
+        
+        html_tmpl = html_tmpl.replace('{{{data}}}', s)
+
+
+
+        s = '["strike",'
+        for i in range(num_months):          
+            s = s + "'P-%s', 'C-%s', " % (sorted_months[i], sorted_months[i])
+        s = s + '],'
+        for strike, items in sorted(matrix.iteritems()):
+            s = s + '[%s,' % str(strike)
+            l = ''
+            for month, cp in sorted(items.iteritems()):
+                l = l + ''.join('%s,%s,' % (cp['P'][1], cp['C'][1]))
+                                
+            s = s + l + '],\n'
+        
+        print 'sorted months' + sorted_months[0]
+        html_tmpl = html_tmpl.replace('{{{dataPremium}}}', s)
+        html_tmpl = html_tmpl.replace('{{{thisContractMonth}}}', sorted_months[0])
+        
+
+        
+        return html_tmpl
+
+
+    
+
+    @cherrypy.expose
+    def opt_implv_ex(self):
+        #r_host = cherrypy.request.app.config['redis']['redis.server']
+        #r_port = cherrypy.request.app.config['redis']['redis.port']
+        #r_db = cherrypy.request.app.config['redis']['redis.db']
+        #r_sleep = cherrypy.request.app.config['redis']['redis.sleep']
+        
+        opt_implv = cherrypy.request.app.config['redis']['redis.datastore.key.option_implv']
+        #rs = redis.Redis(r_host, r_port, r_db)
+        
+        rs = QServer.r_conn
+                
+        opt_implv_tmpl = '%s%s/opt-chains-ex-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(opt_implv_tmpl)
+        html_tmpl = f.read()
+
+        s_dict = rs.get(opt_implv)
+        
+        # sample value
+        # {u'25400': {u'20150828': {u'P': [u'null', 1400.0], u'C': [0.21410911336791702, 29.0]}, u'20150929': {u'P': [u'null', u'null'], u'C': [0.1934532406732742, 170.0]}}, ...
+        matrix = json.loads(s_dict)
+        
+
+        
+        strike = matrix.keys()[0]
+        print matrix
+        num_months = len(matrix[strike])
+        s = '["strike",'
+        
+        sorted_months = sorted(matrix[strike].keys())
+        for i in range(num_months):          
+            s = s + "'P-%s', 'C-%s', " % (sorted_months[i], sorted_months[i])
+        s = s + '],'
+        for strike, items in sorted(matrix.iteritems()):
+            s = s + '[%s,' % str(strike)
+            l = ''
+            for month, cp in sorted(items.iteritems()):
+                print month, cp
+                l = l + ''.join('%s,%s,' % (cp['P'][0], cp['C'][0]))
+                                
+            s = s + l + '],\n'
+        
+        html_tmpl = html_tmpl.replace('{{{data}}}', s)
+
+
+
+        s = '[{label:"strikes",type:"number"},'
+        for i in range(num_months):          
+            s = s + "{label: 'P-%s', type:'number'},\
+                {label: 'Pb-%s', id:'i0', type:'number', role:'interval'},\
+                {label: 'Pa-%s', id:'i0', type:'number', role:'interval'},\
+                {label: 'C-%s', type:'number', },\
+                {label: 'Cb-%s', id:'i0', type:'number', role:'interval'},\
+                {label: 'Ca-%s', id:'i0',type:'number', role:'interval'},"\
+                 % (sorted_months[i], sorted_months[i],sorted_months[i], sorted_months[i],sorted_months[i], sorted_months[i])
+        s = s + '],'
+        for strike, items in sorted(matrix.iteritems()):
+            s = s + '[%s,' % str(strike)
+            l = ''
+            for month, cp in sorted(items.iteritems()):
+                l = l + ''.join('%s,%s,%s,%s,%s,%s,' % (cp['P'][1], cp['P'][2],cp['P'][3],cp['C'][1],cp['C'][2],cp['C'][3]))
+                                
+            s = s + l + '],\n'
+        
+        
+        html_tmpl = html_tmpl.replace('{{{dataPremium}}}', s)
+        
+        html_tmpl = html_tmpl.replace('{{{thisContractMonth}}}', sorted_months[0])
+        
+
+        
+        return html_tmpl
+
+
+
+
+
+
+
+
+    @cherrypy.expose
+    def ws_cal_implvol(self, s, x, cp, ed, xd, r, d, v, p, out='iv'):
+        try:
+        #spot, strike, callput, evaldate, exdate, rate, div, vol, premium
+            rs = optcal.cal_implvol(float(s), float(x), cp, ed, xd, float(r), float(d), float(v), float(p))
+            return str(rs['imvol'])
+        except:
+            return
+
+    @cherrypy.expose
+    def ws_cal_option(self, s, x, cp, ed, xd, r, d, v, out='npv'):
+        #spot, strike, callput, evaldate, exdate, rate, div, vol
+        
+        keys = ['npv', 'delta', 'gamma', 'theta', 'vega'];
+        
+        try:
+            rs = optcal.cal_option(float(s), float(x), cp, ed, xd, float(r), float(d), float(v))
+            
+            if out == 'csv':
+                logging.debug('ws_cal_option: ' + ','.join(str(rs[s]) for s in keys))
+                return ','.join(str(rs[s]) for s in keys)
+            elif out == 'json':
+                return json.dumps(rs)
+            else:
+                return str(rs[out])  
+        except:
+            #exc_type, exc_value, exc_traceback = sys.exc_info()
+            return traceback.format_exc()
+            
+    @cherrypy.expose
+    def ws_get_hist_implv(self, dataAt):
+    # given a date string YYMMDDHHMM, this routine returns an array of
+    # implied vols arranged in a format like the below
+    #      [["strike",'P-20150828', 'C-20150828', 'P-20150929', 'C-20150929', ],
+    #                      [21800,0.29153118077,null,0.241032122988,null,],
+    #                      [22000,0.284002011642,null,0.238145680311,null,],
+    #                      [22200,0.270501965746,null,0.222647164832,null,]]   
+        pass
+    
+    
+    @cherrypy.expose
+    def ws_market_data(self, r_ckey, fid):
+        if str(fid).upper() == 'ALL':
+            return QServer.r_conn.get(r_ckey)
+        val = QServer.r_conn.get(r_ckey)
+        if val is None:
+            return 'invalid request. Check your input again!'
+        dict = json.loads(QServer.r_conn.get(r_ckey))
+        return str(dict[fid]) 
+ 
+    @cherrypy.expose
+    def ws_position_chart(self):
+        p = portfolio.PortfolioManager(config)
+        p.retrieve_position()
+        opt_pos_chart_tmpl = '%s%s/opt-pos-chart-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(opt_pos_chart_tmpl)
+        html_tmpl = f.read()
+        html_tmpl = html_tmpl.replace('{{{dataPCpos}}}', p.get_grouped_options_str_array())
+        
+        html_tmpl = html_tmpl.replace('{{{dataTablePos}}}', p.get_tbl_pos_csv())
+        
+        html_tmpl = html_tmpl.replace('{{{option_months}}}', ''.join(('%s, ' % m) for m in p.get_traded_months()))
+        v = p.group_pos_by_right()
+        html_tmpl = html_tmpl.replace('{{{PRvsCR}}}}', '%0.2f : %0.2f' % (v[0][1], v[1][1]))
+        
+        #print p.get_portfolio_summary()
+        #html_tmpl = html_tmpl.replace('{{{pos_summary}}}', ''.join('<li>%s:   %s</li>' % (x[0],x[1]) for x in p.get_portfolio_summary() ))
+        #print '\n'.join('%s:\t\t%s' % (k,v) for k,v in sorted(json.loads(DataMap.rs.get(port_key)).iteritems()))
+        
+        
+        return html_tmpl
+ 
+    @cherrypy.expose
+    def ws_position_summary(self):
+        p = portfolio.PortfolioManager(config)
+        keys = [("delta_1percent","number"),("delta_all","number"),("delta_c","number"),("delta_p","number"),\
+                ("theta_1percent","number"),("theta_all","number"),("theta_c","number"),("theta_p","number"),\
+                ("unreal_pl","number"),("last_updated","string"),("status","string")]
+        d = p.get_portfolio_summary()
+        
+        dict= {}
+        dict['cols'] = [{'label': x[0], 'type': x[1]} for x in keys]
+        dict['rows'] = [{'v': d[x[0]]} for x in keys]
+        print json.dumps(dict)
+        return json.dumps(dict)
+
+    @cherrypy.expose
+    def ws_recal_pos(self, force_refresh=False):
+        p = portfolio.PortfolioManager(config)
+        if force_refresh:
+            p.retrieve_position()
+        l_gmap = p.recal_port()
+        print l_gmap
+        return json.dumps(l_gmap)        
+        
+
+
+    @cherrypy.expose
+    def ws_pos_csv(self):
+        p = portfolio.PortfolioManager(config)
+        p.retrieve_position()
+        s = "%s" % p.get_tbl_pos_csv_old()
+        #s = "%s" % p.get_tbl_pos_csv()
+        #print s
+        s = s.replace(',[', '').replace(']', '<br>')
+        
+        print s
+        return s[1:len(s)-3]
+    
+    @cherrypy.expose
+    def getSHquote(self, qs):
+
+#http://api.money.126.net/data/feed/0000001,1399001,1399300        
+#_ntes_quote_callback({"0000001":{"code": "0000001", "percent": 0.015468, "askvol1": 0, "askvol3": 0, "askvol2": 0, "askvol5": 0,
+# "askvol4": 0, "price": 2972.57, "open": 2978.03, "bid5": 0, "bid4": 0, "bid3": 0, "bid2": 0, "bid1": 0, "high": 3014.41, "low": 2929.0, 
+#"updown": 45.28, "type": "SH", "bidvol1": 0, "status": 0, "bidvol3": 0, "bidvol2": 0, "symbol": "000001", "update": "2015/08/27 12:43:00", 
+#"bidvol5": 0, "bidvol4": 0, "volume": 19800251400, "ask5": 0, "ask4": 0, "ask1": 0, "name": "\u4e0a\u8bc1\u6307\u6570", "ask3": 0, "ask2": 0,
+# "arrow": "\u2191", "time": "2015/08/27 12:42:57", "yestclose": 2927.29, "turnover": 204156106776} });        
+        url = 'http://api.money.126.net/data/feed/%s?callback=ne3587367b7387dc' % qs
+        print url
+        pg = urllib2.urlopen(url.encode('utf-8'))    
+        s = pg.read().replace('ne3587367b7387dc(', '')
+        
+        
+        s = s[:len(s)-2]
+        print s
+        return s
+    
+    
+
+    
+    
+    @cherrypy.expose
+    def ws_port_summary(self):    
+        
+        rs = QServer.r_conn
+        ps_key = cherrypy.request.app.config['redis']['redis.datastore.key.port_summary']
+        s_portsum = rs.get(ps_key)
+        #dict = json.loads(s_portsum)
+        return s_portsum
+    
+
+    @cherrypy.expose
+    def ws_port_items(self):    
+        
+        rs = QServer.r_conn
+        key = cherrypy.request.app.config['redis']['redis.datastore.key.port_items']
+        s_portitems = rs.get(key)
+        #dict = json.loads(s_portsum)
+        return s_portitems
+    
+    
+    @cherrypy.expose
+    def port_bubble_chart(self):
+    
+        s_data = self.ws_bubble_data()
+
+        bubble_chart_tmpl = '%s%s/bubble-port.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(bubble_chart_tmpl)
+        html_tmpl = f.read()
+        html_tmpl = html_tmpl.replace('{{{bubble_data}}}', s_data)
+        
+        contract_month = eval(cherrypy.request.app.config['market']['option.underlying.month_price'])[0][0]
+        html_tmpl = html_tmpl.replace('{{{FUT_CONTRACT}}}', 'HSI-%s-FUT-' % (contract_month))
+        
+        
+        
+        s_acctitems, last_updated, account_no = self.ws_acct_data()
+        print s_acctitems, last_updated, account_no
+        html_tmpl = html_tmpl.replace('{{{barAcct}}}', s_acctitems)
+        html_tmpl = html_tmpl.replace('{{{account_no}}}', account_no)
+        html_tmpl = html_tmpl.replace('{{{last_updated}}}', last_updated)
+        
+        
+        
+        return html_tmpl
+       
+    @cherrypy.expose
+    def ws_bubble_data(self):
+        # Tick Value      Description
+        # 5001            impl vol
+        # 5002            delta
+        # 5003            gamma
+        # 5004            theta
+        # 5005            vega
+        # 5006            premium        
+        # 6001            avgCost
+        # 6002            pos
+        # 6003            totCost
+        # 6004            avgPx
+        # 6005            pos delta
+        # 6006            pos theta
+        # 6007            multiplier
+        # 6009            curr_port_value
+        # 6008            unreal_pl
+        # 6020            pos value impact +1% vol change
+        # 6021            pos value impact -1% vol change
+        s_portitems = self.ws_port_items() 
+        
+        litems = json.loads(s_portitems)
+        
+        # only interested in unrealized items, pos != 0 
+        ldict = filter(lambda x: x['6002'] <> 0, litems)
+        
+        lcontract = map(lambda x: x['contract'], ldict)
+        lpos_delta = map(lambda x: x['6005'], ldict)
+        lstrike = map(lambda x: x['contract'].split('-')[2], ldict)
+        ltheta = map(lambda x:  x['6006'], ldict)
+        lupl = map(lambda x: x['6008'], ldict)
+        
+        
+        
+        
+        colnames = "[['contract', 'strike', 'unreal PL', 'theta', 'delta'],"
+        print '----------------------'
+        s_data = colnames + ''.join('["%s",%s,%s,%s,%s],' % (lcontract[i], lstrike[i], lupl[i], ltheta[i], abs(lpos_delta[i])) for i in range(len(lcontract)))+ ']'
+      
+        return s_data
+    
+    
+            
+    
+    
+    @cherrypy.expose
+    def ws_acct_data(self):
+        rs = QServer.r_conn
+        key = cherrypy.request.app.config['redis']['redis.datastore.key.acct_summary']
+        s_acctitems = rs.get(key)
+        dict = json.loads(s_acctitems)
+        colnames = "[['Category', 'Value', { role: 'style' } ],"
+        unwanted_cols = ['DayTradesRemaining','last_updated', 'AccountType']
+        s_data = colnames + ''.join('["%s", %s, "%s"],' % (k, '%s'%(v[0]), '#3366CC' if float(v[0]) > 500000 else '#DC3912') if k not in unwanted_cols else '' for k, v in dict.iteritems()   )+ ']'
+      
+        return (s_data, dict['last_updated'], dict['AccountType'][2])
+    
+    
+    
+    @cherrypy.expose
+    def ws_msg_bot(self, msg):
+        a = AlertHelper(self.config)
+        a.post_msg(msg)  
+        
+        
+            
+         
+if __name__ == '__main__':
+            
+#     logging.basicConfig(filename = "log/opt.log", filemode = 'a', 
+#                         level=logging.DEBUG,
+#                         format='%(asctime)s %(levelname)-8s %(message)s')      
+#  
+# 
+#     config = ConfigParser.ConfigParser()
+#     config.read("config/app.cfg")
+#     host = config.get("redis", "redis.server").strip('"').strip("'")
+#     port = config.get("redis", "redis.port")
+#     db = config.get("redis", "redis.db")    
+#     r_conn = redis.Redis(host,port,db)
+#     cherrypy.quickstart(QServer(r_conn, config), '/', "config/app.cfg")
+   
+    if len(sys.argv) != 2:
+        print("Usage: %s <config file>" % sys.argv[0])
+        exit(-1)    
+
+    cfg_path= sys.argv[1:]    
+    config = ConfigParser.ConfigParser()
+    if len(config.read(cfg_path)) == 0:      
+        raise ValueError, "Failed to open config file" 
+    
+    logconfig = eval(config.get("opt_serve", "opt_serve.logconfig").strip('"').strip("'"))
+    logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'
+    logging.basicConfig(**logconfig)            
+    host = config.get("redis", "redis.server").strip('"').strip("'")
+    port = config.get("redis", "redis.port")
+    db = config.get("redis", "redis.db")    
+    r_conn = redis.Redis(host,port,db)
+    
+    cherrypy.quickstart(QServer(r_conn, config), '/', cfg_path[0])
+    
+   

+ 4 - 1
finopt/opt_serve.py

@@ -642,7 +642,10 @@ class QServer(object):
         s_portitems = self.ws_port_items() 
         
         litems = json.loads(s_portitems)
-        
+	
+	#2018 fix - get rid of 'FUT' contracts 
+	litems = filter(lambda x: 'FUT' not in x['contract'], litems)        
+
         # only interested in unrealized items, pos != 0 
         ldict = filter(lambda x: x['6002'] <> 0, litems)
         

+ 699 - 0
finopt/opt_serve.py.20160509

@@ -0,0 +1,699 @@
+# -*- coding: utf-8 -*-
+import sys, traceback
+import logging
+import os
+import ast
+import urllib, urllib2, cookielib
+import datetime, time
+import re
+import json
+import cherrypy
+import hashlib
+import uuid
+import redis
+import json
+import optcal
+import ConfigParser
+import portfolio
+from comms.alert_bot import AlertHelper
+from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
+from ws4py.websocket import WebSocket
+from ws4py.websocket import EchoWebSocket
+from sets import Set
+import thread
+
+
+class QServer(object):
+    
+    config = None
+    r_conn = None
+    
+    
+    def __init__(self, r_conn, config):
+        super(QServer, self).__init__()
+        QServer.r_conn = r_conn
+        QServer.config = config
+        #print QServer.r_conn
+        
+
+    
+    
+    @cherrypy.expose
+    def index(self):
+        
+        #s_line = 'welcome!'
+        #r_host = cherrypy.request.app.config['redis']['redis.server']
+        #r_port = cherrypy.request.app.config['redis']['redis.port']
+        #r_db = cherrypy.request.app.config['redis']['redis.db']
+        #r_sleep = cherrypy.request.app.config['redis']['redis.sleep']
+    
+        #rs = redis.Redis(r_host, r_port, r_db)
+        rs = QServer.r_conn
+        s_line = rs.info()
+	
+        html =''
+        for k, v in cherrypy.request.app.config.iteritems():
+            html = html + '<dt>%s</dt><dd>%s</dd>' % (k, v)
+       
+        impl_link = "<a href=./opt_implv><img src='public/chart.png' width='42' height=42'/>options implied vol curves</a>"
+        pos_link = "<a href=./ws_position_chart><img src='public/moneyup.png' width='42' height='42'/>Positions</a>" 
+        stackpos_link = "<a href=./ws_position_chart_ex><img src='public/scale.png' width='42' height='42' />Positions (Stacked View)</a>"
+        bubble_link = "<a href=./port_bubble_chart><img src='public/Market-Risk-Icon.png' width='42' height='42' />Risk Distributions</a>"
+
+	html = ''
+	s_line = ''
+
+        return """<html><body><li>%s</li><li>%s</li><li>%s</li><li>%s</li><br><dl>%s</dl></br>%s</body></html>""" % (bubble_link, impl_link, pos_link, stackpos_link, html, s_line)
+ 
+ 
+    @cherrypy.expose
+    def opt_chains(self): 
+        r_host = cherrypy.request.app.config['redis']['redis.server']
+        r_port = cherrypy.request.app.config['redis']['redis.port']
+        r_db = cherrypy.request.app.config['redis']['redis.db']
+        r_sleep = cherrypy.request.app.config['redis']['redis.sleep']
+        opt_chains = cherrypy.request.app.config['redis']['redis.datastore.key.option_chains']
+        rs = redis.Redis(r_host, r_port, r_db)        
+        opt_chain_tmpl = '%s%s/opt-chains-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+ 
+        f = open(opt_chain_tmpl)
+        html_tmpl = f.read()
+
+        s_dict = rs.get(opt_chains)
+        matrix = json.loads(s_dict)
+        
+        strike = matrix.keys()[0]
+        print matrix
+        num_months = len(matrix[strike])
+        s = '["strike",'
+#         for i in range(num_months):          
+#             s = s + "'P-%s', 'C-%s', " % (matrix[strike].keys()[i], matrix[strike].keys()[i])
+        s = s + '],'
+        for month, strikes in sorted(matrix.iteritems()):
+            l = ''
+            for strike, cp in sorted(strikes.iteritems()):
+                l = l + '[%s,%s,%s,%s,%s],' % (strike, cp['P']['0'], cp['P']['1'], cp['P']['2'], cp['P']['3'])
+                
+                                
+            s = s + l + '],\n'
+        
+        print s
+        html_tmpl = html_tmpl.replace('{{{data}}}', s)
+        
+        return html_tmpl 
+ 
+    @cherrypy.expose
+    def opt_implv(self):
+        #r_host = cherrypy.request.app.config['redis']['redis.server']
+        #r_port = cherrypy.request.app.config['redis']['redis.port']
+        #r_db = cherrypy.request.app.config['redis']['redis.db']
+        #r_sleep = cherrypy.request.app.config['redis']['redis.sleep']
+        
+        opt_implv = cherrypy.request.app.config['redis']['redis.datastore.key.option_implv']
+        #rs = redis.Redis(r_host, r_port, r_db)
+        
+        rs = QServer.r_conn
+                
+        opt_implv_tmpl = '%s%s/opt-chains-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(opt_implv_tmpl)
+        html_tmpl = f.read()
+
+        s_dict = rs.get(opt_implv)
+        
+        # sample value
+        # {u'25400': {u'20150828': {u'P': [u'null', 1400.0], u'C': [0.21410911336791702, 29.0]}, u'20150929': {u'P': [u'null', u'null'], u'C': [0.1934532406732742, 170.0]}}, ...
+        matrix = json.loads(s_dict)
+        
+
+        
+        strike = matrix.keys()[0]
+        print matrix
+        num_months = len(matrix[strike])
+        s = '["strike",'
+        
+        sorted_months = sorted(matrix[strike].keys())
+        for i in range(num_months):          
+            s = s + "'P-%s', 'C-%s', " % (sorted_months[i], sorted_months[i])
+        s = s + '],'
+        for strike, items in sorted(matrix.iteritems()):
+            s = s + '[%s,' % str(strike)
+            l = ''
+            for month, cp in sorted(items.iteritems()):
+                print month, cp
+                l = l + ''.join('%s,%s,' % (cp['P'][0], cp['C'][0]))
+                                
+            s = s + l + '],\n'
+        
+        html_tmpl = html_tmpl.replace('{{{data}}}', s)
+
+
+
+        s = '["strike",'
+        for i in range(num_months):          
+            s = s + "'P-%s', 'C-%s', " % (sorted_months[i], sorted_months[i])
+        s = s + '],'
+        for strike, items in sorted(matrix.iteritems()):
+            s = s + '[%s,' % str(strike)
+            l = ''
+            for month, cp in sorted(items.iteritems()):
+                l = l + ''.join('%s,%s,' % (cp['P'][1], cp['C'][1]))
+                                
+            s = s + l + '],\n'
+        
+        print 'sorted months' + sorted_months[0]
+        html_tmpl = html_tmpl.replace('{{{dataPremium}}}', s)
+        html_tmpl = html_tmpl.replace('{{{thisContractMonth}}}', sorted_months[0])
+        
+
+        
+        return html_tmpl
+
+
+    
+
+    @cherrypy.expose
+    def opt_implv_ex(self):
+        #r_host = cherrypy.request.app.config['redis']['redis.server']
+        #r_port = cherrypy.request.app.config['redis']['redis.port']
+        #r_db = cherrypy.request.app.config['redis']['redis.db']
+        #r_sleep = cherrypy.request.app.config['redis']['redis.sleep']
+        
+        opt_implv = cherrypy.request.app.config['redis']['redis.datastore.key.option_implv']
+        #rs = redis.Redis(r_host, r_port, r_db)
+        
+        rs = QServer.r_conn
+                
+        opt_implv_tmpl = '%s%s/opt-chains-ex-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(opt_implv_tmpl)
+        html_tmpl = f.read()
+
+        s_dict = rs.get(opt_implv)
+        
+        # sample value
+        # {u'25400': {u'20150828': {u'P': [u'null', 1400.0], u'C': [0.21410911336791702, 29.0]}, u'20150929': {u'P': [u'null', u'null'], u'C': [0.1934532406732742, 170.0]}}, ...
+        matrix = json.loads(s_dict)
+        
+
+        
+        strike = matrix.keys()[0]
+        print matrix
+        num_months = len(matrix[strike])
+        s = '["strike",'
+        
+        sorted_months = sorted(matrix[strike].keys())
+        for i in range(num_months):          
+            s = s + "'P-%s', 'C-%s', " % (sorted_months[i], sorted_months[i])
+        s = s + '],'
+        for strike, items in sorted(matrix.iteritems()):
+            s = s + '[%s,' % str(strike)
+            l = ''
+            for month, cp in sorted(items.iteritems()):
+                print month, cp
+                l = l + ''.join('%s,%s,' % (cp['P'][0], cp['C'][0]))
+                                
+            s = s + l + '],\n'
+        
+        html_tmpl = html_tmpl.replace('{{{data}}}', s)
+
+
+
+        s = '[{label:"strikes",type:"number"},'
+        for i in range(num_months):          
+            s = s + "{label: 'P-%s', type:'number'},\
+                {label: 'Pb-%s', id:'i0', type:'number', role:'interval'},\
+                {label: 'Pa-%s', id:'i0', type:'number', role:'interval'},\
+                {label: 'C-%s', type:'number', },\
+                {label: 'Cb-%s', id:'i0', type:'number', role:'interval'},\
+                {label: 'Ca-%s', id:'i0',type:'number', role:'interval'},"\
+                 % (sorted_months[i], sorted_months[i],sorted_months[i], sorted_months[i],sorted_months[i], sorted_months[i])
+        s = s + '],'
+        for strike, items in sorted(matrix.iteritems()):
+            s = s + '[%s,' % str(strike)
+            l = ''
+            for month, cp in sorted(items.iteritems()):
+                l = l + ''.join('%s,%s,%s,%s,%s,%s,' % (cp['P'][1], cp['P'][2],cp['P'][3],cp['C'][1],cp['C'][2],cp['C'][3]))
+                                
+            s = s + l + '],\n'
+        
+        
+        html_tmpl = html_tmpl.replace('{{{dataPremium}}}', s)
+        
+        html_tmpl = html_tmpl.replace('{{{thisContractMonth}}}', sorted_months[0])
+        
+
+        
+        return html_tmpl
+
+
+
+
+
+
+
+
+    @cherrypy.expose
+    def ws_cal_implvol(self, s, x, cp, ed, xd, r, d, v, p, out='iv'):
+        try:
+        #spot, strike, callput, evaldate, exdate, rate, div, vol, premium
+            rs = optcal.cal_implvol(float(s), float(x), cp, ed, xd, float(r), float(d), float(v), float(p))
+            return str(rs['imvol'])
+        except:
+            return
+
+    @cherrypy.expose
+    def ws_cal_option(self, s, x, cp, ed, xd, r, d, v, out='npv'):
+        #spot, strike, callput, evaldate, exdate, rate, div, vol
+        
+        keys = ['npv', 'delta', 'gamma', 'theta', 'vega'];
+        
+        try:
+            rs = optcal.cal_option(float(s), float(x), cp, ed, xd, float(r), float(d), float(v))
+            
+            if out == 'csv':
+                logging.debug('ws_cal_option: ' + ','.join(str(rs[s]) for s in keys))
+                return ','.join(str(rs[s]) for s in keys)
+            elif out == 'json':
+                return json.dumps(rs)
+            else:
+                return str(rs[out])  
+        except:
+            #exc_type, exc_value, exc_traceback = sys.exc_info()
+            return traceback.format_exc()
+            
+    @cherrypy.expose
+    def ws_get_hist_implv(self, dataAt):
+    # given a date string YYMMDDHHMM, this routine returns an array of
+    # implied vols arranged in a format like the below
+    #      [["strike",'P-20150828', 'C-20150828', 'P-20150929', 'C-20150929', ],
+    #                      [21800,0.29153118077,null,0.241032122988,null,],
+    #                      [22000,0.284002011642,null,0.238145680311,null,],
+    #                      [22200,0.270501965746,null,0.222647164832,null,]]   
+        pass
+    
+    
+    @cherrypy.expose
+    def ws_market_data(self, r_ckey, fid):
+        if str(fid).upper() == 'ALL':
+            return QServer.r_conn.get(r_ckey)
+        val = QServer.r_conn.get(r_ckey)
+        if val is None:
+            return 'invalid request. Check your input again!'
+        dict = json.loads(QServer.r_conn.get(r_ckey))
+        return str(dict[fid]) 
+ 
+    @cherrypy.expose
+    def ws_position_chart(self):
+        p = portfolio.PortfolioManager(config)
+        p.retrieve_position()
+        opt_pos_chart_tmpl = '%s%s/opt-pos-chart-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(opt_pos_chart_tmpl)
+        html_tmpl = f.read()
+        html_tmpl = html_tmpl.replace('{{{dataPCpos}}}', p.get_grouped_options_str_array())
+        
+        html_tmpl = html_tmpl.replace('{{{dataTablePos}}}', p.get_tbl_pos_csv())
+        
+        html_tmpl = html_tmpl.replace('{{{option_months}}}', ''.join(('%s, ' % m) for m in p.get_traded_months()))
+        v = p.group_pos_by_right()
+        html_tmpl = html_tmpl.replace('{{{PRvsCR}}}}', '%0.2f : %0.2f' % (v[0][1], v[1][1]))
+        
+        #print p.get_portfolio_summary()
+        #html_tmpl = html_tmpl.replace('{{{pos_summary}}}', ''.join('<li>%s:   %s</li>' % (x[0],x[1]) for x in p.get_portfolio_summary() ))
+        #print '\n'.join('%s:\t\t%s' % (k,v) for k,v in sorted(json.loads(DataMap.rs.get(port_key)).iteritems()))
+        
+        
+        return html_tmpl
+    
+    #
+    # ws_position_chart_ex
+    #
+    # 
+    # this is an extended version of ws_position_chart
+    # shows options by month, strikes, right instead of just strikes and right
+    # 2016-03-23
+    def generate_garray(self, plist):
+        
+        
+        # generate a key map with month-right-strike
+        # example: ('20160330-C-20000', 0.2),...
+        
+        klist = map(lambda x: ('%s-%s-%s' % (x[2], x[3], x[4]), float(x[5])/50.0*float(x[6])), plist)
+        # for e in sorted(klist):
+        #     print e
+        
+        # get the unique keys in klist
+        unique_keys= Set(map(lambda x:x[0], klist))
+        strikes =[e for e in Set(map(lambda x:x[4], plist))]
+        # sort the months in ascending order
+        months = sorted([e for e in Set(map(lambda x:x[2], plist))])
+	print klist
+        print strikes
+        # print months
+        # print len(klist), len(s)
+        
+        # group and sum position by month, strike, right
+        grouped_pos = []
+        for elem in unique_keys:
+            grp1 = filter(lambda x: x[0] == elem, klist)
+            print grp1
+            # sum items with same key
+            # example: [('20160330-P-19600', -1.0), ('20160330-P-19600', 0.2)]
+            grouped_pos.append( grp1[0] if len(grp1) == 1 else reduce(lambda x,y: (x[0], x[1]+y[1]), grp1) )
+            print '---'
+        
+        print grouped_pos    
+            
+        garr = {}
+        def init_garray(x):
+            garr[x] = {}
+        map(init_garray, sorted(strikes))
+        print garr
+        
+        def set_garray(x):
+            vals = x[0].split(('-'))
+            
+            if vals[0] == months[0]:
+                
+                if vals[1] == 'C':
+                    garr[vals[2]]['NEAR_C'] = x[1]
+                else:
+                    garr[vals[2]]['NEAR_P'] = x[1]
+            elif vals[0] == months[1]:
+        
+                if vals[1] == 'C':
+                    garr[vals[2]]['FAR_C'] = x[1]
+                else:
+                    garr[vals[2]]['FAR_P'] = x[1]
+                  
+        # find all C of near month
+        map(set_garray, grouped_pos)
+        print garr
+        s=''
+        for k, v in garr.iteritems():
+            s+= '[%s, %s,%s,%s,%s],' % (k, v['NEAR_P'] if 'NEAR_P' in v else 'null',
+                                         v['NEAR_C'] if 'NEAR_C' in v else 'null',  
+                                         v['FAR_P'] if 'FAR_P' in v else 'null', 
+                                         v['FAR_C'] if 'FAR_C' in v else 'null', )
+        return s    
+    
+    
+    @cherrypy.expose
+    def ws_position_chart_ex(self):
+        p = portfolio.PortfolioManager(config)
+        p.retrieve_position()
+        opt_pos_chart_tmpl = '%s%s/opt-pos-chart-stacked-tmpl.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(opt_pos_chart_tmpl)
+        html_tmpl = f.read()
+
+        html_tmpl = html_tmpl.replace('{{{dataPCpos}}}', self.generate_garray(p.get_tbl_pos_list()))
+        
+        html_tmpl = html_tmpl.replace('{{{dataTablePos}}}', p.get_tbl_pos_csv())
+        
+        html_tmpl = html_tmpl.replace('{{{option_months}}}', ''.join(('%s, ' % m) for m in p.get_traded_months()))
+        v = p.group_pos_by_right()
+        html_tmpl = html_tmpl.replace('{{{PRvsCR}}}}', '%0.2f : %0.2f' % (v[0][1], v[1][1]))
+        
+        #print p.get_portfolio_summary()
+        #html_tmpl = html_tmpl.replace('{{{pos_summary}}}', ''.join('<li>%s:   %s</li>' % (x[0],x[1]) for x in p.get_portfolio_summary() ))
+        #print '\n'.join('%s:\t\t%s' % (k,v) for k,v in sorted(json.loads(DataMap.rs.get(port_key)).iteritems()))
+        
+        
+        return html_tmpl
+
+
+ 
+    @cherrypy.expose
+    def ws_position_summary(self):
+        p = portfolio.PortfolioManager(config)
+        keys = [("delta_1percent","number"),("delta_all","number"),("delta_c","number"),("delta_p","number"),\
+                ("theta_1percent","number"),("theta_all","number"),("theta_c","number"),("theta_p","number"),\
+                ("unreal_pl","number"),("last_updated","string"),("status","string")]
+        d = p.get_portfolio_summary()
+        
+        dict= {}
+        dict['cols'] = [{'label': x[0], 'type': x[1]} for x in keys]
+        dict['rows'] = [{'v': d[x[0]]} for x in keys]
+        print json.dumps(dict)
+        return json.dumps(dict)
+
+    @cherrypy.expose
+    def ws_recal_pos(self, force_refresh=False):
+        p = portfolio.PortfolioManager(config)
+        if force_refresh:
+            p.retrieve_position()
+        l_gmap = p.recal_port()
+        print l_gmap
+        return json.dumps(l_gmap)        
+        
+
+
+    @cherrypy.expose
+    def ws_pos_csv(self):
+        p = portfolio.PortfolioManager(config)
+        p.retrieve_position()
+        s = "%s" % p.get_tbl_pos_csv_old()
+        #s = "%s" % p.get_tbl_pos_csv()
+        #print s
+        s = s.replace(',[', '').replace(']', '<br>')
+        
+        print s
+        return s[1:len(s)-3]
+    
+    @cherrypy.expose
+    def getSHquote(self, qs):
+
+#http://api.money.126.net/data/feed/0000001,1399001,1399300        
+#_ntes_quote_callback({"0000001":{"code": "0000001", "percent": 0.015468, "askvol1": 0, "askvol3": 0, "askvol2": 0, "askvol5": 0,
+# "askvol4": 0, "price": 2972.57, "open": 2978.03, "bid5": 0, "bid4": 0, "bid3": 0, "bid2": 0, "bid1": 0, "high": 3014.41, "low": 2929.0, 
+#"updown": 45.28, "type": "SH", "bidvol1": 0, "status": 0, "bidvol3": 0, "bidvol2": 0, "symbol": "000001", "update": "2015/08/27 12:43:00", 
+#"bidvol5": 0, "bidvol4": 0, "volume": 19800251400, "ask5": 0, "ask4": 0, "ask1": 0, "name": "\u4e0a\u8bc1\u6307\u6570", "ask3": 0, "ask2": 0,
+# "arrow": "\u2191", "time": "2015/08/27 12:42:57", "yestclose": 2927.29, "turnover": 204156106776} });        
+        url = 'http://api.money.126.net/data/feed/%s?callback=ne3587367b7387dc' % qs
+        print url
+        pg = urllib2.urlopen(url.encode('utf-8'))    
+        s = pg.read().replace('ne3587367b7387dc(', '')
+        
+        
+        s = s[:len(s)-2]
+        print s
+        return s
+    
+    
+
+    
+    
+    @cherrypy.expose
+    def ws_port_summary(self):    
+        
+        rs = QServer.r_conn
+        ps_key = cherrypy.request.app.config['redis']['redis.datastore.key.port_summary']
+        s_portsum = rs.get(ps_key)
+        #dict = json.loads(s_portsum)
+        return s_portsum
+    
+
+    @cherrypy.expose
+    def ws_port_items(self):    
+        
+        rs = QServer.r_conn
+        key = cherrypy.request.app.config['redis']['redis.datastore.key.port_items']
+        s_portitems = rs.get(key)
+        #dict = json.loads(s_portsum)
+        return s_portitems
+    
+    
+    @cherrypy.expose
+    def port_bubble_chart(self):
+    
+        s_data = self.ws_bubble_data()
+
+        bubble_chart_tmpl = '%s%s/bubble-port.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(bubble_chart_tmpl)
+        html_tmpl = f.read()
+        html_tmpl = html_tmpl.replace('{{{bubble_data}}}', s_data)
+        
+        contract_month = eval(cherrypy.request.app.config['market']['option.underlying.month_price'])[0][0]
+        html_tmpl = html_tmpl.replace('{{{FUT_CONTRACT}}}', 'HSI-%s-FUT-' % (contract_month))
+        
+        
+        
+        s_acctitems, last_updated, account_no = self.ws_acct_data()
+        print s_acctitems, last_updated, account_no
+        html_tmpl = html_tmpl.replace('{{{barAcct}}}', s_acctitems)
+        html_tmpl = html_tmpl.replace('{{{account_no}}}', account_no)
+        html_tmpl = html_tmpl.replace('{{{last_updated}}}', last_updated)
+        
+        
+        
+        return html_tmpl
+       
+    @cherrypy.expose
+    def ws_bubble_data(self):
+        # Tick Value      Description
+        # 5001            impl vol
+        # 5002            delta
+        # 5003            gamma
+        # 5004            theta
+        # 5005            vega
+        # 5006            premium        
+        # 6001            avgCost
+        # 6002            pos
+        # 6003            totCost
+        # 6004            avgPx
+        # 6005            pos delta
+        # 6006            pos theta
+        # 6007            multiplier
+        # 6009            curr_port_value
+        # 6008            unreal_pl
+        # 6020            pos value impact +1% vol change
+        # 6021            pos value impact -1% vol change
+        s_portitems = self.ws_port_items() 
+        
+        litems = json.loads(s_portitems)
+        
+        # only interested in unrealized items, pos != 0 
+        ldict = filter(lambda x: x['6002'] <> 0, litems)
+        
+        lcontract = map(lambda x: x['contract'], ldict)
+        lpos_delta = map(lambda x: x['6005'], ldict)
+        lstrike = map(lambda x: x['contract'].split('-')[2], ldict)
+        ltheta = map(lambda x:  x['6006'], ldict)
+        lupl = map(lambda x: x['6008'], ldict)
+        
+        
+        
+        
+        colnames = "[['contract', 'strike', 'unreal PL', 'theta', 'delta'],"
+        print '----------------------'
+        s_data = colnames + ''.join('["%s",%s,%s,%s,%s],' % (lcontract[i], lstrike[i], lupl[i], ltheta[i], abs(lpos_delta[i])) for i in range(len(lcontract)))+ ']'
+      
+        return s_data
+    
+    
+            
+    
+    
+    @cherrypy.expose
+    def ws_acct_data(self):
+        rs = QServer.r_conn
+        key = cherrypy.request.app.config['redis']['redis.datastore.key.acct_summary']
+        s_acctitems = rs.get(key)
+        dict = json.loads(s_acctitems)
+        colnames = "[['Category', 'Value', { role: 'style' } ],"
+        unwanted_cols = ['DayTradesRemaining','last_updated', 'AccountType']
+        s_data = colnames + ''.join('["%s", %s, "%s"],' % (k, '%s'%(v[0]), '#3366CC' if float(v[0]) > 500000 else '#DC3912') if k not in unwanted_cols else '' for k, v in dict.iteritems()   )+ ']'
+      
+        return (s_data, dict['last_updated'], dict['AccountType'][2])
+    
+    
+    
+    @cherrypy.expose
+    def ws_msg_bot(self, msg):
+        a = AlertHelper(self.config)
+        a.post_msg(msg)  
+        
+
+    @cherrypy.expose
+    def ws(self):
+        logging.info('at ws')
+        # you can access the class instance through
+        handler = cherrypy.request.ws_handler
+    
+        while handler.opened == False:
+            logging.info( 'not opened')
+        
+        logging.info( 'opened')
+        
+        
+    @cherrypy.expose
+    def ws_entry(self):
+        html = '%s%s/wstest.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(html)
+        return f.read()
+    
+class OptWebSocket(WebSocket):
+    
+#     def __init__(self):
+#         logging.debug('instantiated.')
+
+
+        
+    def received_message(self, message):
+        self.send(message.data, message.is_binary)
+        logging.info('received %s' % message.data)   
+        
+        
+#     def opened(self):
+#         logging.info('web socket opened')
+        #self.send('hello')
+#         while 1:
+#             self.send('%f' % time.time(), False)
+#             time.sleep(2)
+
+            
+
+    def opened(self):
+
+        logging.info('web socket opened')
+        def data_provider():   
+            
+            while 1:         
+#                print ('%f' % time.time())
+#                time.sleep(2)
+            
+                def cb():
+                    #for i in range(1, 200, 25):
+                    #    yield "#" * i
+                    yield '%f' % time.time()
+                
+                   
+                self.send(cb())
+        
+                logging.info('--- here')
+                time.sleep(2)  
+            
+        thread.start_new_thread(data_provider())
+        
+              
+    def closed(self, code, reason=None):
+        print "Closed down", code, reason
+        
+                 
+if __name__ == '__main__':
+            
+#     logging.basicConfig(filename = "log/opt.log", filemode = 'a', 
+#                         level=logging.DEBUG,
+#                         format='%(asctime)s %(levelname)-8s %(message)s')      
+#  
+# 
+#     config = ConfigParser.ConfigParser()
+#     config.read("config/app.cfg")
+#     host = config.get("redis", "redis.server").strip('"').strip("'")
+#     port = config.get("redis", "redis.port")
+#     db = config.get("redis", "redis.db")    
+#     r_conn = redis.Redis(host,port,db)
+#     cherrypy.quickstart(QServer(r_conn, config), '/', "config/app.cfg")
+   
+    if len(sys.argv) != 2:
+        print("Usage: %s <config file>" % sys.argv[0])
+        exit(-1)    
+
+    cfg_path= sys.argv[1:]    
+    config = ConfigParser.ConfigParser()
+    if len(config.read(cfg_path)) == 0:      
+        raise ValueError, "Failed to open config file" 
+    
+    logconfig = eval(config.get("opt_serve", "opt_serve.logconfig").strip('"').strip("'"))
+    logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'
+    logging.basicConfig(**logconfig)            
+    host = config.get("redis", "redis.server").strip('"').strip("'")
+    port = config.get("redis", "redis.port")
+    db = config.get("redis", "redis.db")    
+    r_conn = redis.Redis(host,port,db)
+
+
+    
+    WebSocketPlugin(cherrypy.engine).subscribe()
+    cherrypy.tools.websocket = WebSocketTool()    
+    cherrypy.quickstart(QServer(r_conn, config), '/', cfg_path[0])
+    
+   

+ 16 - 7
finopt/optcal.py

@@ -266,8 +266,17 @@ if __name__ == '__main__':
     #spot 24119.0, X 25000, right: P, evaldate: 20150812, expiry: 20150828, rate: 0.0005, div: 0.0005, vol: 0.2000, premium: 334.0000
     #spot 24149.0, X 25200, right: P, evaldate: 20150812, expiry: 20150828, rate: 0.0005, div: 0.0005, vol: 0.2000, premium: 437.5000
     
-    results = cal_option(22363.0, 22000, 'C', '20151201', '20151230', 0.00012, 0.0328, 0.198)
+    #results = cal_option(22363.0, 22000, 'C', '20151201', '20151230', 0.00012, 0.0328, 0.198)
+    results = cal_option(30766.0, 29800, 'P', '20180105', '20180227', 0.00012, 0.0328, 0.168)
     print ''.join ('%s=%0.4f, '%(k,v) for k, v in results.iteritems())
+    results = cal_option(30766.0, 29800, 'P', '20180105', '20180227', 0.00012, 0.0328, 0.198)
+    print ''.join ('%s=%0.4f, '%(k,v) for k, v in results.iteritems())
+
+    #results = cal_implvol(30766.0, 29800, 'P', '20180105', '20180227', 0.00012, 0.0328, 0.168, results['npv'])
+    results = cal_implvol(30766, 39800, 'P', '20180105', '20180106', 0.00012, 0.0328, 0.168, 300)
+    print results
+
+
 #     results = cal_option(23067.0, 22000, 'P', '20151018', '20151029', 0.0009, 0.0328, 0.2918)
 #     npv1 = results['npv']
 #     v1 = 0.2918
@@ -310,7 +319,7 @@ if __name__ == '__main__':
 #     print chk.advance(Date(17, October, 2015), 1, 2)
     #print get_HSI_expiry(2016)
     
-    holidays = get_hk_holidays(2017)
+#    holidays = get_hk_holidays(2017)
 
     
     
@@ -327,9 +336,9 @@ if __name__ == '__main__':
                 November,
                 December,
                 ] 
-    for i in month_names:
-        dd = get_HSI_last_trading_day(holidays, i, 2017)
-        print dd
+#    for i in month_names:
+#        dd = get_HSI_last_trading_day(holidays, i, 2017)
+#        print dd
         
-    print holidays	
-    print get_HSI_last_trading_day(['20170128'], 1, 2017)
+#    print holidays	
+#    print get_HSI_last_trading_day(['20170128'], 1, 2017)

+ 1032 - 0
finopt/options_data.bak

@@ -0,0 +1,1032 @@
+# -*- coding: utf-8 -*-
+
+import sys, traceback
+import json
+import logging
+import thread
+import ConfigParser
+from ib.ext.Contract import Contract
+from ib.opt import ibConnection, message
+from time import sleep
+import time, datetime
+import optcal
+import opt_serve
+import cherrypy
+import redis
+import ystockquote
+import portfolio
+            
+# Tick Value      Description
+# 5001            impl vol
+# 5002            delta
+# 5003            gamma
+# 5004            theta
+# 5005            vega
+# 5006            premium
+# IB tick types code reference
+# https://www.interactivebrokers.com/en/software/api/api.htm
+class OptionsMarketDataManager():
+    
+    IMPL_VOL = 5001
+    DELTA    = 5002
+    GAMMA    = 5003
+    THETA    = 5004
+    VEGA     = 5005
+    PREMIUM  = 5006
+    
+
+    
+    # 
+    # map to futures ticker ids key:contract_mth  val: tick_id undlys ={'20150828': 1} 
+    undlys = {}
+    
+
+    # market data manager reference
+    mdm = None
+    rs = None
+    config = {}
+    
+    cal_greeks_config = {}
+
+    def __init__(self, config, mdm):
+        
+        self.mdm = mdm
+        self.mdm.setOMDM(self)
+        self.config = config
+
+        # config file sample values        
+        # option.underlying = "('HSI', 'FUT', 'HKFE', 'HKD', '', 0, '')"
+        # option.underlying.month_price = "[['20150828', 22817.0], ['20150929', 22715.0]]"
+        # option.underlying.tick_size = 200
+        # option.chain_range = 0.08
+        
+        undly_tuple = eval(config.get("market", "option.underlying").strip('"').strip("'"))
+        undly_months_prices = eval(config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+        undly_yahoo_ws = eval(config.get("market", "option.underlying.yahoo_ws").strip('"').strip("'"))
+        self.cal_greeks_config = eval(config.get("market", "option.greeks.recal").strip('"').strip("'"))
+    
+        
+        
+        undly_tick_size = int(config.get("market", "option.underlying.tick_size"))
+        undly_chain_range = float(config.get("market", "option.chain_range"))
+    
+        logging.debug("OptionsMarketDataManager --------------")
+        logging.debug("OptionsMarketDataManager init: undly_tuple %s" % str(undly_tuple))
+        logging.debug("OptionsMarketDataManager init: undly_months_prices %s" % str(undly_months_prices))
+        logging.debug("OptionsMarketDataManager init: undly_tick_size %d" % undly_tick_size)
+        logging.debug("OptionsMarketDataManager init: undly_chain_range %f" % undly_chain_range)
+        logging.debug("OptionsMarketDataManager --------------")
+        
+        self.option_range(undly_tuple, undly_months_prices, undly_yahoo_ws, undly_tick_size, undly_chain_range)
+
+
+    # 20150820 fix previous logic which onlyu support near month underlying subscription 
+    #def option_range(self, contractTuple, months, undlypx, tsize, bound):
+    def option_range(self, contractTuple, months_prices, undly_yahoo_ws, tsize, bound):
+        
+
+        for mth_price in months_prices:
+            
+            # set up the underlying structure
+            # fix the contract month value
+            undly = []
+            for e in contractTuple:
+                undly.append(e)
+            # set month value
+            undly[4] = mth_price[0] 
+
+
+            # subscribe the underlying
+            undly_tick_id = self.mdm.subscribe_tuple(tuple(undly))
+            # store the tick id for lookup later
+            self.undlys[mth_price[0]] = undly_tick_id
+             
+            logging.debug('OptionsMarketDataManager: option_range >>>>> subscribe underlying contract: %s' % undly)
+            
+            
+            
+            px = float(eval('%s("%s")' % (undly_yahoo_ws['func'], mth_price[2]))) if (undly_yahoo_ws['use_yahoo'] == True) else mth_price[1]
+            logging.info("OptionsMarketDataManager: ********************")
+            logging.warn("OptionsMarketDataManager: *** ")
+            logging.info("OptionsMarketDataManager: ***")
+            logging.info("OptionsMarketDataManager: *** Initial underlying price used: %.2f " % px)
+            logging.info("OptionsMarketDataManager: *** Price obtained from %s->%s" % ("yahoo" if undly_yahoo_ws['use_yahoo'] else "config file", \
+                                                                                       undly_yahoo_ws['func']))
+            logging.info("OptionsMarketDataManager: ***")
+            logging.info("OptionsMarketDataManager: ***")
+            logging.info("OptionsMarketDataManager: ********************")            
+            undlypx = round(px  / tsize) * tsize
+            upper_limit = undlypx * (1 + bound)
+            lower_limit = undlypx * (1 - bound)        
+            
+            
+            logging.info("OptionsMarketDataManager: *** subscription boundaries between %0.4f and %0.4f" % (lower_limit, upper_limit))
+            
+            # send to IB conn to subscribe
+            for i in range(int(undlypx), int(upper_limit ), tsize):
+                self.add_subscription(contractTuple, undly[4], i, 'C')
+                self.add_subscription(contractTuple, undly[4], i, 'P')
+             
+            for i in range(int(undlypx) - tsize, int(lower_limit), -tsize):
+                self.add_subscription(contractTuple, undly[4], i, 'C')
+                self.add_subscription(contractTuple, undly[4], i, 'P')            
+
+
+    def add_subscription(self, contractTuple, month, X, right):
+        
+        #contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', '20150828', 0, '')
+        opt = []
+        for e in contractTuple:
+            opt.append(e)
+        opt[1] = 'OPT'
+        opt[4] = month
+        opt[5] = X if isinstance(X, int) else int(X)
+        opt[6] = right
+        id = self.mdm.subscribe_tuple(tuple(opt))
+        logging.debug('OptionsMarketDataManager: add_subscription: %s, tick_id %d' % (opt, id))
+        return id
+        
+    
+    def determine_premium(self, msg):    
+
+
+        premium = None
+        
+        
+        #if msg.field == 4:
+        if msg.field in [4,9]:
+            premium = msg.price
+            return premium
+            
+        if msg.field in [1,2]: #bid
+            
+            
+                    
+            if msg.price < 0:
+                logging.debug("OptionsMarketDataManager determine_premium: received bogus price from IB feed. %0.4f" % msg.price)
+                logging.debug("OptionsMarketDataManager determine_premium: attempt to use last price in datamap.")
+                if 4 in DataMap().get(msg.tickerId):
+                    last_price = DataMap().get(msg.tickerId)[4]
+                    if last_price > 0:
+                        premium = last_price
+                        logging.debug("OptionsMarketDataManager determine_premium: using last price %0.4f" % premium)
+                        return premium
+                logging.debug("OptionsMarketDataManager determine_premium: No last price, no premium derived.")
+                return None
+        else:
+            logging.debug("OptionsMarketDataManager determine_premium: price field not in 1,2 or 4. skip this message...")
+            return None
+        
+        #   
+        # at this point, msg price is > 0 and msg.field in 1 or 2
+        #
+        
+        def derive_mid_price(field_id):
+            bidask = 2 if (field_id == 1) else 1
+            if bidask in DataMap().get(msg.tickerId):
+                if DataMap().get(msg.tickerId)[bidask] > 0.0:
+                    mid = (DataMap().get(msg.tickerId)[bidask] + msg.price) / 2
+                else:
+                    mid = msg.price
+            logging.debug("OptionsMarketDataManager: incoming price field%d == %0.4f datamap field%d ==2 %0.4f"\
+                           % (field_id, msg.price, bidask, DataMap().get(msg.tickerId)[bidask]))                                 
+            return mid
+        
+        premium = derive_mid_price(msg.field)
+        
+                    
+        if premium is None:
+            # skip calculation because we don't have a valid value for premium 
+            logging.debug("OptionsMarketDataManager: derive_mid_price: unable to derive a usable premium for ticker id %s, skipping computation" % (msg.tickerId))
+            return None
+        
+
+                
+            
+            
+    def on_tick_data_changed(self, msg):
+        #print 'omdm %s' % msg
+        if msg.typeName in ['tickPrice', 'tickSize']:
+           
+            if msg.typeName == 'tickPrice':
+                
+#                 if self.isTickIdAnOption(msg.tickerId):
+#                 
+#                     contract = DataMap().get(msg.tickerId)['contract']
+#                     
+#                     
+#                     
+#                     logging.info("OptionsMarketDataManager: on_tick_data_changed: received msg field %s for ticker id %s" % (msg.field, msg.tickerId))
+#                     # last price%Y%m%d
+#                     premium = None
+#                     if msg.price < 0:
+#                         logging.debug("OptionsMarketDataManager: received bogus price from IB feed. %0.4f" % msg.price) 
+#                         return
+#                     
+#                     if msg.field == 4:
+#                         premium = msg.price    
+#                     elif msg.field == 1: #bid
+#                         if 2 in DataMap().get(msg.tickerId):
+#                             if DataMap().get(msg.tickerId)[2] > 0.0:
+#                                 premium = (DataMap().get(msg.tickerId)[2] + msg.price) / 2
+#                             else:
+#                                 premium = msg.price
+#                                 
+#                             logging.debug("OptionsMarketDataManager: msgfiled ==1 %0.4f msgfiled ==2 %0.4f" % (msg.price, DataMap().get(msg.tickerId)[2]))                                 
+#                     elif msg.field == 2: #ask
+#                         if 1 in DataMap().get(msg.tickerId):
+#                             if DataMap().get(msg.tickerId)[1] > 0.0:
+#                                 premium = (DataMap().get(msg.tickerId)[1] + msg.price) / 2
+#                             else:
+#                                 premium = msg.price
+#                             logging.debug("OptionsMarketDataManager: msgfiled ==2 %0.4f msgfiled ==1 %0.4f" % (msg.price, DataMap().get(msg.tickerId)[1]))                                
+#                     if premium is None:
+#                         # skip calculation because we don't have a valid value for premium 
+#                         logging.debug("OptionsMarketDataManager: on_tick_data_changed: unable to derive a usable premium for ticker id %s, skipping computation" % (msg.tickerId))
+#                         return
+                if self.isTickIdAnOption(msg.tickerId):
+                
+                    contract = DataMap().get(msg.tickerId)['contract']
+                    logging.info("OptionsMarketDataManager: on_tick_data_changed: received msg field %s for ticker id %s" % (msg.field, msg.tickerId))                    
+                            
+                    premium = self.determine_premium(msg)
+                    if premium == None:
+                        return
+                    
+                    
+                    
+                    undly = DataMap().get(self.getUndlyId(contract.m_expiry))
+                    if 4 in undly:  # the last price
+                        spot = undly[4]
+                        
+                        logging.info('OptionsMarketDataManager:on_tick_data_changed: undelying spot %0.4f of month %s' % (spot, contract.m_expiry))
+                        today = time.strftime('%Y%m%d') 
+                        logging.info('OptionsMarketDataManager:on_tick_data_changed: today %s ' % time.strftime('%Y%m%d'))
+                        div = self.cal_greeks_config['div']
+                        rate = self.cal_greeks_config['rate']
+                        
+                        # vol is not used in the calculation of implv but quantlib requires the parameter to be passed
+                        vol = self.cal_greeks_config['vol']
+                        logging.info('OptionsMarketDataManager:on_tick_data_changed: symbol %s, spot %s, X %s, right: %s, evaldate: %s, expiry: %s, rate: %0.4f, div: %0.4f, vol: %0.4f, premium: %0.4f' % 
+                                        (contract.m_symbol, spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, vol, premium))
+                        
+                        try:
+                        
+                            iv = optcal.cal_implvol(spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, vol, premium)
+                            
+                        except Exception, err:
+                            logging.error(traceback.format_exc())
+                            logging.error("OptionsMarketDataManager: *******************recovering from a implvol error ******")
+                            
+                            intrinsic = abs(contract.m_strike - spot)  
+                            iv = optcal.cal_implvol(spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, vol, premium)
+                            logging.error("OptionsMarketDataManager: ******** Using intrinsic value to calculate premium %0.4f instead of the spot premium %0.4f"\
+                                                                        % (intrinsic, premium ))
+                        
+                        logging.info('OptionsMarketDataManager:on_tick_data_changed: implied vol: %0.4f' % iv['imvol'])
+                        results = optcal.cal_option(spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, iv['imvol'])
+                        
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.IMPL_VOL] = iv['imvol']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.DELTA] = results['delta']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.GAMMA] = results['gamma']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.THETA] = results['theta']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.VEGA] = results['vega']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.PREMIUM] = results['npv'] 
+        
+                        # update Redis store
+                        #
+                        DataMap().update_rd(msg.tickerId)
+        
+                else: # underlying price changed
+                    # check whether new option chains need to be added
+                    #
+                    # check logic to be implemented
+                    
+                    
+                    # save undly tick to REDIS
+                    contract = DataMap().get(msg.tickerId)['contract']
+                    logging.debug('OptionsMarketDataManager:on_tick_data_changed: ------- Underlying Price updated: tick id %d datamap contract %s' % (msg.tickerId, ContractHelper.printContract(contract)))
+                    
+                    
+                    DataMap().update_rd(msg.tickerId)
+          
+    
+    # return tick_id of underlying 
+    # this function has problem, it doesn't cater for the symbol type
+    # so MHI contracts will be processed just fine but with HSI futures  
+    # returned as its underlying
+    # >>> ok as it doesn't casuse any problem in calculating the greeks
+    # >>> but need to be fixed later
+    def getUndlyId(self, contract_mth):
+        return self.undlys[contract_mth]
+    
+    def isTickIdAnOption(self, tickid):
+        return tickid not in self.undlys.values()
+            
+
+class MarketDataManager():
+    
+    config = {}
+    tick_id = 0
+    instrus = {}
+    con = None
+    omdm = None
+    
+    def __init__(self, config):
+        self.config = config
+        ibgw = config.get("market", "ib.gateway").strip('"').strip("'")
+        ibport = config.get("market", "ib.port")
+        ibid = config.get("market", "ib.appid")
+        logging.debug("ibgw, port, app id: %s %s %s" % (ibgw, ibport, ibid))
+        self.con = ibConnection(ibgw, int(ibport), int(ibid))
+        
+        self.con.registerAll(self.on_ib_message)
+
+    
+
+    def setOMDM(self, omdm):
+        self.omdm = omdm
+        self.omdm.mdm = self
+        
+    def subscribe_tuple(self, tuple):        
+        return self.subscribe(ContractHelper.makeContract(tuple))
+
+    def subscribe(self, contract):
+
+
+        tick_id = -1
+        if self.isContractSubscribed(contract) == False:
+            #logging.debug( 'MarketDataManager:subscribe subscrbing to' + ContractHelper.printContract(contract))
+            
+            self.con.reqMktData(self.tick_id, contract, '', False)
+            tick_id = self.tick_id
+            #self.instrus[self.tick_id] = {}
+            #self.instrus[self.tick_id]['contract'] = contract
+            
+            DataMap().set(self.tick_id, {})
+            DataMap().get(self.tick_id)['contract'] = contract
+            
+            logging.debug( 'MarketDataManager:subscribe DataMap stored value - >' + ContractHelper.printContract(DataMap().get(self.tick_id)['contract']))
+            self.tick_id = self.tick_id + 1
+            
+        else:
+            logging.debug("Contract has been subscribed already %s" % ContractHelper.printContract(contract))
+        return tick_id
+
+    def isContractSubscribed(self, contract):
+        
+        
+        
+        #for c in self.instrus.values():
+        # 20150814 - changed to rely on DataMap values
+        for c in DataMap().getData().values():
+            if c['contract'] == contract:
+                logging.debug("MarketDataManager: isContractSubscribed: YES %s" % ContractHelper.printContract(c['contract']))
+                return True   
+        
+        logging.debug("MarketDataManager: isContractSubscribed: No. Subscribing to %s" % ContractHelper.printContract(contract))
+        return False
+
+
+        
+
+    def on_ib_message(self, msg):
+        
+        if msg.typeName in ['tickPrice', 'tickSize']:
+
+#             if msg.tickerId not in self.instrus:
+#                 self.instrus[msg.tickerId] = {}
+            
+            if msg.typeName == 'tickPrice':
+                #self.instrus[msg.tickerId][msg.field] = msg.price
+                DataMap().get(msg.tickerId)[msg.field] = msg.price
+                 
+            if msg.typeName == 'tickSize':
+                #self.instrus[msg.tickerId][msg.field] = msg.size
+                DataMap().get(msg.tickerId)[msg.field] = msg.size
+            
+            #if msg.typeName == "tickSize":
+            #    logging.debug(msg)
+            
+            
+        if self.omdm != None:
+            self.omdm.on_tick_data_changed(msg)
+
+
+                               
+
+    def connect(self):
+        rc = self.con.connect()
+        logging.info("-----------------")
+        logging.info("-----------MarketDataManager ----Connect to IB connection: status: %s" % str(rc))
+        logging.info("-----------------")
+        if rc == False:
+            logging.error("-----------MarketDataManager ---- Connect to IB Failed!!! Terminating....")
+            sys.exit(-1)
+        
+        
+    def disconnect(self):
+        for i in range(self.tick_id):
+            self.con.cancelMktData(i)
+            logging.debug("cancelling tick id subscription %d" % i)
+        self.con.disconnect()
+
+class ContractHelper():
+    
+    def __init__(self, contractTuple):
+        self.makeContract(contractTuple)
+    
+    @staticmethod
+    def makeContract(contractTuple):
+        newContract = Contract()
+        newContract.m_symbol = contractTuple[0]
+        newContract.m_secType = contractTuple[1]
+        newContract.m_exchange = contractTuple[2]
+        newContract.m_currency = contractTuple[3]
+        newContract.m_expiry = contractTuple[4]
+        newContract.m_strike = contractTuple[5]
+        newContract.m_right = contractTuple[6]
+        logging.debug( 'Contract Values:%s,%s,%s,%s,%s,%s,%s:' % contractTuple)
+        return newContract
+    
+    @staticmethod
+    def convert2Tuple(newContract):
+        newContractTuple = (newContract.m_symbol,\
+                            newContract.m_secType,\
+                            newContract.m_exchange,\
+                            newContract.m_currency,\
+                            newContract.m_expiry,\
+                            newContract.m_strike,\
+                            newContract.m_right, newContract.m_conId)
+        logging.debug( 'Contract Values:%s,%s,%s,%s,%s,%s,%s %s:' % newContractTuple)
+        return newContractTuple
+    
+    
+    @staticmethod
+    def printContract(contract):
+        s = '[%s-%s-%s-%s-%s-%s-%s-%s]' % (contract.m_symbol,
+                                                       contract.m_secType,
+                                                       contract.m_exchange,
+                                                       contract.m_currency,
+                                                       contract.m_expiry,
+                                                       contract.m_strike,
+                                                       contract.m_right,
+                                                       contract.m_conId)
+        #logging.info(s)
+        return s
+    
+    @staticmethod
+    def makeRedisKey(contract):
+        #print "makerediskey %s" % ContractHelper.printContract(contract)
+#20150904        
+        contract.m_strike = int(contract.m_strike)
+        
+        if contract.m_secType == 'OPT':
+            s = '%s-%s-%s-%s' % (contract.m_symbol,
+                                                           contract.m_expiry,
+                                                           contract.m_strike,
+                                                           contract.m_right)
+        else:
+            s = '%s-%s-%s-%s' % (contract.m_symbol,
+                                                           contract.m_expiry,
+                                                           contract.m_secType, '')
+            
+        return s
+      
+    @staticmethod
+    def makeRedisKeyEx(contract, old=False):
+        # this routine is to circumvent a problem in makeRedisKey with 
+        # the key in position 3 having different meanings under different conditions.
+        #  
+        # 
+        # to differentiate the keys generated by the old and new functions, 
+        # contract keys created using this routine have their last slot
+        # hard coded a magic number 102
+        
+        if (old):
+            return ContractHelper.makeRedisKey(contract)
+        
+        contract.m_strike = int(contract.m_strike)
+        s = '%s-%s-%s-%s-%s-%s-%d' % (contract.m_symbol,
+                                                           contract.m_expiry,
+                                                           contract.m_strike,
+                                                           contract.m_right,
+                                                           contract.m_secType,
+                                                           contract.m_currency,
+                                                           102)
+        return s
+      
+def test():
+#     contractTuple = ('USD', 'CASH', 'IDEALPRO', 'JPY', '', 0.0, '')
+#     contractTuple2 = ('xSD', 'CASH', 'IDEALPRO', 'JPY', '', 0.0, '')
+#      
+#     print 1 if contractTuple == contractTuple2 else -1
+#     
+#     
+#     
+#     contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', '20150929', 0, '')
+#     
+#     
+#     d = DataMap()
+#     d.set('c', ContractHelper.makeContract(contractTuple))
+#     c = d.get('c')
+#     print ContractHelper.printContract(c)
+#     mdm = MarketDataManager({})
+#     
+#     o = OptionsMarketDataManager(mdm, c)
+    
+    def f1(*a):
+        s = ''
+        for i in a:
+            s = s + '%s,' % i
+        return s
+
+    rs = redis.Redis()
+    start = 201508200930
+    end = int(datetime.datetime.now().strftime('%Y%m%d%H%M'))
+    
+    for i in range(start, end):
+        if rs.exists(i):
+            j= json.loads(rs.get(i))
+            
+            i = str(i)
+# 
+#             print '[new Date(%s,%s,%s,%s,%s), %s, %s, %s, undefined],' % (i[0:4], i[4:6], i[6:8], i[8:10], i[10:12],
+#                                                         j['22400']['20150828']['P'][0], 
+#                                                         j['22600']['20150828']['P'][0],
+#                                                          j['22600']['20150929']['P'][0])
+            s =  '[new Date(%s,%d,%s,%s,%s),' % (i[0:4], int(i[4:6])-1, i[6:8], i[8:10], i[10:12])
+            s = s + f1(j['21400']['20150828']['P'][0], j['21800']['20150828']['P'][0], j['21800']['20150929']['P'][0], 'undefined', ']')
+            print s
+
+    
+
+    sys.exit(-1)
+
+class DataMap():
+    instrus = {}
+    rs = None
+    rskeys = {}
+    mkt_sessions = {}
+    config = None
+
+
+    def init_redis(self, config):
+        #self.config = config
+        DataMap.config = config
+        host = config.get("redis", "redis.server").strip('"').strip("'")
+        port = config.get("redis", "redis.port")
+        db = config.get("redis", "redis.db")
+        self.rskeys['redis.datastore.key.option_implv'] = config.get("redis", "redis.datastore.key.option_implv").strip('"').strip("'")
+        self.rskeys['redis.datastore.key.option_chains'] = config.get("redis", "redis.datastore.key.option_chains").strip('"').strip("'")
+        self.rskeys['redis.datastore.key.option_set'] = config.get("redis", "redis.datastore.key.option_set").strip('"').strip("'")
+        self.rskeys['redis.datastore.key.option_implv_ts_set'] = config.get("redis", "redis.datastore.key.option_implv_ts_set").strip('"').strip("'")
+        self.rskeys['redis.datastore.key.option_implv_ts'] = config.get("redis", "redis.datastore.key.option_implv_ts").strip('"').strip("'")
+        
+        
+        self.rskeys['hkex.openhours'] = config.get("market", "hkex.openhours").strip('"').strip("'")
+        self.rskeys['option.bid_ask_spread_tolerance'] = config.get("market", "option.bid_ask_spread_tolerance")
+        
+        DataMap.mkt_sessions = json.loads(self.rskeys['hkex.openhours'])
+        
+
+        
+        DataMap.rs = redis.Redis(host, port, db)
+        logging.info(self.rs.info())
+    
+    def redisConn(self):
+        return DataMap.rs
+    
+    
+    # this routine clones an element in instrus but without the contract object  
+    def simple_clone(self, contract):
+        instru = {}
+        for key, stuff in contract.iteritems():
+            if key <> 'contract':
+                instru[key] = stuff
+
+        return instru
+        
+        
+    def update_rd(self, tickerId):
+        
+        # this routine saves options data into redis map
+        # map structure
+        # 
+        #key:
+        #    underlying
+        #         month
+        #            strike
+        #                right
+        #value:
+        #                values
+        rs = DataMap().redisConn()
+        if rs == None:
+            logging.error("update_rd: Redis connection is None! Has it been initialized?")
+            return
+    
+        contract = DataMap().get(tickerId)['contract']
+        r_key = ContractHelper.makeRedisKey(contract)
+        
+        rs.sadd(self.rskeys['redis.datastore.key.option_set'], r_key)
+        data = DataMap().simple_clone(DataMap().get(tickerId))
+        data['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
+        
+        jd = json.dumps(data)
+        rs.set(r_key, jd)
+
+        #logging.debug( "update rd: [%s] %s " % (r_key, jd)) 
+        
+    
+    
+    def get(self, key):
+        return DataMap.instrus[key]
+    
+    def set(self, key, val):
+        DataMap.instrus[key] = val
+        
+    
+    def getData(self):
+        return DataMap.instrus
+    
+    
+    def isKeyInMap(self, k):
+        return (k in DataMap.instrus)
+    
+    
+    def createOptionsPanelData(self):
+
+        # this function dumps the data in DataMap and turn it into a json string
+        # set the value on redis map
+        # and make it available for the web service to query the json string
+        # it is up to the receiving end to format the json sring into whatever 
+        # structure for viewing. for example, into a data array accepted by google charts
+      
+        matrix = {}
+        for tick_id, instr_v in DataMap().getData().iteritems():
+#20150910 - data now contains MHI 
+# need to add extra condition to filter out MHI
+
+            #if DataMap().get(tick_id)['contract'].m_secType == 'OPT':
+            if DataMap().get(tick_id)['contract'].m_secType == 'OPT' and \
+                DataMap().get(tick_id)['contract'].m_symbol == 'HSI':
+                strike = DataMap().get(tick_id)['contract'].m_strike
+                expiry = DataMap().get(tick_id)['contract'].m_expiry
+                right = DataMap().get(tick_id)['contract'].m_right
+                if expiry not in matrix:
+                    matrix[expiry] = {}
+                if strike not in matrix[expiry]:
+                    matrix[expiry][strike] = {}
+                if right not in matrix[expiry][strike]:
+                    matrix[expiry][strike][right] = {}
+
+                for field, value in sorted(instr_v.iteritems()):
+                    if type(value) is Contract:
+                        pass
+                        #s = s + "%s," % ContractHelper.printContract(value)
+                    else:
+                        matrix[expiry][strike][right][field] = value
+                     
+                    
+        
+        
+        DataMap.rs.set(DataMap.rskeys['redis.datastore.key.option_chains'], json.dumps(matrix))
+        logging.debug("createOptionsPanelData: %s" % str( matrix))
+    
+    def printMap(self):
+
+        for tick_id, instr_v in DataMap().getData().iteritems():
+            s = 'Tick:%s [%s]>>' % (tick_id, ContractHelper.printContract(DataMap().get(tick_id)['contract'])) 
+            for field, value in sorted(instr_v.iteritems()):
+                if type(value) is Contract:
+                    continue
+                    #s = s + "%s," % ContractHelper.printContract(value)
+                else:
+                    s = s + "%d:[%s], " % (field, value)
+                 
+            print "%s" % s
+
+    def printMapCSV(self):
+        keys = [0,1,2,3,4,5,6,7,8,9,14,5001,5002,5003,5004,5005,5006]
+        print 'tick id,contract,' + ','.join(str(k) for k in keys)
+        for tick_id, instr_v in DataMap().getData().iteritems():
+            
+            #s = '%s,%s-%s-%s,' % (tick_id, DataMap().get(tick_id)['contract'].m_right, DataMap().get(tick_id)['contract'].m_expiry, DataMap().get(tick_id)['contract'].m_strike)
+            s = '%s,%s,' % (tick_id, ContractHelper.makeRedisKey(DataMap().get(tick_id)['contract']))
+            for k in keys:
+                if k in instr_v.keys():
+                    s = s + "%s, " % (instr_v[k])
+                else:
+                    s = s + ','
+                 
+            print "%s" % s
+        
+        
+    def createImplVolChartData(self):    
+
+        
+        matrix = {}
+        for tick_id, instr_v in DataMap().getData().iteritems():
+#            if DataMap().get(tick_id)['contract'].m_secType == 'OPT':
+            if DataMap().get(tick_id)['contract'].m_secType == 'OPT' and \
+                DataMap().get(tick_id)['contract'].m_symbol == 'HSI':
+                
+                strike = DataMap().get(tick_id)['contract'].m_strike
+                expiry = DataMap().get(tick_id)['contract'].m_expiry
+                right = DataMap().get(tick_id)['contract'].m_right
+                if strike not in matrix:
+                    matrix[strike] = {}
+                if expiry not in matrix[strike]:
+                    matrix[strike][expiry] = {}
+                if right not in matrix[strike][expiry]:
+#                    matrix[strike][expiry][right] = ['null','null']
+#20150915                     
+                    matrix[strike][expiry][right] = ['null','null','null','null']
+                if 5001 in instr_v.keys(): 
+                    matrix[strike][expiry][right][0] =  (instr_v[5001]) if self.isfloat(instr_v[5001]) else 'null'
+                # if bid and ask is available show mid price, else use last price
+                # but first verify that bid ask spread are not wider than 10%, else use last price
+                
+                
+                if 1 in instr_v.keys() and 2 in instr_v.keys() and 4 in instr_v.keys():
+                    
+                    bid = float(instr_v[1])
+                    ask = float(instr_v[2])
+                    mid = (bid + ask) / 2.0
+                    tolerance = float(self.rskeys['option.bid_ask_spread_tolerance'])
+                    logging.debug( "createImplVolChartData %s %s %s %s %s %s bid/ask ratio %f tolerance level %f" %\
+                                         (strike, expiry, right, instr_v[1], instr_v[2], instr_v[4], mid / max(bid, ask),\
+                                          tolerance)) 
+                    if  (mid / max(bid, ask)) >  tolerance and bid > 0 and ask > 0:
+                        matrix[strike][expiry][right][1] = str(mid)
+                        
+                        
+                    else:
+                        matrix[strike][expiry][right][1] =  (instr_v[4]) if self.isfloat(instr_v[4]) else 'null'
+                        logging.debug( 'createImplVolChartData: unusable bid/ask prices. using last price instead %s' % (instr_v[4]))
+                        
+#20150915     
+# add bid / ask to the the matrix
+# [0] delta, [1] mid or last, [2]                                    
+                    matrix[strike][expiry][right][2] =  (instr_v[1] if instr_v[1] > 0 else matrix[strike][expiry][right][1]) if self.isfloat(instr_v[1]) else 'null'
+                    matrix[strike][expiry][right][3] =  (instr_v[2] if instr_v[2] > 0 else matrix[strike][expiry][right][1]) if self.isfloat(instr_v[2]) else 'null'
+                    logging.debug( 'createImplVolChartData: unusable bid/ask prices. bid and ask %s,%s' % \
+                                   (str(matrix[strike][expiry][right][2]), str(matrix[strike][expiry][right][2])))
+                        
+                else:
+                    if 4 in instr_v.keys():
+                        #print "%s %s %s " % (instr_v[1], instr_v[2], instr_v[4]) 
+                        matrix[strike][expiry][right][1] =  (instr_v[4]) if self.isfloat(instr_v[4]) else 'null'
+                        matrix[strike][expiry][right][2] =  (instr_v[4]) if self.isfloat(instr_v[4]) else 'null'
+                        matrix[strike][expiry][right][3] =  (instr_v[4]) if self.isfloat(instr_v[4]) else 'null'
+        
+        DataMap.rs.set(DataMap.rskeys['redis.datastore.key.option_implv'], json.dumps(matrix))
+        logging.debug("createImplVolChartData: %s" % str( matrix))
+        
+        # save impl vol to historical time series
+        tk = datetime.datetime.now().strftime('%Y%m%d%H%M')        
+        DataMap.rs.sadd(self.rskeys['redis.datastore.key.option_implv_ts_set'], tk)
+        DataMap.rs.set(tk, json.dumps(matrix))
+        logging.debug("createImplVolChartData: saving option implvol matrix key=[%s] " % tk)
+        
+#       Sample values generated after executing the below:
+#
+#         ["strike",'P-20150828', 'C-20150828', 'P-20150929', 'C-20150929', ],[22400,0.285061765463,null,0.237686059088,null,],
+#         [22600,0.27409953156,null,0.236034059533,0.232365044818,],
+#         [22800,0.263230478241,0.320792426398,0.236005003448,0.234991796766,],
+#         [23000,0.251675376297,null,0.228237217842,0.230063445797,],
+#         [23200,0.241904579328,0.311715835907,0.2242344851,0.226293660467,],
+#         [23400,0.227987380777,0.284433386637,0.218600527288,0.203082047571,],
+        
+#         num_months = len(matrix[strike])
+#         s = '["strike",'
+#         for i in range(num_months):          
+#             s = s + "'P-%s', 'C-%s', " % (matrix[strike].keys()[i], matrix[strike].keys()[i])
+#         s = s + '],'
+#         for strike, items in sorted(matrix.iteritems()):
+#             s = s + '[%s,' % str(strike)
+#             l = ''
+#             for month, cp in sorted(items.iteritems()):
+#                 l = l + ''.join('%s,%s,' % (cp['P'], cp['C']))
+#                 #(('0.4f') % (cp['P']) if self.isfloat(cp['P']) else cp['P'], 
+#                 #                            ('0.4f') % (cp['C']) if self.isfloat(cp['C']) else cp['C'])
+#                                 
+#             s = s + l + '],\n'
+#         print s               
+
+    def isfloat(self, value):
+        try:
+          float(value)
+          return True
+        except ValueError:
+          return False
+
+            
+    def printContractByID(self, tick_id):
+        tick_id = int(tick_id)
+
+        if DataMap().isKeyInMap(tick_id):
+    
+            
+            s = 'Tick:%s [%s]>>' % (tick_id, ContractHelper.printContract(DataMap().get(tick_id)['contract'])) 
+            for field, value in DataMap().get(tick_id).iteritems():
+                if type(value) is not Contract:
+                    s = s + "%d:[%s], " % (field, value)
+            print "%s" % s
+
+        print 'values stored in redis:'
+        print "%s" % DataMap.rs.get(ContractHelper.makeRedisKey(DataMap().get(tick_id)['contract']))
+
+
+    def refreshRedisImplVol(self, sec):
+        while 1:
+            now = int(time.strftime('%H%M'))
+            
+            #print "%d" % now  + ','.join("%s,%s"% (v[0],v[1]) for k,v in DataMap.mkt_sessions.iteritems())
+            
+            if (now >= int(DataMap.mkt_sessions['morning'][0]) and now <= int(DataMap.mkt_sessions['morning'][1]) \
+                or  now >= int(DataMap.mkt_sessions['afternoon'][0]) and now <= int(DataMap.mkt_sessions['afternoon'][1])):
+                DataMap().createImplVolChartData()
+                logging.info('refreshRedisImplVol: update redis map every %ds' % sec)
+            
+            sleep(sec)
+
+    def refresh_portfolio(self, sec):
+        p = portfolio.PortfolioManager(self.config)
+        
+        p.retrieve_position()
+#20150914
+        #p.recal_port()
+
+        
+#        undly_months_prices = eval(self.config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+        
+        cnt = 0
+        while 1:
+            now = int(time.strftime('%H%M'))
+            if (now >= int(DataMap.mkt_sessions['morning'][0]) and now <= int(DataMap.mkt_sessions['morning'][1]) \
+                or  now >= int(DataMap.mkt_sessions['afternoon'][0]) and now <= int(DataMap.mkt_sessions['afternoon'][1])):             
+                
+                
+#20151002
+#                     logging.debug('refresh_portfolio: force retrieve all positions from IB!!')
+
+# force retrive again on every run
+#                     p.retrieve_position()
+                    
+                    
+# subscribe new contracts traded
+#                     toks = map(lambda x: x.split('-'), p.get_pos_contracts())
+#             
+#                     for t in toks:
+#                         contractTuple = (t[0], 'FUT', 'HKFE', 'HKD', '', 0, '')
+#                         
+#                         #add_subscription(self, contractTuple, month, X, right):
+#                         rc = map(lambda x: (omd.add_subscription(x[0], x[1], x[2], x[3])),\
+#                                              [(contractTuple, m[0], t[2], 'P') for m in undly_months_prices] +\
+#                                              [(contractTuple, m[0], t[2], 'C') for m in undly_months_prices])        
+#                 
+                     s = p.recal_port()
+                     #print s
+                    
+            sleep(sec)
+
+# copy list of subscribed contracts from the console in options_data
+# save them into a text file
+# call this function to generate subscription txt that 
+# can be processed by ib_mds.py
+def create_subscription_file(src, dest):
+    
+    # improper file content will cause
+    # this function to fail
+    f = open(src)
+    lns = f.readlines()
+
+    a= filter(lambda x: x[0] <> '\n', map(lambda x: x.split(','), lns))
+    contracts = map(lambda x: x.split('-'), [c[1] for c in a])
+    options = filter(lambda x: x[2] <> 'FUT', contracts)
+    futures = filter(lambda x: x[2] == 'FUT', contracts)
+    print contracts
+    #HSI,FUT,HKFE,HKD,20151029,0,
+    futm= map(lambda x: "%s,%s,%s,%s,%s,%s,%s" % (x[0], 'FUT', 'HKFE', 'HKD', x[1], '0', ''), futures)
+    outm= map(lambda x: "%s,%s,%s,%s,%s,%s,%s" % (x[0], 'OPT', 'HKFE', 'HKD', x[1], x[2], x[3]), options)
+    f1 = open(dest, 'w')
+    f1.write(''.join('%s\n'% c for c in outm))
+    f1.write(''.join('%s\n'% c for c in futm))
+    f1.close()
+
+def console(config, omd):
+    undly_months_prices = eval(config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+    done = False
+    while not done:
+#        try:
+        print "Available commands are: l - list all subscribed contracts, i - force recal impl vol, s <H/M> <X> - H for HSI M for MHI manual subscription of HKFE options"
+        print "                        p - print portfolio summary,   r - rescan portfolio and update subscription list"
+        print "                        c [src] [dest] - dump subscribed contracts to an external file"
+        print "                       <id> - list subscribed contract by id, q - terminate program"
+        cmd = raw_input(">>")
+        input = cmd.split(' ')
+        if input[0]  == "q":
+            done = True
+        elif input[0] == 'l' or input[0] == '':
+            #DataMap().printMap()
+            DataMap().printMapCSV()
+            DataMap().createOptionsPanelData()
+        elif input[0] == 'i':           
+            DataMap().createImplVolChartData()
+        elif input[0] == 's':
+            symbol = 'HSI' if input[1].upper() == 'H' else 'MHI'
+            print symbol
+            X = int(input[2])
+            contractTuple = (symbol, 'FUT', 'HKFE', 'HKD', '', 0, '')
+            #add_subscription(self, contractTuple, month, X, right):
+            rc = map(lambda x: (omd.add_subscription(x[0], x[1], x[2], x[3])),\
+                                 [(contractTuple, m[0], X, 'P') for m in undly_months_prices] +\
+                                 [(contractTuple, m[0], X, 'C') for m in undly_months_prices])
+            print 'subscribed items: %s' % rc
+        elif input[0] == 'p':
+#             port_key  = '%s_%s' % (config.get("redis", "redis.datastore.key.port_prefix").strip('"').strip("'"), \
+#                             config.get("redis", "redis.datastore.key.port_summary").strip('"').strip("'"))
+            port_key  = config.get("redis", "redis.datastore.key.port_summary").strip('"').strip("'")
+
+            print 'position summary'
+
+            print '\n'.join('%s:\t\t%s' % (k,v) for k,v in sorted(json.loads(DataMap.rs.get(port_key)).iteritems()))
+            print '-----end position summary\nl'
+        elif input[0] == 'r':
+            add_portfolio_subscription(config, omd)
+        elif input[0] == 'c':
+            create_subscription_file(input[1], input[2])
+            
+        elif isinstance(input[0], int): 
+            DataMap().printContractByID(input[0])
+        else:
+            pass
+            
+#         except:
+#             exc_type, exc_value, exc_traceback = sys.exc_info()
+#             traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
+
+
+def add_portfolio_subscription(config, omd):
+    logging.info('add_portfolio_subscription: subscribe market data for portfolio items')
+    p = portfolio.PortfolioManager(config)
+    p.retrieve_position()
+    
+    
+    undly_months_prices = eval(config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+    
+    # example: MHI-20150929-22600-P
+    toks = map(lambda x: x.split('-'), p.get_pos_contracts())
+    print p.get_pos_contracts()
+    #[['MHI','20150929', 22600, 'P'], [...]]
+    for t in toks:
+        contractTuple = (t[0], 'FUT', 'HKFE', 'HKD', '', 0, '')
+        
+        #add_subscription(self, contractTuple, month, X, right):
+        rc = map(lambda x: (omd.add_subscription(x[0], x[1], x[2], x[3])),\
+                             [(contractTuple, m[0], t[2], 'P') for m in undly_months_prices] +\
+                             [(contractTuple, m[0], t[2], 'C') for m in undly_months_prices])        
+#         rc = map(lambda x: (omd.add_subscription(x[0], x[1], x[2], x[3])),\
+#                         [((tok[0], 'OPT', 'HKFE', 'HKD', '', 0, ''), m[0], tok[2], tok[3]) for tok in toks])
+                             
+    logging.info('add_portfolio_subscription: add results: %s' % rc)
+
+
+
+if __name__ == '__main__':
+        
+    if len(sys.argv) != 2:
+        print("Usage: %s <config file>" % sys.argv[0])
+        exit(-1)    
+
+    cfg_path= sys.argv[1:]    
+    config = ConfigParser.SafeConfigParser()
+    if len(config.read(cfg_path)) == 0: 
+        raise ValueError, "Failed to open config file" 
+      
+    logconfig = eval(config.get("options_data", "options_data.logconfig").strip('"').strip("'"))
+    logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'    
+    logging.basicConfig(**logconfig)        
+    
+    DataMap().init_redis(config)
+    mdm = MarketDataManager(config)  
+    mdm.connect()
+    omd = OptionsMarketDataManager(config, mdm)
+#     contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', '', 0, '')
+#     o.option_range(contractTuple, [['20150828', 22817.0], ['20150929', 22715.0]], 200, 0.08)
+
+    add_portfolio_subscription(config, omd)
+
+    thread.start_new_thread(DataMap().refreshRedisImplVol, (60,))
+#    thread.start_new_thread(DataMap().refresh_portfolio, (5,))
+
+    thread.start_new_thread(DataMap().refresh_portfolio, (5, ))
+    console(config, omd)
+  
+
+       
+    mdm.disconnect()
+    
+         
+    

+ 1168 - 0
finopt/options_data.py.bak20170117

@@ -0,0 +1,1168 @@
+# -*- coding: utf-8 -*-
+
+import sys, traceback
+import json
+import logging
+import thread
+import ConfigParser
+from ib.ext.Contract import Contract
+from ib.opt import ibConnection#, message
+from time import sleep
+import time, datetime
+import optcal
+import opt_serve
+import cherrypy
+import redis
+import ystockquote
+import portfolio
+from misc2.helpers import ContractHelper
+
+            
+# Tick Value      Description
+# 5001            impl vol
+# 5002            delta
+# 5003            gamma
+# 5004            theta
+# 5005            vega
+# 5006            premium
+# IB tick types code reference
+# https://www.interactivebrokers.com/en/software/api/api.htm
+class OptionsMarketDataManager():
+    
+    IMPL_VOL = 5001
+    DELTA    = 5002
+    GAMMA    = 5003
+    THETA    = 5004
+    VEGA     = 5005
+    PREMIUM  = 5006
+    
+
+    
+    # 
+    # map to futures ticker ids key:contract_mth  val: tick_id undlys ={'20150828': 1} 
+    undlys = {}
+    
+
+    # market data manager reference
+    mdm = None
+    rs = None
+    config = {}
+    
+    cal_greeks_config = {}
+
+    def __init__(self, config, mdm):
+        
+        self.mdm = mdm
+        self.mdm.setOMDM(self)
+        self.config = config
+
+        # config file sample values        
+        # option.underlying = "('HSI', 'FUT', 'HKFE', 'HKD', '', 0, '')"
+        # option.underlying.month_price = "[['20150828', 22817.0], ['20150929', 22715.0]]"
+        # option.underlying.tick_size = 200
+        # option.chain_range = 0.08
+        
+        undly_tuple = eval(config.get("market", "option.underlying").strip('"').strip("'"))
+        #undly_months_prices = eval(config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+        # 20160612
+        undly_months_prices = DataMap().rskeys['option.underlying.month_price']
+        
+        undly_yahoo_ws = eval(config.get("market", "option.underlying.yahoo_ws").strip('"').strip("'"))
+        self.cal_greeks_config = eval(config.get("market", "option.greeks.recal").strip('"').strip("'"))
+    
+        
+        
+        undly_tick_size = int(config.get("market", "option.underlying.tick_size"))
+        undly_chain_range = float(config.get("market", "option.chain_range"))
+    
+        logging.debug("OptionsMarketDataManager --------------")
+        logging.debug("OptionsMarketDataManager init: undly_tuple %s" % str(undly_tuple))
+        logging.debug("OptionsMarketDataManager init: undly_months_prices %s" % str(undly_months_prices))
+        logging.debug("OptionsMarketDataManager init: undly_tick_size %d" % undly_tick_size)
+        logging.debug("OptionsMarketDataManager init: undly_chain_range %f" % undly_chain_range)
+        logging.debug("OptionsMarketDataManager --------------")
+        
+        self.option_range(undly_tuple, undly_months_prices, undly_yahoo_ws, undly_tick_size, undly_chain_range)
+
+
+    # 20150820 fix previous logic which onlyu support near month underlying subscription 
+    #def option_range(self, contractTuple, months, undlypx, tsize, bound):
+    def option_range(self, contractTuple, months_prices, undly_yahoo_ws, tsize, bound):
+        
+
+        for mth_price in months_prices:
+            
+            # set up the underlying structure
+            # fix the contract month value
+            undly = []
+            for e in contractTuple:
+                undly.append(e)
+            # set month value
+            undly[4] = mth_price[0] 
+
+
+            # subscribe the underlying
+            undly_tick_id = self.mdm.subscribe_tuple(tuple(undly))
+            # store the tick id for lookup later
+            self.undlys[mth_price[0]] = undly_tick_id
+             
+            logging.debug('OptionsMarketDataManager: option_range >>>>> subscribe underlying contract: %s' % undly)
+            
+            
+            
+            px = float(eval('%s("%s")' % (undly_yahoo_ws['func'], mth_price[2]))) if (undly_yahoo_ws['use_yahoo'] == True) else mth_price[1]
+            logging.info("OptionsMarketDataManager: ********************")
+            logging.warn("OptionsMarketDataManager: *** ")
+            logging.info("OptionsMarketDataManager: ***")
+            logging.info("OptionsMarketDataManager: *** Initial underlying price used: %.2f " % px)
+            logging.info("OptionsMarketDataManager: *** Price obtained from %s->%s" % ("yahoo" if undly_yahoo_ws['use_yahoo'] else "config file", \
+                                                                                       undly_yahoo_ws['func']))
+            logging.info("OptionsMarketDataManager: ***")
+            logging.info("OptionsMarketDataManager: ***")
+            logging.info("OptionsMarketDataManager: ********************")            
+            undlypx = round(px  / tsize) * tsize
+            upper_limit = undlypx * (1 + bound)
+            lower_limit = undlypx * (1 - bound)        
+            
+            
+            logging.info("OptionsMarketDataManager: *** subscription boundaries between %0.4f and %0.4f" % (lower_limit, upper_limit))
+            
+            # send to IB conn to subscribe
+            for i in range(int(undlypx), int(upper_limit ), tsize):
+                self.add_subscription(contractTuple, undly[4], i, 'C')
+                self.add_subscription(contractTuple, undly[4], i, 'P')
+             
+            for i in range(int(undlypx) - tsize, int(lower_limit), -tsize):
+                self.add_subscription(contractTuple, undly[4], i, 'C')
+                self.add_subscription(contractTuple, undly[4], i, 'P')            
+
+
+    def add_subscription(self, contractTuple, month, X, right):
+        
+        #contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', '20150828', 0, '')
+        opt = []
+        for e in contractTuple:
+            opt.append(e)
+        opt[1] = 'OPT'
+        opt[4] = month
+        opt[5] = X if isinstance(X, int) else int(X)
+        opt[6] = right
+        id = self.mdm.subscribe_tuple(tuple(opt))
+        logging.debug('OptionsMarketDataManager: add_subscription: %s, tick_id %d' % (opt, id))
+        return id
+        
+    
+    def determine_premium(self, msg):    
+
+
+        premium = None
+        
+        
+        #if msg.field == 4:
+        if msg.field in [4,9]:
+            premium = msg.price
+            return premium
+            
+        if msg.field in [1,2]: #bid
+            
+            
+                    
+            if msg.price < 0:
+                logging.debug("OptionsMarketDataManager determine_premium: received bogus price from IB feed. %0.4f" % msg.price)
+                logging.debug("OptionsMarketDataManager determine_premium: attempt to use last price in datamap.")
+                if 4 in DataMap().get(msg.tickerId):
+                    last_price = DataMap().get(msg.tickerId)[4]
+                    if last_price > 0:
+                        premium = last_price
+                        logging.debug("OptionsMarketDataManager determine_premium: using last price %0.4f" % premium)
+                        return premium
+                logging.debug("OptionsMarketDataManager determine_premium: No last price, no premium derived.")
+                return None
+        else:
+            logging.debug("OptionsMarketDataManager determine_premium: price field not in 1,2 or 4. skip this message...")
+            return None
+        
+        #   
+        # at this point, msg price is > 0 and msg.field in 1 or 2
+        #
+        
+        def derive_mid_price(field_id):
+            bidask = 2 if (field_id == 1) else 1
+            if bidask in DataMap().get(msg.tickerId):
+                if DataMap().get(msg.tickerId)[bidask] > 0.0:
+                    mid = (DataMap().get(msg.tickerId)[bidask] + msg.price) / 2
+                else:
+                    mid = msg.price
+            logging.debug("OptionsMarketDataManager: incoming price field%d == %0.4f datamap field%d ==2 %0.4f"\
+                           % (field_id, msg.price, bidask, DataMap().get(msg.tickerId)[bidask]))                                 
+            return mid
+        
+        premium = derive_mid_price(msg.field)
+        
+                    
+        if premium is None:
+            # skip calculation because we don't have a valid value for premium 
+            logging.debug("OptionsMarketDataManager: derive_mid_price: unable to derive a usable premium for ticker id %s, skipping computation" % (msg.tickerId))
+            return None
+        
+
+                
+            
+            
+    def on_tick_data_changed(self, msg):
+        #print 'omdm %s' % msg
+        if msg.typeName in ['tickPrice', 'tickSize']:
+           
+            if msg.typeName == 'tickPrice':
+                
+#                 if self.isTickIdAnOption(msg.tickerId):
+#                 
+#                     contract = DataMap().get(msg.tickerId)['contract']
+#                     
+#                     
+#                     
+#                     logging.info("OptionsMarketDataManager: on_tick_data_changed: received msg field %s for ticker id %s" % (msg.field, msg.tickerId))
+#                     # last price%Y%m%d
+#                     premium = None
+#                     if msg.price < 0:
+#                         logging.debug("OptionsMarketDataManager: received bogus price from IB feed. %0.4f" % msg.price) 
+#                         return
+#                     
+#                     if msg.field == 4:
+#                         premium = msg.price    
+#                     elif msg.field == 1: #bid
+#                         if 2 in DataMap().get(msg.tickerId):
+#                             if DataMap().get(msg.tickerId)[2] > 0.0:
+#                                 premium = (DataMap().get(msg.tickerId)[2] + msg.price) / 2
+#                             else:
+#                                 premium = msg.price
+#                                 
+#                             logging.debug("OptionsMarketDataManager: msgfiled ==1 %0.4f msgfiled ==2 %0.4f" % (msg.price, DataMap().get(msg.tickerId)[2]))                                 
+#                     elif msg.field == 2: #ask
+#                         if 1 in DataMap().get(msg.tickerId):
+#                             if DataMap().get(msg.tickerId)[1] > 0.0:
+#                                 premium = (DataMap().get(msg.tickerId)[1] + msg.price) / 2
+#                             else:
+#                                 premium = msg.price
+#                             logging.debug("OptionsMarketDataManager: msgfiled ==2 %0.4f msgfiled ==1 %0.4f" % (msg.price, DataMap().get(msg.tickerId)[1]))                                
+#                     if premium is None:
+#                         # skip calculation because we don't have a valid value for premium 
+#                         logging.debug("OptionsMarketDataManager: on_tick_data_changed: unable to derive a usable premium for ticker id %s, skipping computation" % (msg.tickerId))
+#                         return
+                if self.isTickIdAnOption(msg.tickerId):
+                
+                    contract = DataMap().get(msg.tickerId)['contract']
+                    logging.info("OptionsMarketDataManager: on_tick_data_changed: received msg field %s for ticker id %s" % (msg.field, msg.tickerId))                    
+                            
+                    premium = self.determine_premium(msg)
+                    if premium == None:
+                        return
+                    
+                    
+                    
+                    undly = DataMap().get(self.getUndlyId(contract.m_expiry))
+                    if 4 in undly:  # the last price
+                        spot = undly[4]
+                        
+                        logging.info('OptionsMarketDataManager:on_tick_data_changed: undelying spot %0.4f of month %s' % (spot, contract.m_expiry))
+                        today = time.strftime('%Y%m%d') 
+                        logging.info('OptionsMarketDataManager:on_tick_data_changed: today %s ' % time.strftime('%Y%m%d'))
+                        div = self.cal_greeks_config['div']
+                        rate = self.cal_greeks_config['rate']
+                        
+                        # vol is not used in the calculation of implv but quantlib requires the parameter to be passed
+                        vol = self.cal_greeks_config['vol']
+                        logging.info('OptionsMarketDataManager:on_tick_data_changed: symbol %s, spot %s, X %s, right: %s, evaldate: %s, expiry: %s, rate: %0.4f, div: %0.4f, vol: %0.4f, premium: %0.4f' % 
+                                        (contract.m_symbol, spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, vol, premium))
+                        
+                        try:
+                        
+                            iv = optcal.cal_implvol(spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, vol, premium)
+                            
+                        except Exception, err:
+                            logging.error(traceback.format_exc())
+                            logging.error("OptionsMarketDataManager: *******************recovering from a implvol error ******")
+                            
+                            intrinsic = abs(contract.m_strike - spot)  
+                            iv = optcal.cal_implvol(spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, vol, premium)
+                            logging.error("OptionsMarketDataManager: ******** Using intrinsic value to calculate premium %0.4f instead of the spot premium %0.4f"\
+                                                                        % (intrinsic, premium ))
+                        
+                        logging.info('OptionsMarketDataManager:on_tick_data_changed: implied vol: %0.4f' % iv['imvol'])
+                        results = optcal.cal_option(spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, iv['imvol'])
+                        
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.IMPL_VOL] = iv['imvol']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.DELTA] = results['delta']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.GAMMA] = results['gamma']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.THETA] = results['theta']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.VEGA] = results['vega']
+                        DataMap().get(msg.tickerId)[OptionsMarketDataManager.PREMIUM] = results['npv'] 
+        
+                        # update Redis store
+                        #
+                        DataMap().update_rd(msg.tickerId)
+        
+                else: # underlying price changed
+                    # check whether new option chains need to be added
+                    #
+                    # check logic to be implemented
+                    
+                    
+                    # save undly tick to REDIS
+                    contract = DataMap().get(msg.tickerId)['contract']
+                    logging.debug('OptionsMarketDataManager:on_tick_data_changed: ------- Underlying Price updated: tick id %d datamap contract %s' % (msg.tickerId, ContractHelper.printContract(contract)))
+                    
+                    
+                    DataMap().update_rd(msg.tickerId)
+          
+    
+    # return tick_id of underlying 
+    # this function has problem, it doesn't cater for the symbol type
+    # so MHI contracts will be processed just fine but with HSI futures  
+    # returned as its underlying
+    # >>> ok as it doesn't casuse any problem in calculating the greeks
+    # >>> but need to be fixed later
+    def getUndlyId(self, contract_mth):
+        return self.undlys[contract_mth]
+    
+    def isTickIdAnOption(self, tickid):
+        return tickid not in self.undlys.values()
+            
+
+class MarketDataManager():
+    
+    config = {}
+    tick_id = 0
+    instrus = {}
+    con = None
+    omdm = None
+    
+    def __init__(self, config):
+        self.config = config
+        ibgw = config.get("market", "ib.gateway").strip('"').strip("'")
+        ibport = config.get("market", "ib.port")
+        ibid = config.get("market", "ib.appid")
+        logging.debug("ibgw, port, app id: %s %s %s" % (ibgw, ibport, ibid))
+        self.con = ibConnection(ibgw, int(ibport), int(ibid))
+        
+        self.con.registerAll(self.on_ib_message)
+
+    
+
+    def setOMDM(self, omdm):
+        self.omdm = omdm
+        self.omdm.mdm = self
+        
+    def subscribe_tuple(self, tuple):        
+        return self.subscribe(ContractHelper.makeContract(tuple))
+
+    def subscribe(self, contract):
+
+
+        tick_id = -1
+        if self.isContractSubscribed(contract) == False:
+            #logging.debug( 'MarketDataManager:subscribe subscrbing to' + ContractHelper.printContract(contract))
+            
+            self.con.reqMktData(self.tick_id, contract, '', False)
+            tick_id = self.tick_id
+            #self.instrus[self.tick_id] = {}
+            #self.instrus[self.tick_id]['contract'] = contract
+            
+            DataMap().set(self.tick_id, {})
+            DataMap().get(self.tick_id)['contract'] = contract
+            
+            logging.debug( 'MarketDataManager:subscribe DataMap stored value - >' + ContractHelper.printContract(DataMap().get(self.tick_id)['contract']))
+            self.tick_id = self.tick_id + 1
+            
+        else:
+            logging.debug("Contract has been subscribed already %s" % ContractHelper.printContract(contract))
+        return tick_id
+
+    def isContractSubscribed(self, contract):
+        
+        
+        
+        #for c in self.instrus.values():
+        # 20150814 - changed to rely on DataMap values
+        for c in DataMap().getData().values():
+            if c['contract'] == contract:
+                logging.debug("MarketDataManager: isContractSubscribed: YES %s" % ContractHelper.printContract(c['contract']))
+                return True   
+        
+        logging.debug("MarketDataManager: isContractSubscribed: No. Subscribing to %s" % ContractHelper.printContract(contract))
+        return False
+
+
+        
+
+    def on_ib_message(self, msg):
+        
+        if msg.typeName in ['tickPrice', 'tickSize']:
+
+#             if msg.tickerId not in self.instrus:
+#                 self.instrus[msg.tickerId] = {}
+            
+            if msg.typeName == 'tickPrice':
+                #self.instrus[msg.tickerId][msg.field] = msg.price
+                DataMap().get(msg.tickerId)[msg.field] = msg.price
+                 
+            if msg.typeName == 'tickSize':
+                #self.instrus[msg.tickerId][msg.field] = msg.size
+                DataMap().get(msg.tickerId)[msg.field] = msg.size
+            
+            #if msg.typeName == "tickSize":
+            #    logging.debug(msg)
+            
+            
+        if self.omdm != None:
+            self.omdm.on_tick_data_changed(msg)
+
+
+                               
+
+    def connect(self):
+        rc = self.con.connect()
+        logging.info("-----------------")
+        logging.info("-----------MarketDataManager ----Connect to IB connection: status: %s" % str(rc))
+        logging.info("-----------------")
+        if rc == False:
+            logging.error("-----------MarketDataManager ---- Connect to IB Failed!!! Terminating....")
+            sys.exit(-1)
+        
+        
+    def disconnect(self):
+        for i in range(self.tick_id):
+            self.con.cancelMktData(i)
+            logging.debug("cancelling tick id subscription %d" % i)
+        self.con.disconnect()
+
+      
+def test():
+#     contractTuple = ('USD', 'CASH', 'IDEALPRO', 'JPY', '', 0.0, '')
+#     contractTuple2 = ('xSD', 'CASH', 'IDEALPRO', 'JPY', '', 0.0, '')
+#      
+#     print 1 if contractTuple == contractTuple2 else -1
+#     
+#     
+#     
+#     contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', '20150929', 0, '')
+#     
+#     
+#     d = DataMap()
+#     d.set('c', ContractHelper.makeContract(contractTuple))
+#     c = d.get('c')
+#     print ContractHelper.printContract(c)
+#     mdm = MarketDataManager({})
+#     
+#     o = OptionsMarketDataManager(mdm, c)
+    
+    def f1(*a):
+        s = ''
+        for i in a:
+            s = s + '%s,' % i
+        return s
+
+    rs = redis.Redis()
+    start = 201508200930
+    end = int(datetime.datetime.now().strftime('%Y%m%d%H%M'))
+    
+    for i in range(start, end):
+        if rs.exists(i):
+            j= json.loads(rs.get(i))
+            
+            i = str(i)
+# 
+#             print '[new Date(%s,%s,%s,%s,%s), %s, %s, %s, undefined],' % (i[0:4], i[4:6], i[6:8], i[8:10], i[10:12],
+#                                                         j['22400']['20150828']['P'][0], 
+#                                                         j['22600']['20150828']['P'][0],
+#                                                          j['22600']['20150929']['P'][0])
+            s =  '[new Date(%s,%d,%s,%s,%s),' % (i[0:4], int(i[4:6])-1, i[6:8], i[8:10], i[10:12])
+            s = s + f1(j['21400']['20150828']['P'][0], j['21800']['20150828']['P'][0], j['21800']['20150929']['P'][0], 'undefined', ']')
+            print s
+
+    
+
+    sys.exit(-1)
+
+class DataMap():
+    instrus = {}
+    rs = None
+    rskeys = {}
+    mkt_sessions = {}
+    
+    config = None
+
+
+    def init_redis(self, config):
+        #self.config = config
+        DataMap.config = config
+        host = config.get("redis", "redis.server").strip('"').strip("'")
+        port = config.get("redis", "redis.port")
+        db = config.get("redis", "redis.db")
+        self.rskeys['redis.datastore.key.option_implv'] = config.get("redis", "redis.datastore.key.option_implv").strip('"').strip("'")
+        self.rskeys['redis.datastore.key.option_chains'] = config.get("redis", "redis.datastore.key.option_chains").strip('"').strip("'")
+        self.rskeys['redis.datastore.key.option_set'] = config.get("redis", "redis.datastore.key.option_set").strip('"').strip("'")
+        self.rskeys['redis.datastore.key.option_implv_ts_set'] = config.get("redis", "redis.datastore.key.option_implv_ts_set").strip('"').strip("'")
+        self.rskeys['redis.datastore.key.option_implv_ts'] = config.get("redis", "redis.datastore.key.option_implv_ts").strip('"').strip("'")
+        
+        
+        self.rskeys['hkex.openhours'] = config.get("market", "hkex.openhours").strip('"').strip("'")
+        self.rskeys['option.bid_ask_spread_tolerance'] = config.get("market", "option.bid_ask_spread_tolerance")
+        
+        DataMap.mkt_sessions = json.loads(self.rskeys['hkex.openhours'])
+        
+
+        
+        DataMap.rs = redis.Redis(host, port, db)
+        logging.info(self.rs.info())
+        
+        
+        self.rskeys['option.underlying.month_price'] = self.set_option_calendar()
+
+
+    # 20160612
+    # routine to determine the near and far month contract expiry date
+    #
+    # logic:
+    #      - determine today's date
+    #
+    #      - auto mode:
+    #      - look up entries in redis
+    #      - if records are found, use the records in redis
+    #         else 
+    #            retrive record from hkgov website
+    #            update redis db
+    #      -  
+    # 
+    # for all hard coded options end dates:
+    # https://www.hkex.com.hk/eng/prod/drprod/hkifo/tradcalend_2.htm to get all dates 
+    # 
+    #
+    def set_option_calendar(self):
+        year = int(datetime.datetime.now().strftime('%Y'))
+        month = int(datetime.datetime.now().strftime('%m'))
+
+
+        holiday_key_prefix = config.get("redis", "redis.datastore.key.hkex_holiday_prefix").strip('"').strip("'")        
+        
+        rs = self.redisConn()
+        holiday_key = '%s%s' % (holiday_key_prefix, year)
+        holidays = rs.get(holiday_key)
+                    
+#        if holidays == None:
+#	fix 2017-01-03
+        if holidays == 'null':
+            holidays = optcal.get_hk_holidays(year)
+            logging.info("options_data:set_option_calendar: retrieved from gov.hk --> update Redis: [%s]: %s", holiday_key, json.dumps(holidays))
+            rs.set(holiday_key, json.dumps(holidays))    
+
+        else:
+            holidays = json.loads(holidays)
+            logging.info("options_data:set_option_calendar: retrieved holidays from redis %s", str(holidays))
+
+        
+        
+        undly_months_prices = eval(config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+        
+        undly_months_prices[0][0] = optcal.get_HSI_last_trading_day(holidays, month, year)
+	next_month = (month + 1) % 12 if (month + 1) % 12 <> 0 else (month + 1) % 12 + 12
+#2016/12 lc - fix year end bug 
+	year = year if next_month <> 1 else year + 1
+	
+        logging.info("options_data:set_option_calendar: next month: year %s:%s " % (next_month, year))
+        undly_months_prices[1][0] = optcal.get_HSI_last_trading_day(holidays, next_month, year)
+        
+        logging.info("options_data:set_option_calendar:  %s " % str(undly_months_prices))
+        return undly_months_prices
+    
+    def redisConn(self):
+        return DataMap.rs
+    
+    
+    # this routine clones an element in instrus but without the contract object  
+    def simple_clone(self, contract):
+        instru = {}
+        for key, stuff in contract.iteritems():
+            if key <> 'contract':
+                instru[key] = stuff
+
+        return instru
+        
+        
+    def update_rd(self, tickerId):
+        
+        # this routine saves options data into redis map
+        # map structure
+        # 
+        #key:
+        #    underlying
+        #         month
+        #            strike
+        #                right
+        #value:
+        #                values
+        rs = DataMap().redisConn()
+        if rs == None:
+            logging.error("update_rd: Redis connection is None! Has it been initialized?")
+            return
+    
+        contract = DataMap().get(tickerId)['contract']
+        r_key = ContractHelper.makeRedisKey(contract)
+        
+        rs.sadd(self.rskeys['redis.datastore.key.option_set'], r_key)
+        data = DataMap().simple_clone(DataMap().get(tickerId))
+        data['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
+        
+        jd = json.dumps(data)
+        rs.set(r_key, jd)
+
+        #logging.debug( "update rd: [%s] %s " % (r_key, jd)) 
+        
+    
+    
+    def get(self, key):
+        return DataMap.instrus[key]
+    
+    def set(self, key, val):
+        DataMap.instrus[key] = val
+        
+    
+    def getData(self):
+        return DataMap.instrus
+    
+    
+    def isKeyInMap(self, k):
+        return (k in DataMap.instrus)
+    
+    
+    def createOptionsPanelData(self):
+
+        # this function dumps the data in DataMap and turn it into a json string
+        # set the value on redis map
+        # and make it available for the web service to query the json string
+        # it is up to the receiving end to format the json sring into whatever 
+        # structure for viewing. for example, into a data array accepted by google charts
+      
+        matrix = {}
+        for tick_id, instr_v in DataMap().getData().iteritems():
+#20150910 - data now contains MHI 
+# need to add extra condition to filter out MHI
+
+            #if DataMap().get(tick_id)['contract'].m_secType == 'OPT':
+            if DataMap().get(tick_id)['contract'].m_secType == 'OPT' and \
+                DataMap().get(tick_id)['contract'].m_symbol == 'HSI':
+                strike = DataMap().get(tick_id)['contract'].m_strike
+                expiry = DataMap().get(tick_id)['contract'].m_expiry
+                right = DataMap().get(tick_id)['contract'].m_right
+                if expiry not in matrix:
+                    matrix[expiry] = {}
+                if strike not in matrix[expiry]:
+                    matrix[expiry][strike] = {}
+                if right not in matrix[expiry][strike]:
+                    matrix[expiry][strike][right] = {}
+
+                for field, value in sorted(instr_v.iteritems()):
+                    if type(value) is Contract:
+                        pass
+                        #s = s + "%s," % ContractHelper.printContract(value)
+                    else:
+                        matrix[expiry][strike][right][field] = value
+                     
+                    
+        
+        
+        DataMap.rs.set(DataMap.rskeys['redis.datastore.key.option_chains'], json.dumps(matrix))
+        logging.debug("createOptionsPanelData: %s" % str( matrix))
+    
+    def printMap(self):
+
+        for tick_id, instr_v in DataMap().getData().iteritems():
+            s = 'Tick:%s [%s]>>' % (tick_id, ContractHelper.printContract(DataMap().get(tick_id)['contract'])) 
+            for field, value in sorted(instr_v.iteritems()):
+                if type(value) is Contract:
+                    continue
+                    #s = s + "%s," % ContractHelper.printContract(value)
+                else:
+                    s = s + "%d:[%s], " % (field, value)
+                 
+            print "%s" % s
+
+    def printMapCSV(self):
+        keys = [0,1,2,3,4,5,6,7,8,9,14,5001,5002,5003,5004,5005,5006]
+        print 'tick id,contract,' + ','.join(str(k) for k in keys)
+        lines = ''
+        for tick_id, instr_v in DataMap().getData().iteritems():
+            
+            #s = '%s,%s-%s-%s,' % (tick_id, DataMap().get(tick_id)['contract'].m_right, DataMap().get(tick_id)['contract'].m_expiry, DataMap().get(tick_id)['contract'].m_strike)
+            s = '%s,%s,' % (tick_id, ContractHelper.makeRedisKey(DataMap().get(tick_id)['contract']))
+            for k in keys:
+                if k in instr_v.keys():
+                    s = s + "%s, " % (instr_v[k])
+                else:
+                    s = s + ','
+            
+            print s
+
+            lines += "%s\n" % s
+        return lines
+        
+        
+    def createImplVolChartData(self):    
+
+        
+        matrix = {}
+        for tick_id, instr_v in DataMap().getData().iteritems():
+#            if DataMap().get(tick_id)['contract'].m_secType == 'OPT':
+            if DataMap().get(tick_id)['contract'].m_secType == 'OPT' and \
+                DataMap().get(tick_id)['contract'].m_symbol == 'HSI':
+                
+                strike = DataMap().get(tick_id)['contract'].m_strike
+                expiry = DataMap().get(tick_id)['contract'].m_expiry
+                right = DataMap().get(tick_id)['contract'].m_right
+                if strike not in matrix:
+                    matrix[strike] = {}
+                if expiry not in matrix[strike]:
+                    matrix[strike][expiry] = {}
+                if right not in matrix[strike][expiry]:
+#                    matrix[strike][expiry][right] = ['null','null']
+#20150915                     
+                    matrix[strike][expiry][right] = ['null','null','null','null']
+                logging.debug('-------------########################------------------- instr_v.keys')
+                logging.debug(instr_v.keys())
+                if 5001 in instr_v.keys(): 
+                    
+                    matrix[strike][expiry][right][0] =  (instr_v[5001]) if self.isfloat(instr_v[5001]) else 'null'
+                # if bid and ask is available show mid price, else use last price
+                # but first verify that bid ask spread are not wider than 10%, else use last price
+                
+                
+                if 1 in instr_v.keys() and 2 in instr_v.keys() and 4 in instr_v.keys():
+                    
+                    bid = float(instr_v[1])
+                    ask = float(instr_v[2])
+                    mid = (bid + ask) / 2.0
+                    tolerance = float(self.rskeys['option.bid_ask_spread_tolerance'])
+                    logging.debug( "createImplVolChartData %s %s %s %s %s %s bid/ask ratio %f tolerance level %f" %\
+                                         (strike, expiry, right, instr_v[1], instr_v[2], instr_v[4], mid / max(bid, ask),\
+                                          tolerance)) 
+                    if  (mid / max(bid, ask)) >  tolerance and bid > 0 and ask > 0:
+                        matrix[strike][expiry][right][1] = str(mid)
+                        
+                        
+                    else:
+                        matrix[strike][expiry][right][1] =  (instr_v[4]) if self.isfloat(instr_v[4]) else 'null'
+                        logging.debug( 'createImplVolChartData: unusable bid/ask prices. using last price instead %s' % (instr_v[4]))
+                        
+#20150915     
+# add bid / ask to the the matrix
+# [0] delta, [1] mid or last, [2]                                    
+                    matrix[strike][expiry][right][2] =  (instr_v[1] if instr_v[1] > 0 else matrix[strike][expiry][right][1]) if self.isfloat(instr_v[1]) else 'null'
+                    matrix[strike][expiry][right][3] =  (instr_v[2] if instr_v[2] > 0 else matrix[strike][expiry][right][1]) if self.isfloat(instr_v[2]) else 'null'
+                    logging.debug( 'createImplVolChartData: bid/ask prices. bid and ask %s,%s' % \
+                                   (str(matrix[strike][expiry][right][2]), str(matrix[strike][expiry][right][3])))
+                        
+                else:
+                    if 4 in instr_v.keys():
+                        #print "%s %s %s " % (instr_v[1], instr_v[2], instr_v[4]) 
+                        matrix[strike][expiry][right][1] =  (instr_v[4]) if self.isfloat(instr_v[4]) else 'null'
+                        matrix[strike][expiry][right][2] =  (instr_v[4]) if self.isfloat(instr_v[4]) else 'null'
+                        matrix[strike][expiry][right][3] =  (instr_v[4]) if self.isfloat(instr_v[4]) else 'null'
+        
+        DataMap.rs.set(DataMap.rskeys['redis.datastore.key.option_implv'], json.dumps(matrix))
+        logging.debug("createImplVolChartData: %s" % str( matrix))
+        
+        # save impl vol to historical time series
+        tk = datetime.datetime.now().strftime('%Y%m%d%H%M')        
+        DataMap.rs.sadd(self.rskeys['redis.datastore.key.option_implv_ts_set'], tk)
+        DataMap.rs.set(tk, json.dumps(matrix))
+        logging.debug("createImplVolChartData: saving option implvol matrix key=[%s] " % tk)
+        
+#       Sample values generated after executing the below:
+#
+#         ["strike",'P-20150828', 'C-20150828', 'P-20150929', 'C-20150929', ],[22400,0.285061765463,null,0.237686059088,null,],
+#         [22600,0.27409953156,null,0.236034059533,0.232365044818,],
+#         [22800,0.263230478241,0.320792426398,0.236005003448,0.234991796766,],
+#         [23000,0.251675376297,null,0.228237217842,0.230063445797,],
+#         [23200,0.241904579328,0.311715835907,0.2242344851,0.226293660467,],
+#         [23400,0.227987380777,0.284433386637,0.218600527288,0.203082047571,],
+        
+#         num_months = len(matrix[strike])
+#         s = '["strike",'
+#         for i in range(num_months):          
+#             s = s + "'P-%s', 'C-%s', " % (matrix[strike].keys()[i], matrix[strike].keys()[i])
+#         s = s + '],'
+#         for strike, items in sorted(matrix.iteritems()):
+#             s = s + '[%s,' % str(strike)
+#             l = ''
+#             for month, cp in sorted(items.iteritems()):
+#                 l = l + ''.join('%s,%s,' % (cp['P'], cp['C']))
+#                 #(('0.4f') % (cp['P']) if self.isfloat(cp['P']) else cp['P'], 
+#                 #                            ('0.4f') % (cp['C']) if self.isfloat(cp['C']) else cp['C'])
+#                                 
+#             s = s + l + '],\n'
+#         print s               
+
+    def isfloat(self, value):
+        try:
+          float(value)
+          return True
+        except ValueError:
+          return False
+
+            
+    def printContractByID(self, tick_id):
+        tick_id = int(tick_id)
+
+        if DataMap().isKeyInMap(tick_id):
+    
+            
+            s = 'Tick:%s [%s]>>' % (tick_id, ContractHelper.printContract(DataMap().get(tick_id)['contract'])) 
+            for field, value in DataMap().get(tick_id).iteritems():
+                if type(value) is not Contract:
+                    s = s + "%d:[%s], " % (field, value)
+            print "%s" % s
+
+        print 'values stored in redis:'
+        print "%s" % DataMap.rs.get(ContractHelper.makeRedisKey(DataMap().get(tick_id)['contract']))
+
+
+    def refreshRedisImplVol(self, sec):
+        while 1:
+            now = int(time.strftime('%H%M'))
+            
+            #print "%d" % now  + ','.join("%s,%s"% (v[0],v[1]) for k,v in DataMap.mkt_sessions.iteritems())
+            
+            if (now >= int(DataMap.mkt_sessions['morning'][0]) and now <= int(DataMap.mkt_sessions['morning'][1]) \
+                or  now >= int(DataMap.mkt_sessions['afternoon'][0]) and now <= int(DataMap.mkt_sessions['afternoon'][1])):
+                DataMap().createImplVolChartData()
+                logging.info('refreshRedisImplVol: update redis map every %ds' % sec)
+            
+            sleep(sec)
+
+    def refresh_portfolio(self, sec):
+        p = portfolio.PortfolioManager(self.config)
+        
+        p.retrieve_position()
+#20150914
+        #p.recal_port()
+
+        
+#        undly_months_prices = eval(self.config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+        
+        cnt = 0
+        while 1:
+            now = int(time.strftime('%H%M'))
+            if (now >= int(DataMap.mkt_sessions['morning'][0]) and now <= int(DataMap.mkt_sessions['morning'][1]) \
+                or  now >= int(DataMap.mkt_sessions['afternoon'][0]) and now <= int(DataMap.mkt_sessions['afternoon'][1])):             
+                
+                
+#20151002
+#                     logging.debug('refresh_portfolio: force retrieve all positions from IB!!')
+
+# force retrive again on every run
+#                     p.retrieve_position()
+                    
+                    
+# subscribe new contracts traded
+#                     toks = map(lambda x: x.split('-'), p.get_pos_contracts())
+#             
+#                     for t in toks:
+#                         contractTuple = (t[0], 'FUT', 'HKFE', 'HKD', '', 0, '')
+#                         
+#                         #add_subscription(self, contractTuple, month, X, right):
+#                         rc = map(lambda x: (omd.add_subscription(x[0], x[1], x[2], x[3])),\
+#                                              [(contractTuple, m[0], t[2], 'P') for m in undly_months_prices] +\
+#                                              [(contractTuple, m[0], t[2], 'C') for m in undly_months_prices])        
+#                 
+                     s = p.recal_port()
+                     #print s
+                    
+            sleep(sec)
+
+
+
+
+# class ContractHelper():
+#       
+#     def __init__(self, contractTuple):
+#         self.makeContract(contractTuple)
+#       
+#       
+#     @staticmethod
+#     def makeContract(contractTuple):
+#         newContract = Contract()
+#         newContract.m_symbol = contractTuple[0]
+#         newContract.m_secType = contractTuple[1]
+#         newContract.m_exchange = contractTuple[2]
+#         newContract.m_currency = contractTuple[3]
+#         newContract.m_expiry = contractTuple[4]
+#         newContract.m_strike = contractTuple[5]
+#         newContract.m_right = contractTuple[6]
+#         logging.debug( 'Contract Values:%s,%s,%s,%s,%s,%s,%s:' % contractTuple)
+#         return newContract
+#       
+#     @staticmethod
+#     def convert2Tuple(newContract):
+#         newContractTuple = (newContract.m_symbol,\
+#                             newContract.m_secType,\
+#                             newContract.m_exchange,\
+#                             newContract.m_currency,\
+#                             newContract.m_expiry,\
+#                             newContract.m_strike,\
+#                             newContract.m_right, newContract.m_conId)
+#         logging.debug( 'Contract Values:%s,%s,%s,%s,%s,%s,%s %s:' % newContractTuple)
+#         return newContractTuple
+#       
+#   
+#     @staticmethod
+#     def contract2mapstring(contract):
+#         return json.dumps(contract.__dict__)
+#   
+#   
+#   
+#     @staticmethod
+#     def mapstring2contract(sm_contract):
+#           
+#         newContract = Contract()
+#         mapContract = json.loads(sm_contract)
+#         map(lambda x: newContract.__setattr__(x, mapContract[x].encode('ascii') if type(mapContract[x]) == unicode else mapContract[x]), mapContract.keys())
+#         return newContract
+#   
+#   
+#     @staticmethod
+#     def map2contract(m_contract):
+#           
+#         newContract = Contract()
+#         map(lambda x: newContract.__setattr__(x, m_contract[x].encode('ascii') if type(m_contract[x]) == unicode else m_contract[x]), m_contract.keys())
+#         return newContract
+#       
+#   
+#   
+#       
+#     @staticmethod
+#     def printContract(contract):
+#         s = '[%s-%s-%s-%s-%s-%s-%s-%s]' % (contract.m_symbol,
+#                                                        contract.m_secType,
+#                                                        contract.m_exchange,
+#                                                        contract.m_currency,
+#                                                        contract.m_expiry,
+#                                                        contract.m_strike,
+#                                                        contract.m_right,
+#                                                        contract.m_conId)
+#         #logging.info(s)
+#         return s
+#       
+#     @staticmethod
+#     def makeRedisKey(contract):
+#         #print "makerediskey %s" % ContractHelper.printContract(contract)
+# #20150904        
+#         contract.m_strike = int(contract.m_strike)
+#           
+#         if contract.m_secType == 'OPT':
+#             s = '%s-%s-%s-%s' % (contract.m_symbol,
+#                                                            contract.m_expiry,
+#                                                            contract.m_strike,
+#                                                            contract.m_right)
+#         else:
+#             s = '%s-%s-%s-%s' % (contract.m_symbol,
+#                                                            contract.m_expiry,
+#                                                            contract.m_secType, '')
+#               
+#         return s
+#         
+#     @staticmethod
+#     def makeRedisKeyEx(contract, old=False):
+#         # this routine is to circumvent a problem in makeRedisKey with 
+#         # the key in position 3 having different meanings under different conditions.
+#         #  
+#         # 
+#         # to differentiate the keys generated by the old and new functions, 
+#         # contract keys created using this routine have their last slot
+#         # hard coded a magic number 102
+#           
+#         if (old):
+#             return ContractHelper.makeRedisKey(contract)
+#           
+#         contract.m_strike = int(contract.m_strike)
+# # amend 10/22 
+# # add exchange to the key         
+#         s = '%s-%s-%s-%s-%s-%s-%s-%d' % (contract.m_symbol,
+#                                                            contract.m_expiry,
+#                                                            contract.m_strike,
+#                                                            contract.m_right,
+#                                                            contract.m_secType,
+#                                                            contract.m_currency,
+#                                                              
+#                                                            contract.m_exchange,
+#                                                              
+#                                                            102)
+#         return s
+
+
+
+# copy list of subscribed contracts from the console in options_data
+# save them into a text file
+# call this function to generate subscription txt that 
+# can be processed by ib_mds.py
+# def create_subscription_file(src, dest):
+#     
+#     # improper file content will cause
+#     # this function to fail
+#     f = open(src)
+#     lns = f.readlines()
+# 
+#     a= filter(lambda x: x[0] <> '\n', map(lambda x: x.split(','), lns))
+#     contracts = map(lambda x: x.split('-'), [c[1] for c in a])
+#     options = filter(lambda x: x[2] <> 'FUT', contracts)
+#     futures = filter(lambda x: x[2] == 'FUT', contracts)
+#     print contracts
+#     #HSI,FUT,HKFE,HKD,20151029,0,
+#     futm= map(lambda x: "%s,%s,%s,%s,%s,%s,%s" % (x[0], 'FUT', 'HKFE', 'HKD', x[1], '0', ''), futures)
+#     outm= map(lambda x: "%s,%s,%s,%s,%s,%s,%s" % (x[0], 'OPT', 'HKFE', 'HKD', x[1], x[2], x[3]), options)
+#     f1 = open(dest, 'w')
+#     f1.write(''.join('%s\n'% c for c in outm))
+#     f1.write(''.join('%s\n'% c for c in futm))
+#     f1.close()
+
+def create_subscription_file(dest):
+    
+    # improper file content will cause
+    # this function to fail
+    
+    lns = DataMap().printMapCSV().split('\n')
+
+    a= filter(lambda x: x[0] <> '', map(lambda x: x.split(','), lns))
+    contracts = map(lambda x: x.split('-'), [c[1] for c in a])
+    options = filter(lambda x: x[2] <> 'FUT', contracts)
+    futures = filter(lambda x: x[2] == 'FUT', contracts)
+    print contracts
+    #HSI,FUT,HKFE,HKD,20151029,0,
+    futm= map(lambda x: "%s,%s,%s,%s,%s,%s,%s" % (x[0], 'FUT', 'HKFE', 'HKD', x[1], '0', ''), futures)
+    outm= map(lambda x: "%s,%s,%s,%s,%s,%s,%s" % (x[0], 'OPT', 'HKFE', 'HKD', x[1], x[2], x[3]), options)
+    f1 = open(dest, 'w')
+    f1.write(''.join('%s\n'% c for c in outm))
+    f1.write(''.join('%s\n'% c for c in futm))
+    f1.close()
+
+
+
+def console(config, omd):
+#    undly_months_prices = eval(config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+# 20160612
+    undly_months_prices = DataMap().rskeys['option.underlying.month_price']
+    
+    done = False
+    while not done:
+#        try:
+        print "Available commands are: l - list all subscribed contracts, i - force recal impl vol, s <H/M> <X> - H for HSI M for MHI manual subscription of HKFE options"
+        print "                        p - print portfolio summary,   r - rescan portfolio and update subscription list"
+        print "                        c [dest] - dump subscribed contracts to an external file"
+        print "                       <id> - list subscribed contract by id, q - terminate program"
+        cmd = raw_input(">>")
+        input = cmd.split(' ')
+        if input[0]  == "q":
+            done = True
+        elif input[0] == 'l' or input[0] == '':
+            #DataMap().printMap()
+            DataMap().printMapCSV()
+            DataMap().createOptionsPanelData()
+        elif input[0] == 'i':           
+            DataMap().createImplVolChartData()
+        elif input[0] == 's':
+            symbol = 'HSI' if input[1].upper() == 'H' else 'MHI'
+            print symbol
+            X = int(input[2])
+            contractTuple = (symbol, 'FUT', 'HKFE', 'HKD', '', 0, '')
+            #add_subscription(self, contractTuple, month, X, right):
+            rc = map(lambda x: (omd.add_subscription(x[0], x[1], x[2], x[3])),\
+                                 [(contractTuple, m[0], X, 'P') for m in undly_months_prices] +\
+                                 [(contractTuple, m[0], X, 'C') for m in undly_months_prices])
+            print 'subscribed items: %s' % rc
+        elif input[0] == 'p':
+#             port_key  = '%s_%s' % (config.get("redis", "redis.datastore.key.port_prefix").strip('"').strip("'"), \
+#                             config.get("redis", "redis.datastore.key.port_summary").strip('"').strip("'"))
+            port_key  = config.get("redis", "redis.datastore.key.port_summary").strip('"').strip("'")
+
+            print 'position summary'
+
+            print '\n'.join('%s:\t\t%s' % (k,v) for k,v in sorted(json.loads(DataMap.rs.get(port_key)).iteritems()))
+            print '-----end position summary\nl'
+        elif input[0] == 'r':
+            add_portfolio_subscription(config, omd)
+        elif input[0] == 'c':
+            create_subscription_file(input[1])
+            
+        elif isinstance(input[0], int): 
+            DataMap().printContractByID(input[0])
+        else:
+            pass
+            
+#         except:
+#             exc_type, exc_value, exc_traceback = sys.exc_info()
+#             traceback.print_tb(exc_traceback, limit=1, file=sys.stdout)
+
+
+def add_portfolio_subscription(config, omd):
+    logging.info('add_portfolio_subscription: subscribe market data for portfolio items')
+    p = portfolio.PortfolioManager(config)
+    p.retrieve_position()
+    
+    
+#    undly_months_prices = eval(config.get("market", "option.underlying.month_price").strip('"').strip("'"))
+    # 20160612
+    undly_months_prices = DataMap().rskeys['option.underlying.month_price']
+    
+    # example: MHI-20150929-22600-P
+    toks = map(lambda x: x.split('-'), p.get_pos_contracts())
+    
+    #[['MHI','20150929', 22600, 'P'], [...]]
+    for t in toks:
+        contractTuple = (t[0], 'FUT', 'HKFE', 'HKD', '', 0, '')
+        
+        #add_subscription(self, contractTuple, month, X, right):
+        rc = map(lambda x: (omd.add_subscription(x[0], x[1], x[2], x[3])),\
+                             [(contractTuple, m[0], t[2], 'P') for m in undly_months_prices] +\
+                             [(contractTuple, m[0], t[2], 'C') for m in undly_months_prices])        
+#         rc = map(lambda x: (omd.add_subscription(x[0], x[1], x[2], x[3])),\
+#                         [((tok[0], 'OPT', 'HKFE', 'HKD', '', 0, ''), m[0], tok[2], tok[3]) for tok in toks])
+                             
+    logging.info('add_portfolio_subscription: add results: %s' % rc)
+
+
+
+if __name__ == '__main__':
+        
+    if len(sys.argv) != 2:
+        print("Usage: %s <config file>" % sys.argv[0])
+        exit(-1)    
+
+    cfg_path= sys.argv[1:]    
+    config = ConfigParser.SafeConfigParser()
+    if len(config.read(cfg_path)) == 0: 
+        raise ValueError, "Failed to open config file" 
+      
+    logconfig = eval(config.get("options_data", "options_data.logconfig").strip('"').strip("'"))
+    logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'    
+    logging.basicConfig(**logconfig)        
+    
+    DataMap().init_redis(config)
+    mdm = MarketDataManager(config)  
+    mdm.connect()
+    omd = OptionsMarketDataManager(config, mdm)
+#     contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', '', 0, '')
+#     o.option_range(contractTuple, [['20150828', 22817.0], ['20150929', 22715.0]], 200, 0.08)
+
+    add_portfolio_subscription(config, omd)
+
+    thread.start_new_thread(DataMap().refreshRedisImplVol, (60,))
+#    thread.start_new_thread(DataMap().refresh_portfolio, (5,))
+
+    thread.start_new_thread(DataMap().refresh_portfolio, (5, ))
+    console(config, omd)
+  
+
+       
+    mdm.disconnect()
+    
+         
+    

+ 601 - 0
finopt/portfolio (copy).py

@@ -0,0 +1,601 @@
+# -*- coding: utf-8 -*-
+
+import sys, traceback
+import json
+import logging
+import thread, threading
+from threading import Lock
+import ConfigParser
+from ib.ext.Contract import Contract
+from ib.opt import ibConnection, message
+from time import sleep
+import time, datetime
+import optcal
+import opt_serve
+import cherrypy
+import redis
+from comms.epc import EPCPub
+
+
+
+#from options_data import ContractHelper
+import options_data
+# Tick Value      Description
+# 5001            impl vol
+# 5002            delta
+# 5003            gamma
+# 5004            theta
+# 5005            vega
+# 5006            premium
+
+# 6001            avgCost
+# 6002            pos
+# 6003            totCost
+# 6004            avgPx
+# 6005            pos delta
+# 6006            pos theta
+# 6007            multiplier
+# 6020            pos value impact +1% vol change
+# 6021            pos value impact -1% vol change
+
+
+# IB tick types code reference
+# https://www.interactivebrokers.com/en/software/api/api.htm
+                    
+
+class PortfolioManager():
+    
+    config = {}
+    con = None
+    r_conn = None
+    port = []
+    grouped_options = None
+    
+    # item 0 is for position, item 1 is for account
+    download_states = [False, False]
+    quit = False
+    interested_types = ['OPT', 'FUT']
+    rs_port_keys = {}
+    ib_port_msg = []
+    tlock = None
+    epc = None
+    account_tags = []
+    ib_acct_msg = {}
+    
+    def __init__(self, config):
+        self.config = config
+
+        host = config.get("market", "ib.gateway").strip('"').strip("'")
+        port = int(config.get("market", "ib.port"))
+        appid = int(config.get("market", "ib.appid.portfolio"))   
+        self.rs_port_keys['port_conid_set'] = config.get("redis", "redis.datastore.key.port_conid_set").strip('"').strip("'")
+        self.rs_port_keys['port_prefix'] = config.get("redis", "redis.datastore.key.port_prefix").strip('"').strip("'")        
+        self.rs_port_keys['port_summary'] = config.get("redis", "redis.datastore.key.port_summary").strip('"').strip("'")
+        self.rs_port_keys['port_items'] = config.get("redis", "redis.datastore.key.port_items").strip('"').strip("'")
+        
+        
+        self.rs_port_keys['acct_summary'] = config.get("redis", "redis.datastore.key.acct_summary").strip('"').strip("'") 
+        self.account_tags = eval(config.get("portfolio", "portfolio.account_summary_tags").strip('"').strip("'"))
+        
+        self.epc = eval(config.get("portfolio", "portfolio.epc").strip('"').strip("'"))
+        # instantiate a epc object if the config says so
+        
+        
+        if self.epc['stream_to_Kafka']:
+            self.epc['epc'] = EPCPub(config) 
+        
+        r_host = config.get("redis", "redis.server").strip('"').strip("'")
+        r_port = config.get("redis", "redis.port")
+        r_db = config.get("redis", "redis.db")             
+        
+        self.r_conn = redis.Redis(r_host, r_port, r_db)
+        
+        self.con = ibConnection(host, port, appid)
+        self.con.registerAll(self.on_ib_message)
+        
+        self.download_states = [False, False]
+        
+        self.tlock = Lock()
+    
+    def retrieve_position(self):
+        self.connect()
+        
+        # clear previous saved values
+        self.port = []
+        self.ib_port_msg = []
+        self.clear_redis_portfolio()
+        
+        self.subscribe()
+        while not self.quit:
+            if self.download_states[0] == True and self.download_states[1] == True:
+                self.disconnect()
+                self.quit = True
+                
+    def clear_redis_portfolio(self):
+        l = map(lambda x: (self.r_conn.delete(x)), self.r_conn.keys(pattern='%s*' % (self.rs_port_keys['port_prefix'])))
+        #self.r_set(self.rs_port_keys['port_summary'], '{"Portfolio Retrieval": "Calculation in progress...Try again later!" }')
+        self.r_conn.set(self.rs_port_keys['port_summary'], '{"Portfolio Retrieval": "Calculation in progress...Try again later!" }')
+
+        logging.debug('clear_redis_portfolio: num items cleared: %d'% len(l))
+        
+    
+    def subscribe(self):
+
+            self.con.reqPositions()
+            logging.debug('account info to retrieve: %s' % (''.join('%s,' % s for s in self.account_tags)))
+            self.con.reqAccountSummary(100, 'All', ''.join('%s,' % s for s in self.account_tags))
+
+            #self.con.register(self.on_ib_message, 'UpdateAccountValue')
+
+    def on_ib_message(self, msg):
+        
+        #print msg.typeName, msg
+        
+        if msg.typeName in "position":
+            if self.download_states[0] == False:
+                logging.debug("%s" %  (options_data.ContractHelper.printContract(msg.contract)))
+                if msg.contract.m_secType in self.interested_types:
+                    logging.debug("PortfolioManager: getting position...%s" % msg)
+                    self.ib_port_msg.append(msg)
+                    #self.construct_port(msg)
+            
+        if msg.typeName == 'positionEnd':
+            
+
+            for pm in self.ib_port_msg:
+                self.construct_port(pm)
+                
+            self.recal_port()
+            self.group_pos_by_strike()
+            logging.debug("PortfolioManager: Complete position download. Disconnecting...")
+            
+
+            self.download_states[0] = True
+            
+        if msg.typeName == "accountSummary":
+            if self.download_states[1] == False:
+                
+                self.ib_acct_msg[msg.tag] = (msg.value, msg.currency, msg.account)
+                logging.debug("PortfolioManager: getting account info...%s" % msg)
+            
+        if msg.typeName == 'accountSummaryEnd':
+            
+            self.ib_acct_msg['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S') 
+            logging.info("-------------- ACCOUNT SUMMARY [%s]" % (self.ib_acct_msg['AccountType'][2]))
+            logging.info('\n\n' + ''.join('%30s: %22s\n' % (k, ''.join('%10s %12s' % (v[0], v[1] if v[1] else ''))) for k,v in self.ib_acct_msg.iteritems()))
+            
+            self.r_conn.set(self.rs_port_keys['acct_summary'], json.dumps(self.ib_acct_msg))
+            if self.epc['epc']:
+                try:
+
+                    self.epc['epc'].post_account_summary(self.ib_acct_msg)
+
+                except:
+                    logging.exception("Exception in function when trying to broadcast account summary message to epc")
+            
+            self.download_states[1] = True
+        
+            
+            
+            
+    def group_pos_by_strike(self):
+
+
+        # split into lines of position       
+        m = map(lambda x: x.split(','), self.port)
+        # transform each line into two elements. first one is a key created 
+        # by combining right and strike, the second is the product of 
+        # position * conversion ratio (HSI=50, MSI=10)
+        n = map(lambda x: (x[3] + x[4], float(x[5]) * float(x[6])/50), m)
+        
+        #p = dict(set(map(lambda x:(x[0], 0), n)))
+        
+        def sumByKey(f, n):
+            # filter the list with only unique keys
+            # transform the list into a dict
+            p = dict(set(map(f, n)))
+            # add the numbers together on key
+            l =[]
+            for x in n:
+                p[x[0]] += x[1]
+                
+            return [(k[1:], k[0:1],v) for k,v in p.iteritems()]
+         
+         
+           
+        #print len(n),n
+        # initialize a list of strikes and the position sum to 0
+        # pass the list to sumByKey routine
+        self.grouped_options = sumByKey(lambda x:(x[0], 0), n)
+        #print len(l), sorted(l)
+        
+    def group_pos_by_right(self):        
+    # group by put, call (summing up all contracts by right type,ignoring strikes)
+        # split into lines of position       
+        m = map(lambda x: x.split(','), self.port)
+        q = map(lambda x: (x[3], float(x[5]) * float(x[6])/50), m)
+        u = dict(set(map(lambda x:(x[0], 0.0), q)))
+#        print u, q
+        for x in q:
+            u[x[0]] += x[1] 
+        return [(a,b) for a,b in u.iteritems()]
+    
+    def get_grouped_options_str_array(self):
+        s = ''
+        for e in sorted(self.grouped_options):
+            s += "[%f,%s,%s]," % (float(e[0])/100.0, e[2] if e[1] == 'P' else 0, e[2] if e[1] == 'C' else 0)
+        return s
+    
+    
+    def get_traded_months(self):
+        
+        li = []
+        for l in self.port:
+            toks=  l.split(',')
+            li.append(toks[2])
+        #print sorted(list(set(li)))
+        return sorted(list(set(li)))
+    
+#     def get_tbl_pos_csv(self):
+#         s_cols = [0,1,2,3,4]
+#         i_cols = [5,6,7]
+#         s = '["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],'
+#         
+#         for l in sorted(self.port):
+#             content = ''    
+#             toks= l.split(',')
+#  #           print toks
+#             for i in s_cols:
+#                 content += '"%s",' % toks[i]
+#             for i in i_cols:
+#                 content += '%s,' % toks[i]
+#             
+#                 
+#             s += "[%s]," % content
+#         return s       
+
+
+    def get_greeks(self, c_key):
+        #print c_key, len(c_key), self.r_conn.get('a')
+        
+        gdata = self.r_conn.get(c_key)
+        
+        #print gdata
+        gmap = json.loads(gdata) if gdata <> None else None
+        return gmap
+
+
+    def get_pos_contracts(self):
+        s_cols = [0,2,4,3]
+        cs = []
+        for l in sorted(self.port):
+            content = ''    
+            toks= l.split(',')
+            c_key = '-'.join('%s' % toks[i] for i in s_cols)
+            cs.append(c_key)
+        return cs
+
+    def get_tbl_pos_csv(self):
+
+        # WARNING: this routine will crash if a new instrument is traded during the day
+        # but it is not previously been subscribed 
+        # the gmap will be missing from redis
+        # the workaround is to restart options_data.py 
+        # to refresh all positions and add back any
+        # new instruments to its subscription list 
+
+        pall = set(self.r_conn.keys(pattern='%s*' % self.rs_port_keys['port_prefix']))
+        s = '["symbol","right","avgcost","spotpx","pos","delta","theta","pos_delta","pos_theta","unreal_pl","last_updated"],'
+        
+        def split_toks(x):
+	
+            try:
+		pmap = json.loads(self.r_conn.get(x))
+            	#print pmap
+            	gmap = json.loads(self.r_conn.get(x[3:]))
+            	#print gmap
+            	s = '["%s","%s",%f,%f,%f,%f,%f,%f,%f,%f,"%s"],' % (x[3:], x[len(x)-1:], pmap['6001'], gmap['5006'], pmap['6002'],\
+                                                                         gmap['5002'],gmap['5004'],\
+                                                                         pmap['6005'],pmap['6006'],pmap['6008'],pmap['last_updated'])
+	    except:
+		return ''
+
+            return s                                                          
+            
+        end_s = s + ''.join (split_toks( x ) for x in pall)
+        return end_s 
+
+
+    def get_tbl_pos_csv_old(self, calGreeks=False):
+        s_cols = [0,1,2,3,4]
+        i_cols = [5,6,7]
+        s = '["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],'
+        
+        for l in sorted(self.port):
+            content = ''    
+            toks= l.split(',')
+ #           print toks
+            for i in s_cols:
+                content += '"%s",' % toks[i]
+            for i in i_cols:
+                content += '%s,' % toks[i]
+            
+                
+            s += "[%s]," % content
+        return s  
+
+    def get_portfolio_summary(self):
+
+        
+        return json.loads(self.r_conn.get(self.rs_port_keys['port_summary']))
+    
+
+    def recal_port(self):
+        # http://stackoverflow.com/questions/448034/multithreaded-resource-access-where-do-i-put-my-locks
+        
+        # a wrapper just to ensure only one thread is doing the portfolio recalculation
+        logging.debug('recal_port: acquiring lock...%s' % threading.currentThread())
+        self.tlock.acquire()
+        try:
+            s = self.recal_port_rentrant_unsafe()
+        finally:
+            logging.debug('recal_port: completed recal. releasing lock...')
+            self.tlock.release()  
+            
+        return s
+
+    def recal_port_rentrant_unsafe(self):
+        
+        
+        logging.debug('PortfolioManager: recal_port')
+
+        # retrieve the portfolio entries from redis, strip the prefix in front
+        plines = map(lambda k: (k.replace(self.rs_port_keys['port_prefix'] + '_', '')),\
+                                 self.r_conn.keys(pattern=('%s*'% self.rs_port_keys['port_prefix'])))
+                                 
+        logging.info ("PortfolioManager: recal_port-> gathering position entries from redis %s" % plines)
+        
+        pos_summary = {'delta_c': 0.0, 'delta_p': 0.0, 'delta_all': 0.0,\
+                       'theta_c': 0.0, 'theta_p': 0.0, 'theta_all': 0.0,\
+                       'delta_1percent' : 0.0, 'theta_1percent' : 0.0,\
+                       'iv_plus1p': 0.0, 'iv_minus1p': 0.0, 'unreal_pl': 0.0}
+        l_gmap = []
+        l_skipped_pos =[]       
+        t_pos_multiplier = 0.0
+        
+        for ckey in plines:
+        
+            gmap = self.get_greeks(ckey)
+            logging.debug('PortfolioManager: recal_port greeks market data %s->%s ' % (ckey, gmap))
+            logging.debug('PortfolioManager: recal_port position-map->%s' % (self.r_get(ckey)))
+            pmap = json.loads(self.r_get(ckey)) 
+            
+            
+            
+            if gmap:
+            
+            # Tick Value      Description
+            # 5001            impl vol
+            # 5002            delta
+            # 5003            gamma
+            # 5004            theta
+            # 5005            vega
+            # 5006            premium        
+            # 6001            avgCost
+            # 6002            pos
+            # 6003            totCost
+            # 6004            avgPx
+            # 6005            pos delta
+            # 6006            pos theta
+            # 6007            multiplier
+            # 6009            curr_port_value
+            # 6008            unreal_pl
+            # 6020            pos value impact +1% vol change
+            # 6021            pos value impact -1% vol change
+            
+           
+                def pos_delta():                 
+                    pd = pmap['6002'] * gmap['5002'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_delta: %f' % pd)
+                    return pd
+                
+                def pos_theta():
+                    pd = pmap['6002'] * gmap['5004'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_theta: %f' % pd)
+                    return pd
+ 
+                def pos_avg_px():
+                    pd = pmap['6001'] / pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_avg_px: %f' % pd)
+                    return pd                   
+                    
+                def pos_tot_cost():
+                    pd = pmap['6001'] * pmap['6002'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_tot_cost: %f' % pd)
+                    return pd                   
+                
+                def pos_unreal_pl():
+                    #(spot premium * multiplier - avgcost) * pos) 
+                    v = (gmap['5006'] * pmap['6007'] - pmap['6001']) * pmap['6002'] 
+                    return v 
+    
+                
+    
+                #logging.debug('PortfolioManager: recal_port greeks %f' % pos_delta(gmap))
+                
+                pmap['6005'] = pos_delta()
+                pmap['6006'] = pos_theta()
+                pmap['6004'] = pos_avg_px()
+                pmap['6003'] = pos_tot_cost()
+                pmap['6008'] = pos_unreal_pl()
+                pmap['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
+                
+                l_gmap.append(pmap)          
+
+                #t_pos_multiplier += (pmap['6002'] * pmap['6007'])
+
+                right = ckey.split('-')[3].lower()
+                pos_summary['delta_' + right] += pmap['6005']
+                pos_summary['delta_all'] += pmap['6005']
+                pos_summary['theta_' + right] += pmap['6006']
+                pos_summary['theta_all'] += pmap['6006']
+                pos_summary['unreal_pl'] += pmap['6008']
+
+                
+                
+                #pos_summary['delta_1percent'] += (pos_summary['delta_all'] / (pmap['6002'] * pmap['6007']))
+                # delta_1% = pos_ / (pos * multiplier)
+                
+                
+                #print 'con,right,avgcost,spot px,pos,delta,theta, pos_delta,pos_theta,pos_unreal_pl,last_updated'
+#                 print ( 'PT_entries: %s,%s,%f,%f,%f,%f,%f,%f,%f,%f,%s' % (ckey, right, pmap['6001'], gmap['5006'], pmap['6002'],\
+#                                                                      gmap['5002'],gmap['5004'],\
+#                                                                      pmap['6005'],pmap['6006'],pmap['6008'],pmap['last_updated']
+#                                                                      ))
+#20150911                
+                #print pmap
+                #print json.dumps(pmap)
+                self.r_set(ckey, json.dumps(pmap))
+                
+                logging.debug('PortfolioManager: update position in redis %s' % self.r_get(ckey))
+                
+            else:
+                l_skipped_pos.append(ckey)
+
+            
+#            self.r_set(ckey, json.dumps(pmap))
+#            logging.debug('PortfolioManager: update position in redis %s' % self.r_get(ckey))
+
+ 
+        #pos_summary['delta_1percent'] = (pos_summary['delta_all'] / t_pos_multiplier)
+        
+        
+
+ 
+        if len(l_skipped_pos) > 0:
+            logging.warn('***************** PortfolioManager: recal_port. SOME POSITIONS WERE NOT PROCESSED!')
+            logging.warn('----------------- DO NOT rely on the numbers in the Summary dictionary (pos_summary)')
+            logging.warn('----------------- Please check your portfolio through other means or subscribe missing')
+            logging.warn('----------------- market data in options_serve.py console. ')
+        logging.info('-------------- POSITION SUMMARY')
+        pos_summary['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S')    
+        pos_summary['entries_skipped'] = l_skipped_pos
+        pos_summary['status'] = 'OK' if len(l_skipped_pos) == 0 else 'NOT_OK'
+        #self.r_set(self.rs_port_keys['port_summary'], json.dumps(pos_summary) )
+        t_pos_summary = json.dumps(pos_summary)
+        self.r_conn.set(self.rs_port_keys['port_summary'], t_pos_summary )
+        self.r_conn.set(self.rs_port_keys['port_items'], json.dumps(l_gmap))
+        #print pos_summary
+        #print l_gmap      
+        # broadcast 
+        if self.epc['epc']:
+            try:
+                self.epc['epc'].post_portfolio_summary(pos_summary)
+                self.epc['epc'].post_portfolio_items(l_gmap)
+            except:
+                logging.exception("Exception in function: recal_port_rentrant_unsafe")
+
+        #logging.info(pos_summary)
+        
+        logging.warn('-------------- Entries for which the greeks are not computed!! %s' %\
+                        ','.join(' %s' % k for k in l_skipped_pos))
+        
+        return l_gmap
+ 
+
+    def construct_port(self, pos_msg):
+        # port structure
+        #
+        # exch,type,contract_mth,right, strike,con_ration,pos,avgcost
+        # 
+        # Tick Value      Description
+        # 5001            impl vol
+        # 5002            delta
+        # 5003            gamma
+        # 5004            theta
+        # 5005            vega
+        # 5005            premium
+        
+        # 6001            avgCost
+        # 6002            pos
+    
+        
+        toks = options_data.ContractHelper.printContract(pos_msg.contract).split('-')
+        s = ''
+        
+        slots = [0, 1, 4, 6]
+        s = s + '%s,%s,%s' % (','.join(toks[i] for i in slots), toks[5].replace('.0', ''), '50.0' if toks[0][1:] == 'HSI' else '10.0')
+        s = s.replace('[', '') + ",%0.4f,%0.4f" % (pos_msg.pos, pos_msg.avgCost)
+        
+        
+        self.port.append(s)
+                
+        ckey = options_data.ContractHelper.makeRedisKey(pos_msg.contract)
+        multiplier = 50.0 if toks[0][1:] == 'HSI' else 10.0
+
+        
+        
+        self.r_set(ckey, json.dumps({"contract": ckey, "6002": pos_msg.pos, "6001": pos_msg.avgCost, "6007": multiplier}))
+        #self.recal_port(ckey)
+ 
+    #the wrapper functions add a prefix session id to every key
+    #
+    def r_set(self, key, val):
+        return self.r_conn.set("%s_%s" % (self.rs_port_keys['port_prefix'], key), val)
+        
+    
+    def r_get(self, key):
+        return self.r_conn.get("%s_%s" % (self.rs_port_keys['port_prefix'], key))
+    
+    
+    def r_sadd(self, key, val):
+        return self.r_conn.sadd("%s_%s" % (self.rs_port_keys['port_prefix'], key), val)
+    
+    def r_sismember(self, set, val):
+        return self.r_conn.sismember("%s_%s" % (self.rs_port_keys['port_prefix'], set), val)
+
+    def r_smembers(self, key):
+        return self.r_conn.smembers("%s_%s" % (self.rs_port_keys['port_prefix'], key))
+       
+
+    def connect(self):
+        self.con.connect()
+
+        
+    def disconnect(self):
+
+        self.con.disconnect()
+        self.quit = True
+
+if __name__ == '__main__':
+           
+    if len(sys.argv) != 2:
+        print("Usage: %s <config file>" % sys.argv[0])
+        exit(-1)    
+
+    cfg_path= sys.argv[1:]    
+    config = ConfigParser.SafeConfigParser()
+    if len(config.read(cfg_path)) == 0:      
+        raise ValueError, "Failed to open config file" 
+    
+    logconfig = eval(config.get("portfolio", "portfolio.logconfig").strip('"').strip("'"))
+    logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'    
+    logging.basicConfig(**logconfig)        
+
+    p = PortfolioManager(config)
+    p.retrieve_position()
+    print p.get_portfolio_summary()
+    print p.get_tbl_pos_csv()
+    print p.get_pos_contracts()
+
+
+    # sample ouput    
+# ["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],["HSI","OPT","20150828","C","22600",50.0,0.0000,0.0000,],["HSI","OPT","20150828","C","23000",50.0,-1.0000,1770.0000,],["HSI","OPT","20150828","C","23600",50.0,-2.0000,1470.0000,],["HSI","OPT","20150828","C","23800",50.0,-1.0000,920.0000,],["HSI","OPT","20150828","C","24000",50.0,-2.0000,1820.0000,],["HSI","OPT","20150828","C","24200",50.0,-1.0000,3120.0000,],["HSI","OPT","20150828","C","24800",50.0,-1.0000,220.0000,],["HSI","OPT","20150828","P","18000",50.0,-2.0000,1045.0000,],["HSI","OPT","20150828","P","18600",50.0,-1.0000,1120.0000,],["HSI","OPT","20150828","P","18800",50.0,-1.0000,1570.0000,],["HSI","OPT","20150828","P","19800",50.0,-1.0000,870.0000,],["HSI","OPT","20150828","P","20200",50.0,-1.0000,970.0000,],["HSI","OPT","20150828","P","20800",50.0,-2.0000,970.0000,],["HSI","OPT","20150828","P","21600",50.0,-1.0000,1570.0000,],["HSI","OPT","20150828","P","21800",50.0,-7.0000,1955.7143,],["HSI","OPT","20150828","P","23200",50.0,1.0000,25930.0000,],["HSI","OPT","20150929","C","24400",50.0,1.0000,24880.0000,],["HSI","OPT","20150929","P","21600",50.0,0.0000,0.0000,],["HSI","OPT","20150929","P","21800",50.0,2.0000,52713.3333,],["HSI","OPT","20150929","P","22600",50.0,3.0000,39763.3333,],["MHI","OPT","20150828","C","24400",10.0,-1.0000,2603.0000,],["MHI","OPT","20150828","P","20800",10.0,-1.0000,313.0000,],["MHI","OPT","20150828","P","21000",10.0,-1.0000,363.0000,],["MHI","OPT","20150828","P","23600",10.0,5.0000,4285.0000,],["MHI","OPT","20150929","C","24400",10.0,1.0000,4947.0000,],["MHI","OPT","20150929","P","21600",10.0,1.0000,12657.0000,],["MHI","OPT","20150929","P","22600",10.0,1.0000,9877.0000,],["MHI","OPT","20150929","P","23600",10.0,4.0000,7757.0000,],
+# [180.000000,-2.0,0],[186.000000,-1.0,0],[188.000000,-1.0,0],[198.000000,-1.0,0],[202.000000,-1.0,0],[208.000000,-2.2,0],[210.000000,-0.2,0],[216.000000,-0.8,0],[218.000000,-5.0,0],[226.000000,0,0.0],[226.000000,3.2,0],[230.000000,0,-1.0],[232.000000,1.0,0],[236.000000,0,-2.0],[236.000000,1.8,0],[238.000000,0,-1.0],[240.000000,0,-2.0],[242.000000,0,-1.0],[244.000000,0,1.0],[248.000000,0,-1.0],
+# {'P': 0.0, 'C': 0.0} [('P', 0.8), ('P', 0.0), ('C', -2.0), ('C', -2.0), ('P', -1.0), ('C', 0.2), ('P', -1.0), ('P', -0.2), ('C', 0.0), ('P', -0.2), ('C', -1.0), ('P', -1.0), ('C', -1.0), ('P', 1.0), ('P', 0.2), ('P', 2.0), ('C', 1.0), ('P', -2.0), ('P', -7.0), ('P', 1.0), ('P', -1.0), ('P', -1.0), ('P', 0.2), ('C', -1.0), ('P', 3.0), ('C', -1.0), ('C', -0.2), ('P', -2.0)]
+# [('P', -8.200000000000001), ('C', -7.0)]
+
+    
+         
+    
+

+ 686 - 0
finopt/portfolio.py.bak20170117

@@ -0,0 +1,686 @@
+# -*- coding: utf-8 -*-
+
+import sys, traceback
+import json
+import logging
+import thread, threading
+from threading import Lock
+import ConfigParser
+from ib.ext.Contract import Contract
+from ib.opt import ibConnection#, message
+from time import sleep
+import time, datetime
+import optcal
+import opt_serve
+import cherrypy
+import redis
+from comms.epc import EPCPub
+
+
+
+#from options_data import ContractHelper
+import options_data
+# Tick Value      Description
+# 5001            impl vol
+# 5002            delta
+# 5003            gamma
+# 5004            theta
+# 5005            vega
+# 5006            premium
+
+# 6001            avgCost
+# 6002            pos
+# 6003            totCost
+# 6004            avgPx
+# 6005            pos delta
+# 6006            pos theta
+# 6007            multiplier
+# 6020            pos value impact +1% vol change
+# 6021            pos value impact -1% vol change
+
+
+# IB tick types code reference
+# https://www.interactivebrokers.com/en/software/api/api.htm
+                    
+
+class PortfolioManager():
+    
+    config = {}
+    con = None
+    r_conn = None
+    port = []
+    grouped_options = None
+    
+    # item 0 is for position, item 1 is for account
+    download_states = None
+    quit = False
+    interested_types = ['OPT', 'FUT']
+    rs_port_keys = {}
+    ib_port_msg = []
+    tlock = None
+    epc = None
+    account_tags = []
+    ib_acct_msg = {}
+    
+    def __init__(self, config):
+        self.config = config
+
+        host = config.get("market", "ib.gateway").strip('"').strip("'")
+        port = int(config.get("market", "ib.port"))
+        appid = int(config.get("market", "ib.appid.portfolio"))   
+        self.rs_port_keys['port_conid_set'] = config.get("redis", "redis.datastore.key.port_conid_set").strip('"').strip("'")
+        self.rs_port_keys['port_prefix'] = config.get("redis", "redis.datastore.key.port_prefix").strip('"').strip("'")        
+        self.rs_port_keys['port_summary'] = config.get("redis", "redis.datastore.key.port_summary").strip('"').strip("'")
+        self.rs_port_keys['port_items'] = config.get("redis", "redis.datastore.key.port_items").strip('"').strip("'")
+        
+        
+        self.rs_port_keys['acct_summary'] = config.get("redis", "redis.datastore.key.acct_summary").strip('"').strip("'") 
+        self.account_tags = eval(config.get("portfolio", "portfolio.account_summary_tags").strip('"').strip("'"))
+        
+        self.epc = eval(config.get("portfolio", "portfolio.epc").strip('"').strip("'"))
+        # instantiate a epc object if the config says so
+        
+        
+        if self.epc['stream_to_Kafka']:
+            self.epc['epc'] = EPCPub(config) 
+        
+        r_host = config.get("redis", "redis.server").strip('"').strip("'")
+        r_port = config.get("redis", "redis.port")
+        r_db = config.get("redis", "redis.db")             
+        
+        self.r_conn = redis.Redis(r_host, r_port, r_db)
+        
+        self.con = ibConnection(host, port, appid)
+        self.con.registerAll(self.on_ib_message)
+        
+        
+    	self.download_states = [False, False]
+        self.tlock = Lock()
+    
+    def retrieve_position(self):
+        self.connect()
+        
+        # clear previous saved values
+        self.port = []
+        self.ib_port_msg = []
+        self.clear_redis_portfolio()
+        
+        self.subscribe()
+        while not self.quit:
+            if self.download_states[0] == True and self.download_states[1] == True:
+                self.disconnect()
+                self.quit = True
+                
+    def clear_redis_portfolio(self):
+        l = map(lambda x: (self.r_conn.delete(x)), self.r_conn.keys(pattern='%s*' % (self.rs_port_keys['port_prefix'])))
+        #self.r_set(self.rs_port_keys['port_summary'], '{"Portfolio Retrieval": "Calculation in progress...Try again later!" }')
+        self.r_conn.set(self.rs_port_keys['port_summary'], '{"Portfolio Retrieval": "Calculation in progress...Try again later!" }')
+
+        logging.debug('clear_redis_portfolio: num items cleared: %d'% len(l))
+        
+    
+    def subscribe(self):
+
+            self.con.reqPositions()
+            logging.debug('account info to retrieve: %s' % (''.join('%s,' % s for s in self.account_tags)))
+            self.con.reqAccountSummary(100, 'All', ''.join('%s,' % s for s in self.account_tags))
+
+            #self.con.register(self.on_ib_message, 'UpdateAccountValue')
+
+    def on_ib_message(self, msg):
+        
+        #print msg.typeName, msg
+        
+        if msg.typeName in "position":
+            if self.download_states[0] == False:
+                logging.debug("%s" %  (options_data.ContractHelper.printContract(msg.contract)))
+                if msg.contract.m_secType in self.interested_types:
+                    logging.debug("PortfolioManager: getting position...%s" % msg)
+                    self.ib_port_msg.append(msg)
+                    #self.construct_port(msg)
+            
+        if msg.typeName == 'positionEnd':
+            
+
+            for pm in self.ib_port_msg:
+                self.construct_port(pm)
+                
+            self.recal_port()
+            self.group_pos_by_strike()
+            logging.debug("PortfolioManager: Complete position download. Disconnecting...")
+            
+
+            self.download_states[0] = True
+            
+        if msg.typeName == "accountSummary":
+            if self.download_states[1] == False:
+                
+                self.ib_acct_msg[msg.tag] = (msg.value, msg.currency, msg.account)
+                logging.debug("PortfolioManager: getting account info...%s" % msg)
+            
+        if msg.typeName == 'accountSummaryEnd':
+            
+            self.ib_acct_msg['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S') 
+            logging.info("-------------- ACCOUNT SUMMARY [%s]" % (self.ib_acct_msg['AccountType'][2]))
+            logging.info('\n\n' + ''.join('%30s: %22s\n' % (k, ''.join('%10s %12s' % (v[0], v[1] if v[1] else ''))) for k,v in self.ib_acct_msg.iteritems()))
+            
+            self.r_conn.set(self.rs_port_keys['acct_summary'], json.dumps(self.ib_acct_msg))
+            if self.epc['epc']:
+                try:
+
+                    self.epc['epc'].post_account_summary(self.ib_acct_msg)
+
+                except:
+                    logging.exception("Exception in function when trying to broadcast account summary message to epc")
+            
+            self.download_states[1] = True
+        
+            
+            
+            
+    def group_pos_by_strike(self):
+
+
+        # split into lines of position       
+        m = map(lambda x: x.split(','), self.port)
+        # transform each line into two elements. first one is a key created 
+        # by combining right and strike, the second is the product of 
+        # position * conversion ratio (HSI=50, MSI=10)
+        n = map(lambda x: (x[3] + x[4], float(x[5]) * float(x[6])/50), m)
+        
+        #p = dict(set(map(lambda x:(x[0], 0), n)))
+        
+        def sumByKey(f, n):
+            # filter the list with only unique keys
+            # transform the list into a dict
+            p = dict(set(map(f, n)))
+            # add the numbers together on key
+            l =[]
+            for x in n:
+                p[x[0]] += x[1]
+                
+            return [(k[1:], k[0:1],v) for k,v in p.iteritems()]
+         
+         
+           
+        #print len(n),n
+        # initialize a list of strikes and the position sum to 0
+        # pass the list to sumByKey routine
+        self.grouped_options = sumByKey(lambda x:(x[0], 0), n)
+        #print len(l), sorted(l)
+
+
+    def group_pos_by_strike_by_month(self):
+
+
+        # split into lines of position       
+        m = map(lambda x: x.split(','), self.port)
+        # transform each line into two elements. first one is a key created 
+        # by combining right and strike, the second is the product of 
+        # position * conversion ratio (HSI=50, MSI=10)
+        
+        mth_set = set(map(lambda x: x[2], m))
+        print max(mth_set)
+        n = map(lambda x: (x[3] + x[4] + x[2], float(x[5]) * float(x[6])/50), m)
+        
+        #p = dict(set(map(lambda x:(x[0], 0), n)))
+        
+        def sumByKey(f, n):
+            # filter the list with only unique keys
+            # transform the list into a dict
+            p = dict(set(map(f, n)))
+            
+            # add the numbers together on key
+            l =[]
+            for x in n:
+                
+                p[x[0]] += x[1]
+                
+            return [(k[1:6], 'NEAR' if k[6:] < max(mth_set) else 'FAR', k[0:1],v) for k,v in p.iteritems()]
+         
+         
+           
+        #print len(n),n
+        # initialize a list of strikes and the position sum to 0
+        # pass the list to sumByKey routine
+        self.grouped_options = sumByKey(lambda x:(x[0],  0), n)
+        #print len(l), sorted(l)
+
+
+
+        
+    def group_pos_by_right(self):        
+    # group by put, call (summing up all contracts by right type,ignoring strikes)
+        # split into lines of position       
+        m = map(lambda x: x.split(','), self.port)
+        q = map(lambda x: (x[3], float(x[5]) * float(x[6])/50), m)
+        u = dict(set(map(lambda x:(x[0], 0.0), q)))
+#        print u, q
+        for x in q:
+            u[x[0]] += x[1] 
+        return [(a,b) for a,b in u.iteritems()]
+    
+    def get_grouped_options_str_array(self):
+        s = ''
+        for e in sorted(self.grouped_options):
+            s += "[%f,%s,%s]," % (float(e[0])/100.0, e[2] if e[1] == 'P' else 0, e[2] if e[1] == 'C' else 0)
+        return s
+ 
+ 
+    def get_grouped_options_str_array_stacked(self):
+        s = ''
+        
+        #[('20800', 'FAR', 'C', -1.0), ('17600', 'NEAR', 'P', -2.0), ('21400', 'NEAR', 'C', -4.0), ('15800', 'NEAR', 'P', -4.0), ('15600', 'FAR', 'P', -3.0), ('14600', 'FAR', 'P', -1.0), ('20400', 'NEAR', 'C', -2.0), ('15600', 'NEAR', 'P', -2.0), ('18200', 'NEAR', 'P', 0.0), ('16000', 'NEAR', 'P', -3.0), ('15000', 'FAR', 'P', -1.0), ('18800', 'FAR', 'P', 3.0), ('21200', 'FAR', 'C', -2.0), ('20800', 'NEAR', 'C', -4.0), ('18200', 'FAR', 'P', 1.0), ('17800', 'NEAR', 'P', -8.0), ('18600', 'NEAR', 'P', -1.0)]
+        for e in sorted(self.grouped_options):
+            s += "[%s,%s,%s,%s,%s]," % (e[0], e[3] if e[1] == 'NEAR' and e[2] =='P' else 0, 
+                                        e[3] if e[1] == 'NEAR' and e[2] =='C' else 0,
+                                        e[3] if e[1] == 'FAR' and e[2] =='P' else 0,
+                                        e[3] if e[1] == 'FAR' and e[2] =='c' else 0)
+        return s   
+    
+    def get_traded_months(self):
+        
+        li = []
+        for l in self.port:
+            toks=  l.split(',')
+            li.append(toks[2])
+        #print sorted(list(set(li)))
+        return sorted(list(set(li)))
+    
+#     def get_tbl_pos_csv(self):
+#         s_cols = [0,1,2,3,4]
+#         i_cols = [5,6,7]
+#         s = '["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],'
+#         
+#         for l in sorted(self.port):
+#             content = ''    
+#             toks= l.split(',')
+#  #           print toks
+#             for i in s_cols:
+#                 content += '"%s",' % toks[i]
+#             for i in i_cols:
+#                 content += '%s,' % toks[i]
+#             
+#                 
+#             s += "[%s]," % content
+#         return s       
+
+
+    def get_greeks(self, c_key):
+        #print c_key, len(c_key), self.r_conn.get('a')
+        
+        gdata = self.r_conn.get(c_key)
+        
+        #print gdata
+        gmap = json.loads(gdata) if gdata <> None else None
+        return gmap
+
+
+    def get_pos_contracts(self):
+        s_cols = [0,2,4,3]
+        cs = []
+        for l in sorted(self.port):
+            content = ''    
+            toks= l.split(',')
+            c_key = '-'.join('%s' % toks[i] for i in s_cols)
+            cs.append(c_key)
+        return cs
+
+    def get_tbl_pos_csv(self):
+
+        # WARNING: this routine will crash if a new instrument is traded during the day
+        # but it is not previously been subscribed 
+        # the gmap will be missing from redis
+        # the workaround is to restart options_data.py 
+        # to refresh all positions and add back any
+        # new instruments to its subscription list 
+
+        pall = set(self.r_conn.keys(pattern='%s*' % self.rs_port_keys['port_prefix']))
+        
+        # 2016/06/11
+        # add 2 new columns: avgcost in points, unreal_pl ratio
+        #s = '["symbol","right","avgcost","spotpx","pos","delta","theta","pos_delta","pos_theta","unreal_pl","last_updated"],'
+        s = '["symbol","right","avgcost","avgpx","spotpx","pos","delta","theta","pos_delta","pos_theta","unreal_pl","%gain/loss" ],'
+        
+        def split_toks(x):
+            try: # 
+                pmap = json.loads(self.r_conn.get(x))
+                #print pmap
+                gmap = json.loads(self.r_conn.get(x[3:]))
+                #print gmap
+
+		pl_percent = (1 - gmap['5006'] / (pmap['6001'] / pmap['6007'])) * 100.0 if pmap['6002'] < 0 else  (gmap['5006'] - pmap['6001'] / pmap['6007']) / (pmap['6001'] / pmap['6007']) * 100  
+
+#                 s = '["%s","%s",%f,%f,%f,%f,%f,%f,%f,%f,"%s"],' % (x[3:], x[len(x)-1:], pmap['6001'], gmap['5006'], pmap['6002'],\
+#                                                                              gmap['5002'],gmap['5004'],\
+#                                                                              pmap['6005'],pmap['6006'],pmap['6008'],pmap['last_updated'])
+        # 2016/06/11        
+                s = '["%s","%s",%f,%f,%f,%f,%f,%f,%f,%f,%f,%f],' % (x[3:], x[len(x)-1:], pmap['6001'], pmap['6001'] / pmap['6007'], gmap['5006'], pmap['6002'],\
+                                                                             gmap['5002'],gmap['5004'],\
+                                                                             pmap['6005'],pmap['6006'],pmap['6008'],
+                                                                             pl_percent)
+                                                                             #(1 - gmap['5006'] / (pmap['6001'] / pmap['6007'])) * 100.0)
+            except:
+                logging.error('entry %s skipped due to an exception. Please validate your position' % x)
+                return ''
+            return s                                                          
+            
+        end_s = s + ''.join (split_toks( x ) for x in pall)
+        return end_s 
+
+
+    def get_tbl_pos_csv_old(self, calGreeks=False):
+        s_cols = [0,1,2,3,4]
+        i_cols = [5,6,7]
+        s = '["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],'
+        
+        for l in sorted(self.port):
+            content = ''    
+            toks= l.split(',')
+ #           print toks
+            for i in s_cols:
+                content += '"%s",' % toks[i]
+            for i in i_cols:
+                content += '%s,' % toks[i]
+            
+                
+            s += "[%s]," % content
+        return s  
+
+
+    def get_tbl_pos_list(self, calGreeks=False):
+        s_cols = [0,1,2,3,4]
+        i_cols = [5,6,7]
+        s = []
+        
+        for l in sorted(self.port):
+            content = []    
+            toks= l.split(',')
+ #           print toks
+            for i in s_cols:
+                content.append(toks[i])
+            for i in i_cols:
+                content.append(toks[i])
+            
+                
+            s.append(content)
+        return s 
+
+    def get_portfolio_summary(self):
+
+        
+        return json.loads(self.r_conn.get(self.rs_port_keys['port_summary']))
+    
+
+    def recal_port(self):
+        # http://stackoverflow.com/questions/448034/multithreaded-resource-access-where-do-i-put-my-locks
+        
+        # a wrapper just to ensure only one thread is doing the portfolio recalculation
+        logging.debug('recal_port: acquiring lock...%s' % threading.currentThread())
+        self.tlock.acquire()
+        try:
+            s = self.recal_port_rentrant_unsafe()
+        finally:
+            logging.debug('recal_port: completed recal. releasing lock...')
+            self.tlock.release()  
+            
+        return s
+
+    def recal_port_rentrant_unsafe(self):
+        
+        
+        logging.debug('PortfolioManager: recal_port')
+
+        # retrieve the portfolio entries from redis, strip the prefix in front
+        plines = map(lambda k: (k.replace(self.rs_port_keys['port_prefix'] + '_', '')),\
+                                 self.r_conn.keys(pattern=('%s*'% self.rs_port_keys['port_prefix'])))
+                                 
+        logging.info ("PortfolioManager: recal_port-> gathering position entries from redis %s" % plines)
+        
+        pos_summary = {'delta_c': 0.0, 'delta_p': 0.0, 'delta_all': 0.0,\
+                       'theta_c': 0.0, 'theta_p': 0.0, 'theta_all': 0.0,\
+                       'delta_1percent' : 0.0, 'theta_1percent' : 0.0,\
+                       'iv_plus1p': 0.0, 'iv_minus1p': 0.0, 'unreal_pl': 0.0}
+        l_gmap = []
+        l_skipped_pos =[]       
+        t_pos_multiplier = 0.0
+        
+        for ckey in plines:
+        
+            gmap = self.get_greeks(ckey)
+            logging.debug('PortfolioManager: recal_port greeks market data %s->%s ' % (ckey, gmap))
+            logging.debug('PortfolioManager: recal_port position-map->%s' % (self.r_get(ckey)))
+            pmap = json.loads(self.r_get(ckey)) 
+            
+            
+            
+            if gmap:
+            
+            # Tick Value      Description
+            # 5001            impl vol
+            # 5002            delta
+            # 5003            gamma
+            # 5004            theta
+            # 5005            vega
+            # 5006            premium        
+            # 6001            avgCost
+            # 6002            pos
+            # 6003            totCost
+            # 6004            avgPx
+            # 6005            pos delta
+            # 6006            pos theta
+            # 6007            multiplier
+            # 6009            curr_port_value
+            # 6008            unreal_pl
+            # 6020            pos value impact +1% vol change
+            # 6021            pos value impact -1% vol change
+            
+           
+                def pos_delta():                 
+                    pd = pmap['6002'] * gmap['5002'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_delta: %f' % pd)
+                    return pd
+                
+                def pos_theta():
+                    pd = pmap['6002'] * gmap['5004'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_theta: %f' % pd)
+                    return pd
+ 
+                def pos_avg_px():
+                    pd = pmap['6001'] / pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_avg_px: %f' % pd)
+                    return pd                   
+                    
+                def pos_tot_cost():
+                    pd = pmap['6001'] * pmap['6002'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_tot_cost: %f' % pd)
+                    return pd                   
+                
+                def pos_unreal_pl():
+                    #(spot premium * multiplier - avgcost) * pos) 
+                    v = (gmap['5006'] * pmap['6007'] - pmap['6001']) * pmap['6002'] 
+                    return v 
+    
+                
+    
+                #logging.debug('PortfolioManager: recal_port greeks %f' % pos_delta(gmap))
+                
+                pmap['6005'] = pos_delta()
+                pmap['6006'] = pos_theta()
+                pmap['6004'] = pos_avg_px()
+                pmap['6003'] = pos_tot_cost()
+                pmap['6008'] = pos_unreal_pl()
+                pmap['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
+                
+                l_gmap.append(pmap)          
+
+                #t_pos_multiplier += (pmap['6002'] * pmap['6007'])
+
+                right = ckey.split('-')[3].lower()
+                pos_summary['delta_' + right] += pmap['6005']
+                pos_summary['delta_all'] += pmap['6005']
+                pos_summary['theta_' + right] += pmap['6006']
+                pos_summary['theta_all'] += pmap['6006']
+                pos_summary['unreal_pl'] += pmap['6008']
+
+                
+                
+                #pos_summary['delta_1percent'] += (pos_summary['delta_all'] / (pmap['6002'] * pmap['6007']))
+                # delta_1% = pos_ / (pos * multiplier)
+                
+                
+                #print 'con,right,avgcost,spot px,pos,delta,theta, pos_delta,pos_theta,pos_unreal_pl,last_updated'
+#                 print ( 'PT_entries: %s,%s,%f,%f,%f,%f,%f,%f,%f,%f,%s' % (ckey, right, pmap['6001'], gmap['5006'], pmap['6002'],\
+#                                                                      gmap['5002'],gmap['5004'],\
+#                                                                      pmap['6005'],pmap['6006'],pmap['6008'],pmap['last_updated']
+#                                                                      ))
+#20150911                
+                #print pmap
+                #print json.dumps(pmap)
+                self.r_set(ckey, json.dumps(pmap))
+                
+                logging.debug('PortfolioManager: update position in redis %s' % self.r_get(ckey))
+                
+            else:
+                l_skipped_pos.append(ckey)
+
+            
+#            self.r_set(ckey, json.dumps(pmap))
+#            logging.debug('PortfolioManager: update position in redis %s' % self.r_get(ckey))
+
+ 
+        #pos_summary['delta_1percent'] = (pos_summary['delta_all'] / t_pos_multiplier)
+        
+        
+
+ 
+        if len(l_skipped_pos) > 0:
+            logging.warn('***************** PortfolioManager: recal_port. SOME POSITIONS WERE NOT PROCESSED!')
+            logging.warn('----------------- DO NOT rely on the numbers in the Summary dictionary (pos_summary)')
+            logging.warn('----------------- Please check your portfolio through other means or subscribe missing')
+            logging.warn('----------------- market data in options_serve.py console. ')
+        logging.info('-------------- POSITION SUMMARY')
+        pos_summary['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S')    
+        pos_summary['entries_skipped'] = l_skipped_pos
+        pos_summary['status'] = 'OK' if len(l_skipped_pos) == 0 else 'NOT_OK'
+        #self.r_set(self.rs_port_keys['port_summary'], json.dumps(pos_summary) )
+        t_pos_summary = json.dumps(pos_summary)
+        self.r_conn.set(self.rs_port_keys['port_summary'], t_pos_summary )
+        self.r_conn.set(self.rs_port_keys['port_items'], json.dumps(l_gmap))
+        #print pos_summary
+        #print l_gmap      
+        # broadcast 
+        if self.epc['epc']:
+            try:
+                self.epc['epc'].post_portfolio_summary(pos_summary)
+                self.epc['epc'].post_portfolio_items(l_gmap)
+            except:
+                logging.exception("Exception in function: recal_port_rentrant_unsafe")
+
+        #logging.info(pos_summary)
+        
+        logging.warn('-------------- Entries for which the greeks are not computed!! %s' %\
+                        ','.join(' %s' % k for k in l_skipped_pos))
+        
+        return l_gmap
+ 
+
+    def construct_port(self, pos_msg):
+        # port structure
+        #
+        # exch,type,contract_mth,right, strike,con_ration,pos,avgcost
+        # 
+        # Tick Value      Description
+        # 5001            impl vol
+        # 5002            delta
+        # 5003            gamma
+        # 5004            theta
+        # 5005            vega
+        # 5005            premium
+        
+        # 6001            avgCost
+        # 6002            pos
+    
+        
+        # sample pos_msg '[HSI', 'OPT', 'None', 'HKD', '20160330', '18200.0', 'P', '204436784]'
+        toks = options_data.ContractHelper.printContract(pos_msg.contract).split('-')
+        s = ''
+        
+        slots = [0, 1, 4, 6]
+        s = s + '%s,%s,%s' % (','.join(toks[i] for i in slots), toks[5].replace('.0', ''), '50.0' if toks[0][1:] == 'HSI' else '10.0')
+        s = s.replace('[', '') + ",%0.4f,%0.4f" % (pos_msg.pos, pos_msg.avgCost)
+        
+#         print toks
+#         print '---> %s' % s
+        self.port.append(s)
+                
+        ckey = options_data.ContractHelper.makeRedisKey(pos_msg.contract)
+        multiplier = 50.0 if toks[0][1:] == 'HSI' else 10.0
+
+        
+        
+        self.r_set(ckey, json.dumps({"contract": ckey, "6002": pos_msg.pos, "6001": pos_msg.avgCost, "6007": multiplier}))
+        #self.recal_port(ckey)
+ 
+    #the wrapper functions add a prefix session id to every key
+    #
+    def r_set(self, key, val):
+        return self.r_conn.set("%s_%s" % (self.rs_port_keys['port_prefix'], key), val)
+        
+    
+    def r_get(self, key):
+        return self.r_conn.get("%s_%s" % (self.rs_port_keys['port_prefix'], key))
+    
+    
+    def r_sadd(self, key, val):
+        return self.r_conn.sadd("%s_%s" % (self.rs_port_keys['port_prefix'], key), val)
+    
+    def r_sismember(self, set, val):
+        return self.r_conn.sismember("%s_%s" % (self.rs_port_keys['port_prefix'], set), val)
+
+    def r_smembers(self, key):
+        return self.r_conn.smembers("%s_%s" % (self.rs_port_keys['port_prefix'], key))
+       
+
+    def connect(self):
+        self.con.connect()
+
+        
+    def disconnect(self):
+
+        self.con.disconnect()
+        self.quit = True
+
+if __name__ == '__main__':
+           
+    if len(sys.argv) != 2:
+        print("Usage: %s <config file>" % sys.argv[0])
+        exit(-1)    
+
+    cfg_path= sys.argv[1:]    
+    config = ConfigParser.SafeConfigParser()
+    if len(config.read(cfg_path)) == 0:      
+        raise ValueError, "Failed to open config file" 
+    
+    logconfig = eval(config.get("portfolio", "portfolio.logconfig").strip('"').strip("'"))
+    logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'    
+    logging.basicConfig(**logconfig)        
+
+    p = PortfolioManager(config)
+    p.retrieve_position()
+    print p.get_portfolio_summary()
+    print p.get_tbl_pos_csv()
+    p.group_pos_by_strike_by_month()
+    print p.grouped_options
+    print p.get_grouped_options_str_array_stacked()
+
+    # sample ouput    
+# ["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],["HSI","OPT","20150828","C","22600",50.0,0.0000,0.0000,],["HSI","OPT","20150828","C","23000",50.0,-1.0000,1770.0000,],["HSI","OPT","20150828","C","23600",50.0,-2.0000,1470.0000,],["HSI","OPT","20150828","C","23800",50.0,-1.0000,920.0000,],["HSI","OPT","20150828","C","24000",50.0,-2.0000,1820.0000,],["HSI","OPT","20150828","C","24200",50.0,-1.0000,3120.0000,],["HSI","OPT","20150828","C","24800",50.0,-1.0000,220.0000,],["HSI","OPT","20150828","P","18000",50.0,-2.0000,1045.0000,],["HSI","OPT","20150828","P","18600",50.0,-1.0000,1120.0000,],["HSI","OPT","20150828","P","18800",50.0,-1.0000,1570.0000,],["HSI","OPT","20150828","P","19800",50.0,-1.0000,870.0000,],["HSI","OPT","20150828","P","20200",50.0,-1.0000,970.0000,],["HSI","OPT","20150828","P","20800",50.0,-2.0000,970.0000,],["HSI","OPT","20150828","P","21600",50.0,-1.0000,1570.0000,],["HSI","OPT","20150828","P","21800",50.0,-7.0000,1955.7143,],["HSI","OPT","20150828","P","23200",50.0,1.0000,25930.0000,],["HSI","OPT","20150929","C","24400",50.0,1.0000,24880.0000,],["HSI","OPT","20150929","P","21600",50.0,0.0000,0.0000,],["HSI","OPT","20150929","P","21800",50.0,2.0000,52713.3333,],["HSI","OPT","20150929","P","22600",50.0,3.0000,39763.3333,],["MHI","OPT","20150828","C","24400",10.0,-1.0000,2603.0000,],["MHI","OPT","20150828","P","20800",10.0,-1.0000,313.0000,],["MHI","OPT","20150828","P","21000",10.0,-1.0000,363.0000,],["MHI","OPT","20150828","P","23600",10.0,5.0000,4285.0000,],["MHI","OPT","20150929","C","24400",10.0,1.0000,4947.0000,],["MHI","OPT","20150929","P","21600",10.0,1.0000,12657.0000,],["MHI","OPT","20150929","P","22600",10.0,1.0000,9877.0000,],["MHI","OPT","20150929","P","23600",10.0,4.0000,7757.0000,],
+# [180.000000,-2.0,0],[186.000000,-1.0,0],[188.000000,-1.0,0],[198.000000,-1.0,0],[202.000000,-1.0,0],[208.000000,-2.2,0],[210.000000,-0.2,0],[216.000000,-0.8,0],[218.000000,-5.0,0],[226.000000,0,0.0],[226.000000,3.2,0],[230.000000,0,-1.0],[232.000000,1.0,0],[236.000000,0,-2.0],[236.000000,1.8,0],[238.000000,0,-1.0],[240.000000,0,-2.0],[242.000000,0,-1.0],[244.000000,0,1.0],[248.000000,0,-1.0],
+# {'P': 0.0, 'C': 0.0} [('P', 0.8), ('P', 0.0), ('C', -2.0), ('C', -2.0), ('P', -1.0), ('C', 0.2), ('P', -1.0), ('P', -0.2), ('C', 0.0), ('P', -0.2), ('C', -1.0), ('P', -1.0), ('C', -1.0), ('P', 1.0), ('P', 0.2), ('P', 2.0), ('C', 1.0), ('P', -2.0), ('P', -7.0), ('P', 1.0), ('P', -1.0), ('P', -1.0), ('P', 0.2), ('C', -1.0), ('P', 3.0), ('C', -1.0), ('C', -0.2), ('P', -2.0)]
+# [('P', -8.200000000000001), ('C', -7.0)]
+
+    
+         
+    
+

+ 686 - 0
finopt/portfolio.py~

@@ -0,0 +1,686 @@
+# -*- coding: utf-8 -*-
+
+import sys, traceback
+import json
+import logging
+import thread, threading
+from threading import Lock
+import ConfigParser
+from ib.ext.Contract import Contract
+from ib.opt import ibConnection#, message
+from time import sleep
+import time, datetime
+import optcal
+import opt_serve
+import cherrypy
+import redis
+from comms.epc import EPCPub
+
+
+
+#from options_data import ContractHelper
+import options_data
+# Tick Value      Description
+# 5001            impl vol
+# 5002            delta
+# 5003            gamma
+# 5004            theta
+# 5005            vega
+# 5006            premium
+
+# 6001            avgCost
+# 6002            pos
+# 6003            totCost
+# 6004            avgPx
+# 6005            pos delta
+# 6006            pos theta
+# 6007            multiplier
+# 6020            pos value impact +1% vol change
+# 6021            pos value impact -1% vol change
+
+
+# IB tick types code reference
+# https://www.interactivebrokers.com/en/software/api/api.htm
+                    
+
+class PortfolioManager():
+    
+    config = {}
+    con = None
+    r_conn = None
+    port = []
+    grouped_options = None
+    
+    # item 0 is for position, item 1 is for account
+    download_states = None
+    quit = False
+    interested_types = ['OPT', 'FUT']
+    rs_port_keys = {}
+    ib_port_msg = []
+    tlock = None
+    epc = None
+    account_tags = []
+    ib_acct_msg = {}
+    
+    def __init__(self, config):
+        self.config = config
+
+        host = config.get("market", "ib.gateway").strip('"').strip("'")
+        port = int(config.get("market", "ib.port"))
+        appid = int(config.get("market", "ib.appid.portfolio"))   
+        self.rs_port_keys['port_conid_set'] = config.get("redis", "redis.datastore.key.port_conid_set").strip('"').strip("'")
+        self.rs_port_keys['port_prefix'] = config.get("redis", "redis.datastore.key.port_prefix").strip('"').strip("'")        
+        self.rs_port_keys['port_summary'] = config.get("redis", "redis.datastore.key.port_summary").strip('"').strip("'")
+        self.rs_port_keys['port_items'] = config.get("redis", "redis.datastore.key.port_items").strip('"').strip("'")
+        
+        
+        self.rs_port_keys['acct_summary'] = config.get("redis", "redis.datastore.key.acct_summary").strip('"').strip("'") 
+        self.account_tags = eval(config.get("portfolio", "portfolio.account_summary_tags").strip('"').strip("'"))
+        
+        self.epc = eval(config.get("portfolio", "portfolio.epc").strip('"').strip("'"))
+        # instantiate a epc object if the config says so
+        
+        
+        if self.epc['stream_to_Kafka']:
+            self.epc['epc'] = EPCPub(config) 
+        
+        r_host = config.get("redis", "redis.server").strip('"').strip("'")
+        r_port = config.get("redis", "redis.port")
+        r_db = config.get("redis", "redis.db")             
+        
+        self.r_conn = redis.Redis(r_host, r_port, r_db)
+        
+        self.con = ibConnection(host, port, appid)
+        self.con.registerAll(self.on_ib_message)
+        
+        
+    	self.download_states = [False, False]
+        self.tlock = Lock()
+    
+    def retrieve_position(self):
+        self.connect()
+        
+        # clear previous saved values
+        self.port = []
+        self.ib_port_msg = []
+        self.clear_redis_portfolio()
+        
+        self.subscribe()
+        while not self.quit:
+            if self.download_states[0] == True and self.download_states[1] == True:
+                self.disconnect()
+                self.quit = True
+                
+    def clear_redis_portfolio(self):
+        l = map(lambda x: (self.r_conn.delete(x)), self.r_conn.keys(pattern='%s*' % (self.rs_port_keys['port_prefix'])))
+        #self.r_set(self.rs_port_keys['port_summary'], '{"Portfolio Retrieval": "Calculation in progress...Try again later!" }')
+        self.r_conn.set(self.rs_port_keys['port_summary'], '{"Portfolio Retrieval": "Calculation in progress...Try again later!" }')
+
+        logging.debug('clear_redis_portfolio: num items cleared: %d'% len(l))
+        
+    
+    def subscribe(self):
+
+            self.con.reqPositions()
+            logging.debug('account info to retrieve: %s' % (''.join('%s,' % s for s in self.account_tags)))
+            self.con.reqAccountSummary(100, 'All', ''.join('%s,' % s for s in self.account_tags))
+
+            #self.con.register(self.on_ib_message, 'UpdateAccountValue')
+
+    def on_ib_message(self, msg):
+        
+        #print msg.typeName, msg
+        
+        if msg.typeName in "position":
+            if self.download_states[0] == False:
+                logging.debug("%s" %  (options_data.ContractHelper.printContract(msg.contract)))
+                if msg.contract.m_secType in self.interested_types:
+                    logging.debug("PortfolioManager: getting position...%s" % msg)
+                    self.ib_port_msg.append(msg)
+                    #self.construct_port(msg)
+            
+        if msg.typeName == 'positionEnd':
+            
+
+            for pm in self.ib_port_msg:
+                self.construct_port(pm)
+                
+            self.recal_port()
+            self.group_pos_by_strike()
+            logging.debug("PortfolioManager: Complete position download. Disconnecting...")
+            
+
+            self.download_states[0] = True
+            
+        if msg.typeName == "accountSummary":
+            if self.download_states[1] == False:
+                
+                self.ib_acct_msg[msg.tag] = (msg.value, msg.currency, msg.account)
+                logging.debug("PortfolioManager: getting account info...%s" % msg)
+            
+        if msg.typeName == 'accountSummaryEnd':
+            
+            self.ib_acct_msg['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S') 
+            logging.info("-------------- ACCOUNT SUMMARY [%s]" % (self.ib_acct_msg['AccountType'][2]))
+            logging.info('\n\n' + ''.join('%30s: %22s\n' % (k, ''.join('%10s %12s' % (v[0], v[1] if v[1] else ''))) for k,v in self.ib_acct_msg.iteritems()))
+            
+            self.r_conn.set(self.rs_port_keys['acct_summary'], json.dumps(self.ib_acct_msg))
+            if self.epc['epc']:
+                try:
+
+                    self.epc['epc'].post_account_summary(self.ib_acct_msg)
+
+                except:
+                    logging.exception("Exception in function when trying to broadcast account summary message to epc")
+            
+            self.download_states[1] = True
+        
+            
+            
+            
+    def group_pos_by_strike(self):
+
+
+        # split into lines of position       
+        m = map(lambda x: x.split(','), self.port)
+        # transform each line into two elements. first one is a key created 
+        # by combining right and strike, the second is the product of 
+        # position * conversion ratio (HSI=50, MSI=10)
+        n = map(lambda x: (x[3] + x[4], float(x[5]) * float(x[6])/50), m)
+        
+        #p = dict(set(map(lambda x:(x[0], 0), n)))
+        
+        def sumByKey(f, n):
+            # filter the list with only unique keys
+            # transform the list into a dict
+            p = dict(set(map(f, n)))
+            # add the numbers together on key
+            l =[]
+            for x in n:
+                p[x[0]] += x[1]
+                
+            return [(k[1:], k[0:1],v) for k,v in p.iteritems()]
+         
+         
+           
+        #print len(n),n
+        # initialize a list of strikes and the position sum to 0
+        # pass the list to sumByKey routine
+        self.grouped_options = sumByKey(lambda x:(x[0], 0), n)
+        #print len(l), sorted(l)
+
+
+    def group_pos_by_strike_by_month(self):
+
+
+        # split into lines of position       
+        m = map(lambda x: x.split(','), self.port)
+        # transform each line into two elements. first one is a key created 
+        # by combining right and strike, the second is the product of 
+        # position * conversion ratio (HSI=50, MSI=10)
+        
+        mth_set = set(map(lambda x: x[2], m))
+        print max(mth_set)
+        n = map(lambda x: (x[3] + x[4] + x[2], float(x[5]) * float(x[6])/50), m)
+        
+        #p = dict(set(map(lambda x:(x[0], 0), n)))
+        
+        def sumByKey(f, n):
+            # filter the list with only unique keys
+            # transform the list into a dict
+            p = dict(set(map(f, n)))
+            
+            # add the numbers together on key
+            l =[]
+            for x in n:
+                
+                p[x[0]] += x[1]
+                
+            return [(k[1:6], 'NEAR' if k[6:] < max(mth_set) else 'FAR', k[0:1],v) for k,v in p.iteritems()]
+         
+         
+           
+        #print len(n),n
+        # initialize a list of strikes and the position sum to 0
+        # pass the list to sumByKey routine
+        self.grouped_options = sumByKey(lambda x:(x[0],  0), n)
+        #print len(l), sorted(l)
+
+
+
+        
+    def group_pos_by_right(self):        
+    # group by put, call (summing up all contracts by right type,ignoring strikes)
+        # split into lines of position       
+        m = map(lambda x: x.split(','), self.port)
+        q = map(lambda x: (x[3], float(x[5]) * float(x[6])/50), m)
+        u = dict(set(map(lambda x:(x[0], 0.0), q)))
+#        print u, q
+        for x in q:
+            u[x[0]] += x[1] 
+        return [(a,b) for a,b in u.iteritems()]
+    
+    def get_grouped_options_str_array(self):
+        s = ''
+        for e in sorted(self.grouped_options):
+            s += "[%f,%s,%s]," % (float(e[0])/100.0, e[2] if e[1] == 'P' else 0, e[2] if e[1] == 'C' else 0)
+        return s
+ 
+ 
+    def get_grouped_options_str_array_stacked(self):
+        s = ''
+        
+        #[('20800', 'FAR', 'C', -1.0), ('17600', 'NEAR', 'P', -2.0), ('21400', 'NEAR', 'C', -4.0), ('15800', 'NEAR', 'P', -4.0), ('15600', 'FAR', 'P', -3.0), ('14600', 'FAR', 'P', -1.0), ('20400', 'NEAR', 'C', -2.0), ('15600', 'NEAR', 'P', -2.0), ('18200', 'NEAR', 'P', 0.0), ('16000', 'NEAR', 'P', -3.0), ('15000', 'FAR', 'P', -1.0), ('18800', 'FAR', 'P', 3.0), ('21200', 'FAR', 'C', -2.0), ('20800', 'NEAR', 'C', -4.0), ('18200', 'FAR', 'P', 1.0), ('17800', 'NEAR', 'P', -8.0), ('18600', 'NEAR', 'P', -1.0)]
+        for e in sorted(self.grouped_options):
+            s += "[%s,%s,%s,%s,%s]," % (e[0], e[3] if e[1] == 'NEAR' and e[2] =='P' else 0, 
+                                        e[3] if e[1] == 'NEAR' and e[2] =='C' else 0,
+                                        e[3] if e[1] == 'FAR' and e[2] =='P' else 0,
+                                        e[3] if e[1] == 'FAR' and e[2] =='c' else 0)
+        return s   
+    
+    def get_traded_months(self):
+        
+        li = []
+        for l in self.port:
+            toks=  l.split(',')
+            li.append(toks[2])
+        #print sorted(list(set(li)))
+        return sorted(list(set(li)))
+    
+#     def get_tbl_pos_csv(self):
+#         s_cols = [0,1,2,3,4]
+#         i_cols = [5,6,7]
+#         s = '["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],'
+#         
+#         for l in sorted(self.port):
+#             content = ''    
+#             toks= l.split(',')
+#  #           print toks
+#             for i in s_cols:
+#                 content += '"%s",' % toks[i]
+#             for i in i_cols:
+#                 content += '%s,' % toks[i]
+#             
+#                 
+#             s += "[%s]," % content
+#         return s       
+
+
+    def get_greeks(self, c_key):
+        #print c_key, len(c_key), self.r_conn.get('a')
+        
+        gdata = self.r_conn.get(c_key)
+        
+        #print gdata
+        gmap = json.loads(gdata) if gdata <> None else None
+        return gmap
+
+
+    def get_pos_contracts(self):
+        s_cols = [0,2,4,3]
+        cs = []
+        for l in sorted(self.port):
+            content = ''    
+            toks= l.split(',')
+            c_key = '-'.join('%s' % toks[i] for i in s_cols)
+            cs.append(c_key)
+        return cs
+
+    def get_tbl_pos_csv(self):
+
+        # WARNING: this routine will crash if a new instrument is traded during the day
+        # but it is not previously been subscribed 
+        # the gmap will be missing from redis
+        # the workaround is to restart options_data.py 
+        # to refresh all positions and add back any
+        # new instruments to its subscription list 
+
+        pall = set(self.r_conn.keys(pattern='%s*' % self.rs_port_keys['port_prefix']))
+        
+        # 2016/06/11
+        # add 2 new columns: avgcost in points, unreal_pl ratio
+        #s = '["symbol","right","avgcost","spotpx","pos","delta","theta","pos_delta","pos_theta","unreal_pl","last_updated"],'
+        s = '["symbol","right","avgcost","avgpx","spotpx","pos","delta","theta","pos_delta","pos_theta","unreal_pl","%gain/loss" ],'
+        
+        def split_toks(x):
+            try: # 
+                pmap = json.loads(self.r_conn.get(x))
+                #print pmap
+                gmap = json.loads(self.r_conn.get(x[3:]))
+                #print gmap
+
+		pl_percent = (1 - gmap['5006'] / (pmap['6001'] / pmap['6007'])) * 100.0 if pmap['6002'] < 0 else  (gmap['5006'] - pmap['6001'] / pmap['6007']) / (pmap['6001'] / pmap['6007']) * 100  
+
+#                 s = '["%s","%s",%f,%f,%f,%f,%f,%f,%f,%f,"%s"],' % (x[3:], x[len(x)-1:], pmap['6001'], gmap['5006'], pmap['6002'],\
+#                                                                              gmap['5002'],gmap['5004'],\
+#                                                                              pmap['6005'],pmap['6006'],pmap['6008'],pmap['last_updated'])
+        # 2016/06/11        
+                s = '["%s","%s",%f,%f,%f,%f,%f,%f,%f,%f,%f,%f],' % (x[3:], x[len(x)-1:], pmap['6001'], pmap['6001'] / pmap['6007'], gmap['5006'], pmap['6002'],\
+                                                                             gmap['5002'],gmap['5004'],\
+                                                                             pmap['6005'],pmap['6006'],pmap['6008'],
+                                                                             pl_percent)
+                                                                             #(1 - gmap['5006'] / (pmap['6001'] / pmap['6007'])) * 100.0)
+            except:
+                logging.error('entry %s skipped due to an exception. Please validate your position' % x)
+                return ''
+            return s                                                          
+            
+        end_s = s + ''.join (split_toks( x ) for x in pall)
+        return end_s 
+
+
+    def get_tbl_pos_csv_old(self, calGreeks=False):
+        s_cols = [0,1,2,3,4]
+        i_cols = [5,6,7]
+        s = '["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],'
+        
+        for l in sorted(self.port):
+            content = ''    
+            toks= l.split(',')
+ #           print toks
+            for i in s_cols:
+                content += '"%s",' % toks[i]
+            for i in i_cols:
+                content += '%s,' % toks[i]
+            
+                
+            s += "[%s]," % content
+        return s  
+
+
+    def get_tbl_pos_list(self, calGreeks=False):
+        s_cols = [0,1,2,3,4]
+        i_cols = [5,6,7]
+        s = []
+        
+        for l in sorted(self.port):
+            content = []    
+            toks= l.split(',')
+ #           print toks
+            for i in s_cols:
+                content.append(toks[i])
+            for i in i_cols:
+                content.append(toks[i])
+            
+                
+            s.append(content)
+        return s 
+
+    def get_portfolio_summary(self):
+
+        
+        return json.loads(self.r_conn.get(self.rs_port_keys['port_summary']))
+    
+
+    def recal_port(self):
+        # http://stackoverflow.com/questions/448034/multithreaded-resource-access-where-do-i-put-my-locks
+        
+        # a wrapper just to ensure only one thread is doing the portfolio recalculation
+        logging.debug('recal_port: acquiring lock...%s' % threading.currentThread())
+        self.tlock.acquire()
+        try:
+            s = self.recal_port_rentrant_unsafe()
+        finally:
+            logging.debug('recal_port: completed recal. releasing lock...')
+            self.tlock.release()  
+            
+        return s
+
+    def recal_port_rentrant_unsafe(self):
+        
+        
+        logging.debug('PortfolioManager: recal_port')
+
+        # retrieve the portfolio entries from redis, strip the prefix in front
+        plines = map(lambda k: (k.replace(self.rs_port_keys['port_prefix'] + '_', '')),\
+                                 self.r_conn.keys(pattern=('%s*'% self.rs_port_keys['port_prefix'])))
+                                 
+        logging.info ("PortfolioManager: recal_port-> gathering position entries from redis %s" % plines)
+        
+        pos_summary = {'delta_c': 0.0, 'delta_p': 0.0, 'delta_all': 0.0,\
+                       'theta_c': 0.0, 'theta_p': 0.0, 'theta_all': 0.0,\
+                       'delta_1percent' : 0.0, 'theta_1percent' : 0.0,\
+                       'iv_plus1p': 0.0, 'iv_minus1p': 0.0, 'unreal_pl': 0.0}
+        l_gmap = []
+        l_skipped_pos =[]       
+        t_pos_multiplier = 0.0
+        
+        for ckey in plines:
+        
+            gmap = self.get_greeks(ckey)
+            logging.debug('PortfolioManager: recal_port greeks market data %s->%s ' % (ckey, gmap))
+            logging.debug('PortfolioManager: recal_port position-map->%s' % (self.r_get(ckey)))
+            pmap = json.loads(self.r_get(ckey)) 
+            
+            
+            
+            if gmap:
+            
+            # Tick Value      Description
+            # 5001            impl vol
+            # 5002            delta
+            # 5003            gamma
+            # 5004            theta
+            # 5005            vega
+            # 5006            premium        
+            # 6001            avgCost
+            # 6002            pos
+            # 6003            totCost
+            # 6004            avgPx
+            # 6005            pos delta
+            # 6006            pos theta
+            # 6007            multiplier
+            # 6009            curr_port_value
+            # 6008            unreal_pl
+            # 6020            pos value impact +1% vol change
+            # 6021            pos value impact -1% vol change
+            
+           
+                def pos_delta():                 
+                    pd = pmap['6002'] * gmap['5002'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_delta: %f' % pd)
+                    return pd
+                
+                def pos_theta():
+                    pd = pmap['6002'] * gmap['5004'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_theta: %f' % pd)
+                    return pd
+ 
+                def pos_avg_px():
+                    pd = pmap['6001'] / pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_avg_px: %f' % pd)
+                    return pd                   
+                    
+                def pos_tot_cost():
+                    pd = pmap['6001'] * pmap['6002'] * pmap['6007']
+                    logging.debug('PortfolioManager: recal_port: pos_tot_cost: %f' % pd)
+                    return pd                   
+                
+                def pos_unreal_pl():
+                    #(spot premium * multiplier - avgcost) * pos) 
+                    v = (gmap['5006'] * pmap['6007'] - pmap['6001']) * pmap['6002'] 
+                    return v 
+    
+                
+    
+                #logging.debug('PortfolioManager: recal_port greeks %f' % pos_delta(gmap))
+                
+                pmap['6005'] = pos_delta()
+                pmap['6006'] = pos_theta()
+                pmap['6004'] = pos_avg_px()
+                pmap['6003'] = pos_tot_cost()
+                pmap['6008'] = pos_unreal_pl()
+                pmap['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
+                
+                l_gmap.append(pmap)          
+
+                #t_pos_multiplier += (pmap['6002'] * pmap['6007'])
+
+                right = ckey.split('-')[3].lower()
+                pos_summary['delta_' + right] += pmap['6005']
+                pos_summary['delta_all'] += pmap['6005']
+                pos_summary['theta_' + right] += pmap['6006']
+                pos_summary['theta_all'] += pmap['6006']
+                pos_summary['unreal_pl'] += pmap['6008']
+
+                
+                
+                #pos_summary['delta_1percent'] += (pos_summary['delta_all'] / (pmap['6002'] * pmap['6007']))
+                # delta_1% = pos_ / (pos * multiplier)
+                
+                
+                #print 'con,right,avgcost,spot px,pos,delta,theta, pos_delta,pos_theta,pos_unreal_pl,last_updated'
+#                 print ( 'PT_entries: %s,%s,%f,%f,%f,%f,%f,%f,%f,%f,%s' % (ckey, right, pmap['6001'], gmap['5006'], pmap['6002'],\
+#                                                                      gmap['5002'],gmap['5004'],\
+#                                                                      pmap['6005'],pmap['6006'],pmap['6008'],pmap['last_updated']
+#                                                                      ))
+#20150911                
+                #print pmap
+                #print json.dumps(pmap)
+                self.r_set(ckey, json.dumps(pmap))
+                
+                logging.debug('PortfolioManager: update position in redis %s' % self.r_get(ckey))
+                
+            else:
+                l_skipped_pos.append(ckey)
+
+            
+#            self.r_set(ckey, json.dumps(pmap))
+#            logging.debug('PortfolioManager: update position in redis %s' % self.r_get(ckey))
+
+ 
+        #pos_summary['delta_1percent'] = (pos_summary['delta_all'] / t_pos_multiplier)
+        
+        
+
+ 
+        if len(l_skipped_pos) > 0:
+            logging.warn('***************** PortfolioManager: recal_port. SOME POSITIONS WERE NOT PROCESSED!')
+            logging.warn('----------------- DO NOT rely on the numbers in the Summary dictionary (pos_summary)')
+            logging.warn('----------------- Please check your portfolio through other means or subscribe missing')
+            logging.warn('----------------- market data in options_serve.py console. ')
+        logging.info('-------------- POSITION SUMMARY')
+        pos_summary['last_updated'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S')    
+        pos_summary['entries_skipped'] = l_skipped_pos
+        pos_summary['status'] = 'OK' if len(l_skipped_pos) == 0 else 'NOT_OK'
+        #self.r_set(self.rs_port_keys['port_summary'], json.dumps(pos_summary) )
+        t_pos_summary = json.dumps(pos_summary)
+        self.r_conn.set(self.rs_port_keys['port_summary'], t_pos_summary )
+        self.r_conn.set(self.rs_port_keys['port_items'], json.dumps(l_gmap))
+        #print pos_summary
+        #print l_gmap      
+        # broadcast 
+        if self.epc['epc']:
+            try:
+                self.epc['epc'].post_portfolio_summary(pos_summary)
+                self.epc['epc'].post_portfolio_items(l_gmap)
+            except:
+                logging.exception("Exception in function: recal_port_rentrant_unsafe")
+
+        #logging.info(pos_summary)
+        
+        logging.warn('-------------- Entries for which the greeks are not computed!! %s' %\
+                        ','.join(' %s' % k for k in l_skipped_pos))
+        
+        return l_gmap
+ 
+
+    def construct_port(self, pos_msg):
+        # port structure
+        #
+        # exch,type,contract_mth,right, strike,con_ration,pos,avgcost
+        # 
+        # Tick Value      Description
+        # 5001            impl vol
+        # 5002            delta
+        # 5003            gamma
+        # 5004            theta
+        # 5005            vega
+        # 5005            premium
+        
+        # 6001            avgCost
+        # 6002            pos
+    
+        
+        # sample pos_msg '[HSI', 'OPT', 'None', 'HKD', '20160330', '18200.0', 'P', '204436784]'
+        toks = options_data.ContractHelper.printContract(pos_msg.contract).split('-')
+        s = ''
+        
+        slots = [0, 1, 4, 6]
+        s = s + '%s,%s,%s' % (','.join(toks[i] for i in slots), toks[5].replace('.0', ''), '50.0' if toks[0][1:] == 'HSI' else '10.0')
+        s = s.replace('[', '') + ",%0.4f,%0.4f" % (pos_msg.pos, pos_msg.avgCost)
+        
+#         print toks
+#         print '---> %s' % s
+        self.port.append(s)
+                
+        ckey = options_data.ContractHelper.makeRedisKey(pos_msg.contract)
+        multiplier = 50.0 if toks[0][1:] == 'HSI' else 10.0
+
+        
+        
+        self.r_set(ckey, json.dumps({"contract": ckey, "6002": pos_msg.pos, "6001": pos_msg.avgCost, "6007": multiplier}))
+        #self.recal_port(ckey)
+ 
+    #the wrapper functions add a prefix session id to every key
+    #
+    def r_set(self, key, val):
+        return self.r_conn.set("%s_%s" % (self.rs_port_keys['port_prefix'], key), val)
+        
+    
+    def r_get(self, key):
+        return self.r_conn.get("%s_%s" % (self.rs_port_keys['port_prefix'], key))
+    
+    
+    def r_sadd(self, key, val):
+        return self.r_conn.sadd("%s_%s" % (self.rs_port_keys['port_prefix'], key), val)
+    
+    def r_sismember(self, set, val):
+        return self.r_conn.sismember("%s_%s" % (self.rs_port_keys['port_prefix'], set), val)
+
+    def r_smembers(self, key):
+        return self.r_conn.smembers("%s_%s" % (self.rs_port_keys['port_prefix'], key))
+       
+
+    def connect(self):
+        self.con.connect()
+
+        
+    def disconnect(self):
+
+        self.con.disconnect()
+        self.quit = True
+
+if __name__ == '__main__':
+           
+    if len(sys.argv) != 2:
+        print("Usage: %s <config file>" % sys.argv[0])
+        exit(-1)    
+
+    cfg_path= sys.argv[1:]    
+    config = ConfigParser.SafeConfigParser()
+    if len(config.read(cfg_path)) == 0:      
+        raise ValueError, "Failed to open config file" 
+    
+    logconfig = eval(config.get("portfolio", "portfolio.logconfig").strip('"').strip("'"))
+    logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'    
+    logging.basicConfig(**logconfig)        
+
+    p = PortfolioManager(config)
+    p.retrieve_position()
+    print p.get_portfolio_summary()
+    print p.get_tbl_pos_csv()
+    p.group_pos_by_strike_by_month()
+    print p.grouped_options
+    print p.get_grouped_options_str_array_stacked()
+
+    # sample ouput    
+# ["exch","type","contract_mth","right","strike","con_ration","pos","avgcost"],["HSI","OPT","20150828","C","22600",50.0,0.0000,0.0000,],["HSI","OPT","20150828","C","23000",50.0,-1.0000,1770.0000,],["HSI","OPT","20150828","C","23600",50.0,-2.0000,1470.0000,],["HSI","OPT","20150828","C","23800",50.0,-1.0000,920.0000,],["HSI","OPT","20150828","C","24000",50.0,-2.0000,1820.0000,],["HSI","OPT","20150828","C","24200",50.0,-1.0000,3120.0000,],["HSI","OPT","20150828","C","24800",50.0,-1.0000,220.0000,],["HSI","OPT","20150828","P","18000",50.0,-2.0000,1045.0000,],["HSI","OPT","20150828","P","18600",50.0,-1.0000,1120.0000,],["HSI","OPT","20150828","P","18800",50.0,-1.0000,1570.0000,],["HSI","OPT","20150828","P","19800",50.0,-1.0000,870.0000,],["HSI","OPT","20150828","P","20200",50.0,-1.0000,970.0000,],["HSI","OPT","20150828","P","20800",50.0,-2.0000,970.0000,],["HSI","OPT","20150828","P","21600",50.0,-1.0000,1570.0000,],["HSI","OPT","20150828","P","21800",50.0,-7.0000,1955.7143,],["HSI","OPT","20150828","P","23200",50.0,1.0000,25930.0000,],["HSI","OPT","20150929","C","24400",50.0,1.0000,24880.0000,],["HSI","OPT","20150929","P","21600",50.0,0.0000,0.0000,],["HSI","OPT","20150929","P","21800",50.0,2.0000,52713.3333,],["HSI","OPT","20150929","P","22600",50.0,3.0000,39763.3333,],["MHI","OPT","20150828","C","24400",10.0,-1.0000,2603.0000,],["MHI","OPT","20150828","P","20800",10.0,-1.0000,313.0000,],["MHI","OPT","20150828","P","21000",10.0,-1.0000,363.0000,],["MHI","OPT","20150828","P","23600",10.0,5.0000,4285.0000,],["MHI","OPT","20150929","C","24400",10.0,1.0000,4947.0000,],["MHI","OPT","20150929","P","21600",10.0,1.0000,12657.0000,],["MHI","OPT","20150929","P","22600",10.0,1.0000,9877.0000,],["MHI","OPT","20150929","P","23600",10.0,4.0000,7757.0000,],
+# [180.000000,-2.0,0],[186.000000,-1.0,0],[188.000000,-1.0,0],[198.000000,-1.0,0],[202.000000,-1.0,0],[208.000000,-2.2,0],[210.000000,-0.2,0],[216.000000,-0.8,0],[218.000000,-5.0,0],[226.000000,0,0.0],[226.000000,3.2,0],[230.000000,0,-1.0],[232.000000,1.0,0],[236.000000,0,-2.0],[236.000000,1.8,0],[238.000000,0,-1.0],[240.000000,0,-2.0],[242.000000,0,-1.0],[244.000000,0,1.0],[248.000000,0,-1.0],
+# {'P': 0.0, 'C': 0.0} [('P', 0.8), ('P', 0.0), ('C', -2.0), ('C', -2.0), ('P', -1.0), ('C', 0.2), ('P', -1.0), ('P', -0.2), ('C', 0.0), ('P', -0.2), ('C', -1.0), ('P', -1.0), ('C', -1.0), ('P', 1.0), ('P', 0.2), ('P', 2.0), ('C', 1.0), ('P', -2.0), ('P', -7.0), ('P', 1.0), ('P', -1.0), ('P', -1.0), ('P', 0.2), ('C', -1.0), ('P', 3.0), ('C', -1.0), ('C', -0.2), ('P', -2.0)]
+# [('P', -8.200000000000001), ('C', -7.0)]
+
+    
+         
+    
+

+ 1 - 0
opt_serve.pid

@@ -0,0 +1 @@
+3650

+ 5 - 0
sh/kill_opt_serve.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+ROOT=~/mchan927/finopt
+export PYTHONPATH=$ROOT
+
+kill -9  $(cat $ROOT/opt_serve.pid)

+ 4 - 0
sh/optcal.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+ROOT=~/mchan927/finopt
+export PYTHONPATH=$ROOT
+python $ROOT/finopt/optcal.py

+ 5 - 0
sh/portfolio1.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+#ROOT={replace-path}
+ROOT=~/mchan927/finopt
+export PYTHONPATH=$ROOT
+python $ROOT/finopt/portfolio.py $ROOT/config/app.cfg

+ 4 - 0
sh/stop_opt_serve.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+ps ax | grep -i 'opt_serve' | grep python | grep -v grep | awk '{print $1}' | xargs kill -SIGTERM
+
+