Bladeren bron

add http service

add support for execDetails
add routines to wrap get HSI last trading day
laxaurus 7 jaren geleden
bovenliggende
commit
6dbd5d25d4

+ 1 - 1
src/comms/ibc/base_client_messaging.py

@@ -174,7 +174,7 @@ class AbstractGatewayListener(BaseMessageListener):
         """ generated source for method execDetails """
         raise NotImplementedException
    
-    def execDetailsEnd(self, event, message_value):  # reqId):
+    def execDetailsEnd(self, event, req_id, end_batch):  # reqId):
         """ generated source for method execDetailsEnd """
         raise NotImplementedException
    

+ 4 - 3
src/comms/ibgw/client_request_handler.py

@@ -42,11 +42,12 @@ class ClientRequestHandler(BaseMessageListener):
         
     def reqExecutions(self, event, exec_filter=None):
         
+        logging.info('ClientRequestHandler - reqExecutions exec_filter string=%s' % exec_filter)
         if exec_filter == 'null':
-            exec_filter = ExecutionFilter()
+            ef = ExecutionFilter()
         else:
-            ExecutionFilterHelper.kv2object(exec_filter, ExecutionFilter)
-        self.tws_connect.reqExecutions(0, exec_filter)
+            ef = ExecutionFilterHelper.kvstring2object(exec_filter, ExecutionFilter)
+        self.tws_connect.reqExecutions(0, ef)
     
     
     def reqIds(self, event, value=None):

+ 8 - 4
src/comms/ibgw/tws_event_handler.py

@@ -1,9 +1,10 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-from misc2.helpers import ContractHelper
+from misc2.helpers import ContractHelper, ExecutionHelper
 import logging
 import traceback
 from ib.ext.EWrapper import EWrapper
+import json
 
 
         
@@ -160,10 +161,13 @@ class TWS_event_handler(EWrapper):
         self.broadcast_event('bondContractDetails', vars())
 
     def execDetails(self, reqId, contract, execution):
-        self.broadcast_event('execDetails', {'req_id': reqId, 'contract': contract, 'execution': execution, 'end_batch': False})
-
+        contract_key= ContractHelper.makeRedisKeyEx(contract)
+        self.broadcast_event('execDetails', {'req_id': reqId, 'contract_key': contract_key, 
+                                             'execution': json.dumps(execution), 'end_batch': False})
+        logging.info('TWS_event_handler:execDetails. [%s] execution id [%d]:= exec px %f' % (execution.account, execution.ExecId , execution.Price))
+                                     
     def execDetailsEnd(self, reqId):
-        self.broadcast_event('execDetails', {'req_id': reqId, 'contract': None, 'execution': None, 'end_batch': True})
+        self.broadcast_event('execDetailsEnd', {'req_id': reqId, 'end_batch': True})
 
     def connectionClosed(self):
         logging.warn('TWS_event_handler: connectionClosed ******')

+ 3 - 2
src/comms/test/base_messaging.py

@@ -449,12 +449,13 @@ class SubscriptionListener(BaseMessageListener):
         logging.info("[%s] received on_kb_reached_last_offset content: [%s]" % (self.name, items))
         print "on_kb_reached_last_offset [%s] %s" % (self.name, items)
         
+
             
 def test_prosumer2(mode):
     
     if mode == 'A':
                 
-        topicsA = ['gw_subscription_changed', 'tickPrice']
+        topicsA = ['gw_subscription_changed', 'tickPrice', 'execDetails', 'execDetailsEnd']
         
         pA = Prosumer(name='A', kwargs={'bootstrap_host':'localhost', 'bootstrap_port':9092,
                                         'redis_host':'localhost', 'redis_port':6379, 'redis_db':0,
@@ -485,7 +486,7 @@ def test_prosumer2(mode):
 
         
     else:    
-        topicsB = ['gw_req_subscriptions', 'reqMktData']
+        topicsB = ['gw_req_subscriptions', 'reqMktData', 'reqExecutions']
         
         pB = Prosumer(name='B', kwargs={'bootstrap_host':'localhost', 'bootstrap_port':9092,
                                         'redis_host':'localhost', 'redis_port':6379, 'redis_db':0,

+ 18 - 0
src/config/ws.cfg

@@ -0,0 +1,18 @@
+[webSocketServer]
+name: 'WebSocketServer'
+bootstrap_host: 'vorsprung'
+bootstrap_port: 9092
+redis_host: 'localhost'
+redis_port: 6379
+redis_db: 0
+tws_host: 'vsu-bison'
+group_id: 'WS'
+session_timeout_ms: 10000
+clear_offsets:  False
+logconfig: {'level': logging.INFO, 'filemode': 'w', 'filename': '/tmp/ws.log'}
+topics: AbstractTableModel.TM_EVENTS + [PortfolioMonitor.EVENT_PORT_VALUES_UPDATED]
+seek_to_end: ['*']
+ws_flush_timeout: 2000
+ws_dirty_count: 20
+ws_port: 9001
+ws_webserver_cfg_path:'/home/laxaurus/workspace/finopt/src/config/ws_webserver.cfg'

+ 18 - 0
src/config/ws_webserver.cfg

@@ -0,0 +1,18 @@
+[global]
+server.socket_host: "0.0.0.0"
+server.socket_port: 8091
+log.screen: False
+log.error_file': '',
+log.access_file': ''
+[/]
+tools.sessions.on : True
+tools.staticdir.root : '/home/laxaurus/workspace/finopt/src/'
+
+[/static]
+tools.staticdir.on : True
+tools.staticdir.tmpl : './html'
+#tools.staticdir : './html'
+
+[/public]
+tools.staticdir.on: True
+tools.staticdir.dir : './html/public'

+ 29 - 4
src/finopt/optcal.py

@@ -141,9 +141,31 @@ def get_hk_holidays(year):
     except:
         traceback.print_exc()
     
-
-
-
+# this version has no need for users to import quantlib constants
+def get_HSI_last_trading_day_ex(month_number, year):
+    month_names = [January,
+                February,
+                March,
+                April,
+                May,
+                June,
+                July,
+                August,
+                September,
+                October,
+                November,
+                December,
+                ]      
+    hk_holidays =  {'2019': ['20190101','20190205','20190206','20190207','20190405',
+                             '20190419','20190420','20190422','20190501','20190513',
+                             '20190607','20190701','20190914','20191001','20191007','20191225','20191226']}
+    return get_HSI_last_trading_day(hk_holidays['2019'], month_names[month_number-1], year)
+    
+'''
+    holidays: holiday list in yyyymmdd format
+    month: month names from [January..December] -> Quantlib type
+    year: yyyy integer
+'''
 def get_HSI_last_trading_day(holidays, month, year):
 
     cal = HongKong()
@@ -387,7 +409,7 @@ if __name__ == '__main__':
 #     print chk.advance(Date(17, October, 2015), 1, 2)
     #print get_HSI_expiry(2016)
     
-     holidays = get_hk_holidays(2018)
+#      holidays = get_hk_holidays(2018)
 # 
 #     
 #     
@@ -410,3 +432,6 @@ if __name__ == '__main__':
 #         
 #     print holidays	
 #     print get_HSI_last_trading_day(['20170128'], 1, 2017)
+    for i in range(11):
+      print get_HSI_last_trading_day_ex(i+1, 2019)
+      

+ 7 - 2
src/hkex/daily_download.py

@@ -82,9 +82,14 @@ class HkexStatDownloader():
 
 
 
+'''
 
-    
-    
+    Download daily stat files from HKEX website
+    The script requires a config file to run 
+    check finopt/config/daily_download.cfg for
+    details
+
+'''
     
 
 if __name__ == '__main__':

+ 285 - 103
src/html/client_g.html

@@ -6,13 +6,118 @@
   <script type="text/javascript" src="https://www.google.com/jsapi"></script>  
 
 </head>
+<style>
+ul#menu li {
+  display:inline;
+}
 
+table.minimalistBlack {
+  border: 1px solid #000000;
+  width: 100%;
+  text-align: left;
+  border-collapse: collapse;
+}
+table.minimalistBlack td, table.minimalistBlack th {
+  border: 1px solid #000000;
+  padding: 5px 4px;
+}
+table.minimalistBlack tbody td {
+  font-size: 12px;
+}
+table.minimalistBlack thead {
+  background: #CFCFCF;
+  background: -moz-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
+  background: -webkit-linear-gradient(top, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
+  background: linear-gradient(to bottom, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
+  border-bottom: 3px solid #000000;
+}
+table.minimalistBlack thead th {
+  font-size: 12px;
+  font-weight: bold;
+  color: #000000;
+  text-align: left;
+}
+table.minimalistBlack tfoot {
+  font-size: 13px;
+  font-weight: bold;
+  color: #000000;
+  border-top: 3px solid #000000;
+}
+table.minimalistBlack tfoot td {
+  font-size: 13px;
+}
+.container{
+  max-width: 20px;
+  margin: o auto;
+  padding: 5px;
+}
+.bg-led{ 
+	padding: 5px 8px;
+    max-width: 20px;
+  	margin: o auto;
+  	
+    background-color:green;
+}
+.chartWithMarkerOverlay {
+    position: relative;
+    width: 700px;
+}
+.overlay-text {
+    width: 200px;
+    height: 200px;
+    position: absolute;
+    top: 30px;   /* chartArea top */
+    left: 200px; /* chartArea left */
+}
+.overlay-marker {
+    width: 50px;
+    height: 50px;
+    position: absolute;
+    top: 375px;   /* chartArea top */
+    left: 350px; /* chartArea left */
+    color: #000066;
+    font: 15px arial;
+ 
+}
+</style>
 <body>
-  <input type="radio" name='acct' id="acct" value="U9050568" checked="checked"> U9050568<br>
-  <input type="radio" name='acct' id="acct" value="U8080985"> U8080985<br>
-   <button id="reload_port">Reload Portfolio</button>
-   <div id="table_div" style="height:600"></div>
-   <div id="chart_div"></div> 
+	<ul id="menu">
+		<li><input type="radio" name='acct' id="acct" value="U9050568" checked="checked"> U9050568</li>
+		<li><input type="radio" name='acct' id="acct" value="U8080985"> U8080985</li>
+		<li><button id="reload_port">Reload Portfolio</button></li>
+		<li class="bg-led indicator"></li>
+	</ul>  
+		
+   
+	<table id='port_sum' class="minimalistBlack">
+		<thead>
+			<tr>
+				<th>Total Delta/Theta</th>
+				<th>Futures Delta/Theta</th>
+				<th>Call Delta/Theta</th>
+				<th>Put Delta/Theta</th>
+				<th>Calls/Puts</th>
+				<th>PL / Potential Gain<div class="container"></th>
+				<th>HSIF Curr / Next Mth</th>
+				<th>change %</th>
+			</tr>
+		</thead>
+		
+		<tbody>
+			<tr>
+				<td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td></tr>
+			<tr>
+				<td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td><td>(nan)</td></tr>
+			<tr>
+		</tbody>
+		</tr>
+	</table>
+   
+	<div id="chart_div"></div> 
+	<div id="table_div" style="height:400"></div>
+	<div class="overlay-marker">
+	<img src="public/green_marker.png" height="50"><div id='spot'></div>
+	</div>   
    <button id="change-btn">change columns</button>
    <button id="test-btn">test</button>
   <form onsubmit="onSubmit(); return false;">
@@ -40,11 +145,16 @@
 	    qs = "input[id=acct][value='" + account + "']"
 	    document.querySelectorAll(qs)[0].checked=true;
 
+	    
+	    // test: setCellValue('port_sum', 1, 2, 'x6yz');
+	    
 	});
   
   
 	$('#test-btn').click(function(){
-		alert('hereee');
+		$('#port_sum').find("tr:eq(1)").find("td:eq(1)").html('new stuff');
+		
+		alert($('#port_sum').find("tr:eq(1)").find("td:eq(1)").html());
 	});  
 	$('#reload_port').click(function(){
 		alert('Reloading portfolio...');
@@ -62,10 +172,12 @@
 
     var chartOptions = {
             width: 800,
-            height: 500,
+            height: 400,
             legend: { position: 'right', maxLines: 3 },
-            bar: { groupWidth: '75%' },
+            bar: { groupWidth: '85%' },
             isStacked: true,allowHtml: true,
+
+
          /*series: {
     			0:{color:'lightgreen'},
     			1:{color:'black'},
@@ -87,8 +199,19 @@
 	google.setOnLoadCallback(init);
 	
 
+	function setCellValue(table, rowNum, colNum, newValue)
+	{
+		//$('#port_sum').find("tr:eq(1)").find("td:eq(1)").html('new stuff');
+	    $('#'+table).find('tr:eq('+rowNum+')').find('td:eq(' + colNum + ')').html(newValue);
+	    
+	};	
 	
-	
+	function getCellValue(table, rowNum, colNum)
+	{
+		//$('#port_sum').find("tr:eq(1)").find("td:eq(1)").html('new stuff');
+	    return $('#'+table).find('tr:eq('+rowNum+')').find('td:eq(' + colNum + ')').html();
+	    
+	};	
 
 	
 	function getRandomInt(min, max) {
@@ -116,6 +239,7 @@
 	    	 	    
 		    numF.format(tableChart.data, 2);  //avg cost
 		    numF.format(tableChart.data, 3);  //market value
+		    numF.format(tableChart.data, 4);  //avg px
 		    colorF.format(tableChart.data, 6); // position
 		    percentF.format(tableChart.data, 7); //delta		
 		    
@@ -124,116 +248,174 @@
 		    numF.format(tableChart.data, 12); // position gamma
 		    colorGF.format(tableChart.data, 13); //unreal p/l
 		    barF.format(tableChart.data, 14) //% gain loss
+		    percentF.format(tableChart.data, 15); //delta
 		
 	}	   
 	   
 
     var ws;
+    var ws_url = "ws://localhost:9001/";
+    var tableChart;
 	
-    function init() {
-
-      // Connect to Web Socket
-      ws = new WebSocket("ws://localhost:9001/");
-   
-      // Set event handlers.
-      ws.onopen = function() {
-    	var account = $('input[id=acct]:checked').val();
-    	  
-    	  
-        output("onopen account = " + account );
-        
-        ws.send(JSON.stringify({event: 'event_tm_request_table_structure', target_resource:{'class': 'Portfolio'}, 'account': account}));
-        ws.send(JSON.stringify({event: 'event_tm_request_table_structure', target_resource:{'class': 'PortfolioColumnChartTM'}, 'account': account}));
-        
-      };
-      
-      ws.onmessage = function(e) {
-        // e.data contains received string.
-        
-        //output("onmessage: " + e.data);
-        d1=  JSON.parse(e.data);
-		console.log(d1);
+	  function init() {
+	
+	    var account = $('input[id=acct]:checked').val();
+	    
+	    // Connect to Web Socket
+	    ws = new WebSocket(ws_url);
+	 
+	    // Set event handlers.
+	    ws.onopen = function() {
+	  	var account = $('input[id=acct]:checked').val();
+	  	  
+	  	  
+	      output("onopen account = " + account );
+	      
+	      ws.send(JSON.stringify({event: 'event_tm_request_table_structure', target_resource:{'class': 'Portfolio'}, 'account': account}));
+	      ws.send(JSON.stringify({event: 'event_tm_request_table_structure', target_resource:{'class': 'PortfolioColumnChartTM'}, 'account': account}));
+	      
+	    };
+	    
+	    ws.onmessage = function(e) {
+			// e.data contains received string.
+			// signal incoming by flashing a green box
+			$('.indicator').fadeIn(50).fadeOut(50); 
+			
+			try {
+				d1=  JSON.parse(e.data);
+				
+			}
+			catch(err) {
+				output("onmessage:json parse exception: value= " + e.data);
+				console.log('error parsing e.data' + e.data);
+			}
 
-		
-    	// 2019.1 if the messageis not intended 
-	if (d1.event == 'event_tm_table_structure_changed' && d1.account != account) 
-		return;
-	else if (d1.event == 'event_tm_table_row_updated' && d1['value']['source']['account'] != account) 
-		return;
+			
+				
+			 
+    	
+		  	// 2019.1 if the messageis not intended
+			if (d1.event == 'event_tm_table_structure_changed' && d1.account != account) 
+				return;
+			else if (d1.event == 'event_tm_table_row_updated' && d1['value']['source']['account'] != account) 
+				return;
 		
 		
-        if (d1.event == "event_tm_table_structure_changed"){
-
-        	
-        	
-			if (d1.source.class == 'Portfolio'){
-				tableChart.data = new google.visualization.DataTable(d1.value);
-				tableChart.view = new google.visualization.DataView(tableChart.data);
-				tableChart.view.setRows(tableChart.view.getFilteredRows([{column: 15, test: function(value, row, column, table) {
-			        return (value == 'HSI' || value == 'MHI')
-			    }},
-			    {column: 6, test: function(value, row, column, table) {
-			        return (value != 0)
-			    }},
-			     
-			    ]));
-
-
-
-	    		setupFormatter(tableChart.data);
-	    		tableChart.chart = new google.visualization.Table(document.getElementById('table_div'));
-	    		tableChart.chart.draw(tableChart.view, tableChart.options);
-				output('number of rows: ' + tableChart.data.getNumberOfRows());
+			if (d1.event == "event_tm_table_structure_changed"){
 				
-			} else if (d1.source.class == 'PortfolioColumnChartTM'){
-				columnChart.data = new google.visualization.DataTable(d1.value);
-				console.log(d1.account);
-				columnChart.view = new google.visualization.DataView(columnChart.data);
-				columnChart.chart = new google.visualization.ColumnChart(
-				        document.getElementById('chart_div'));
-				columnChart.chart.draw(columnChart.view, columnChart.options);
-			}
-           
-        } else if (d1.event == 'event_tm_table_row_updated'){
-
-			console.log(d1.value.row.toString()+ ':' + d1.value.row_values[0]['v']);
-			for (var c=2; c < d1.value.row_values.length; c++){
-				tableChart.data.setCell(d1.value.row, c, d1.value.row_values[c]["v"]);
-
+				if (d1.source.class == 'Portfolio'){
+					tableChart.data = new google.visualization.DataTable(d1.value);
+					tableChart.view = new google.visualization.DataView(tableChart.data);
+					tableChart.view.setRows(tableChart.view.getFilteredRows([{column: 16, test: function(value, row, column, table) {
+				        return (value == 'HSI' || value == 'MHI')
+				    }},
+				    {column: 6, test: function(value, row, column, table) {
+				        return (value != 0)
+				    }},
+				     
+				    ]));
+			  		setupFormatter(tableChart.data);
+			  		tableChart.chart = new google.visualization.Table(document.getElementById('table_div'));
+			  		tableChart.chart.draw(tableChart.view, tableChart.options);
+					output('number of rows: ' + tableChart.data.getNumberOfRows());
+					
+				} else if (d1.source.class == 'PortfolioColumnChartTM'){
+					columnChart.data = new google.visualization.DataTable(d1.value);
+					console.log(d1.account);
+					columnChart.view = new google.visualization.DataView(columnChart.data);
+					columnChart.chart = new google.visualization.ColumnChart(
+					        document.getElementById('chart_div'));
+					columnChart.chart.draw(columnChart.view, columnChart.options);
+					google.visualization.events.addListener(columnChart.chart, 'ready', placeIndexMarker);
+				}
+				        
+			} else if (d1.event == 'event_tm_table_row_updated'){
+				
+				console.log(d1.value.row.toString()+ ':' + d1.value.row_values[0]['v']);
+				for (var c=2; c < d1.value.row_values.length; c++){
+					tableChart.data.setCell(d1.value.row, c, d1.value.row_values[c]["v"]);
+				
+				}
+				//data.setCell(d1.value.row, 1, d1.value.row_values[0]['v']);
+				
+				tableChart.options.sortColumn = tableChart.chart.getSortInfo().column;
+				tableChart.chart.draw(tableChart.view, tableChart.options);
+				        
+				
+			} else if (d1.event == 'event_tm_table_row_inserted'){
+				var newRow = d1.value.row_values.map(function(x){
+					return x['v'];
+				});
+				output('adding new row: ' + newRow.toString());
+				data.addRow(newRow);
+				options.sortColumn = table.getSortInfo().column;
+				table.draw(view, options);
+				
+			} else if (d1.event == 'event_port_values_updated' && d1.account == account){
+			 	setPortSummaryTableVal(d1.value);
+			} else if (d1.event == 'tickPrice'){
+					
+				if (d1.current_or_next == 'current'){
+					setCellValue('port_sum', 1, 6, d1.price);
+					setCellValue('port_sum', 1, 7, d1.change.toFixed(2));
+				}
+				else{ // next month
+					setCellValue('port_sum', 2, 6, d1.price);
+					setCellValue('port_sum', 2, 7, d1.change.toFixed(2));
+				}
+				if (columnChart){
+					placeIndexMarker();
+				}				
+			     
 			}
-			//data.setCell(d1.value.row, 1, d1.value.row_values[0]['v']);
-			
-			tableChart.options.sortColumn = tableChart.chart.getSortInfo().column;
-			tableChart.chart.draw(tableChart.view, tableChart.options);
-           
-
-        } else if (d1.event == 'event_tm_table_row_inserted'){
-        	var newRow = d1.value.row_values.map(function(x){
-        		return x['v'];
-        	});
-        	output('adding new row: ' + newRow.toString());
-        	data.addRow(newRow);
-        	options.sortColumn = table.getSortInfo().column;
-        	table.draw(view, options);
-        } else if (d1.event == 'event_port_values_updated'){
-        	
-        	console.log(d1.value.port_values);
-        }
-        
-      };
+		};
       
-      ws.onclose = function() {
-        output("onclose");
-      };
+		ws.onclose = function() {
+		  output("onclose");
+		};
+		
+		ws.onerror = function(e) {
+		  output("onerror");
+		  console.log(e)
+		};
 
-      ws.onerror = function(e) {
-        output("onerror");
-        console.log(e)
-      };
+	}
+    
 
+	function placeIndexMarker(){
+		// get the current month futures spot px
+		var spot = getCellValue('port_sum', 1, 6);
+		var cli = columnChart.chart.getChartLayoutInterface();
+		//top = cli.getChartAreaBoundingBox().top;
+		//height = cli.getChartAreaBoundingBox().height;
+		top = cli.getBoundingBox('chartarea').top;
+		height = cli.getBoundingBox('chartarea').height;
+		
+		document.querySelector('.overlay-marker').style.top = Math.floor(top - height) + "px";
+		document.querySelector('.overlay-marker').style.left = Math.floor(cli.getXLocation(spot) - 25) + "px";
+	}
+	  
+    function setPortSummaryTableVal(port_values){
+    	var val2cellDict = {
+    			9000: [1,0], 
+    			9001: [1,1],
+    			9002: [1,2],
+    			9003: [1,3],
+    			9031: [1,4],
+    			9040: [1,5],
+    			9010: [2,0],
+    			9012: [2,2],
+    			9013: [2,3],
+    			9032: [2,4],
+    			9041: [2,5]
+    					}
+    	
+    	for(var port_id in val2cellDict) {
+		 	//console.log('in setPortSummaryTableVal ' + String(port_id) + ", ");
+    		setCellValue('port_sum', val2cellDict[port_id][0], val2cellDict[port_id][1], port_values[port_id]);
+    	}
     }
     
-
     
     function onSubmit() {
       var input = document.getElementById("input");

+ 23 - 1
src/misc2/helpers.py

@@ -7,6 +7,7 @@ import threading
 import ConfigParser
 from ib.ext.Contract import Contract
 from ib.ext.Order import Order
+from ib.ext.Execution import Execution
 from ib.ext.ExecutionFilter import ExecutionFilter
 import copy
 
@@ -22,7 +23,8 @@ class BaseHelper():
 
 
     @staticmethod
-    def kv2object(kv, Object):   
+    def kv2object(kv, Object):
+           
         o = Object()
         map(lambda x: o.__setattr__(x, kv[x].encode('ascii') if type(kv[x]) == unicode else kv[x]), kv.keys())
         return o
@@ -98,6 +100,18 @@ class ExecutionFilterHelper(BaseHelper):
         new_filter.m_time = executionFilterTuple[6]
         return new_filter
 
+class ExecutionHelper(BaseHelper):
+    
+
+    @staticmethod
+    def execution2kvstring(execution):
+        return json.dumps(execution.__dict__)
+
+
+    @staticmethod
+    def execution2kv(execution):
+        return copy.deepcopy(execution.__dict__)
+
 
 class ContractHelper(BaseHelper):
     
@@ -324,3 +338,11 @@ class ConfigMap():
         return kwargs
     
 
+
+class LoggerNoBaseMessagingFilter(logging.Filter):
+    def filter(self, record):
+        return not (record.getMessage().startswith('BaseConsumer') or 
+                    record.getMessage().startswith('BaseProducer') or
+                    record.getMessage().startswith('PortfolioMonitor:tds_event_tick_updated'))
+
+

+ 36 - 7
src/rethink/option_chain.py

@@ -287,25 +287,54 @@ class OptionsChain(Publisher):
             if ospot is a non-number, attempt to get option's last px in the Option 
         '''
         
+        
+        def quick_check_number(n, varname):
+        
+            if n == None:
+                logging.warn('WARNING: %s is None' % varname)
+                return False
+            elif math.isnan(n):
+                logging.warn('WARNING: %s is nan' % varname)
+                return False
+            return True
+            
+                
+#         if not quick_check_number(uspot, 'spot'):
+#             logging.warn('>>> irregular value in spot for %s ' % ContractHelper.makeRedisKeyEx(option.get_contract()))
+#                           
+#         if not quick_check_number(premium, 'premium'):
+#             logging.warn('>>> irregular value in premium for %s ' % ContractHelper.makeRedisKeyEx(option.get_contract()))
+        
+        
+        # if uspot is not a number, try to get its last px
         uspot = uspot if not math.isnan(uspot) else self.get_underlying().get_tick_value(4)
-        logging.info('************* cal_option_greeks option= %s' % ContractHelper.makeRedisKeyEx(option.get_contract()))
+        # if px is still invalid, try close px
+        uspot = uspot if not uspot is None else self.get_underlying().get_tick_value(6)
+        
+        #logging.info('************* cal_option_greeks option= %s' % ContractHelper.makeRedisKeyEx(option.get_contract()))
         premium = premium if not math.isnan(premium) else option.get_tick_value(4)
+        premium = premium if not premium is None else option.get_tick_value(6)
+        
         
-        logging.info('*************after>>>>>>> uspot=%8.2f option last=%8.2f pass premium=%8.2f' % (uspot, option.get_tick_value(4), premium))
-        if uspot is None:
+        
+        # at this stage both uspot and premium should have valid values, 
+        # if not, just abort the computation
+        if uspot is None or premium is None:
+            logging.info('************ [%s] either uspot or premium or both are found to be None **** not calculating anything'% ContractHelper.makeRedisKeyEx(option.get_contract()))
             return OptionsChain.EMPTY_GREEKS
+        
         o = option.get_contract()
         
 
             
             
         try:
-            logging.info('OptionChain:cal_option_greeks. uspot->%8.4f, premium last->%8.4f ' % (uspot, option.get_tick_value(4)))
-            logging.info('OptionChain:cal_option_greeks. o.m_strike %8.4f, o.m_right %s, valuation_date %s, o.m_expiry %s, self.rate %8.4f , self.div  %8.4f, self.trade_vol %8.4f ' % 
-                        (o.m_strike, o.m_right, valuation_date,  o.m_expiry, self.rate, self.div, self.trade_vol))
+            #logging.info('OptionChain:cal_option_greeks. uspot->%8.4f, premium last->%8.4f ' % (uspot, option.get_tick_value(4)))
+            #logging.info('OptionChain:cal_option_greeks. o.m_strike %8.4f, o.m_right %s, valuation_date %s, o.m_expiry %s, self.rate %8.4f , self.div  %8.4f, self.trade_vol %8.4f ' % 
+            #            (o.m_strike, o.m_right, valuation_date,  o.m_expiry, self.rate, self.div, self.trade_vol))
             iv = cal_implvol(uspot, o.m_strike, o.m_right, valuation_date, 
                                   o.m_expiry, self.rate, self.div, self.trade_vol, premium)
-            logging.info('OptionChain:cal_option_greeks. cal results:iv=> %s' % str(iv))
+            #logging.info('OptionChain:cal_option_greeks. cal results:iv=> %s' % str(iv))
         except RuntimeError:
             logging.warn('OptionChain:cal_option_greeks. Quantlib threw an error while calculating implied vol: use intrinsic: uspot->%8.2f premium->%8.2f strike->%8.2f right->%s sym->%s' % 
                          (uspot, premium, o.m_strike, o.m_right, o.m_symbol))

+ 60 - 34
src/rethink/portfolio_item.py

@@ -138,6 +138,7 @@ class PortfolioItem():
         try:
             assert contract_key == self.contract_key
             spot_px = self.instrument.get_tick_value(4)
+            spot_px = spot_px if not spot_px is None else self.instrument.get_tick_value(6)
             qty = self.get_quantity()
             if qty == 0:
                 self.set_port_field(PortfolioItem.POSITION_DELTA, 0.0)
@@ -175,6 +176,8 @@ class PortfolioItem():
                     if qty < 0:
                         market_value= spot_px * qty * multiplier
                         potential_gain = market_value - unreal_pl * (1.0 if unreal_pl < 0 else 0)
+                    elif qty > 0:
+                        potential_gain = 0
                         
                 except ZeroDivisionError, TypeError:
                     # caught error for cases where get_average_cost and quantity may be None
@@ -267,6 +270,9 @@ class Portfolio(AbstractTableModel):
     def get_object_name(self):
         return {'account': self.account, 'id': id(self), 'class': self.__class__.__name__}
     
+    def get_account(self):
+        return self.account
+    
     def is_contract_in_portfolio(self, contract_key):
         return self.get_portfolio_port_item(contract_key)
             
@@ -319,13 +325,18 @@ class Portfolio(AbstractTableModel):
         return self.port['opt_chains']
 
     def calculate_item_pl(self, contract_key):
-        self.port['port_items'][contract_key].calculate_pl(contract_key)
+        try:
+            self.port['port_items'][contract_key].calculate_pl(contract_key)
+        except:
+            logging.error('PortfolioItem:calculate_item_pl *** ERROR: port a/c [%s], contract_key [%s]' % (self.get_account(), contract_key))
         
     def calculate_port_pl(self):
 
 
-        p1_items = filter(lambda x: x[1].get_symbol_id() in PortfolioRules.rule_map['interested_position_types']['symbol'], self.port['port_items'].items())
-        p2_items = filter(lambda x: x[1].get_instrument_type() in  PortfolioRules.rule_map['interested_position_types']['instrument_type'], p1_items)
+        p0_items = filter(lambda x: x[1].get_symbol_id() in PortfolioRules.rule_map['interested_position_types']['symbol'], self.port['port_items'].items())
+        p1_items = filter(lambda x: x[1].get_instrument_type() in  PortfolioRules.rule_map['interested_position_types']['instrument_type'], p0_items)
+        p2_items = filter(lambda x: x[1].get_quantity() <> 0, p1_items)
+
         
         port_v = {
               Portfolio.TOTAL_DELTA     : 0.0,
@@ -344,32 +355,43 @@ class Portfolio(AbstractTableModel):
             } 
         def cal_port(x_tuple):
     
-            x = x_tuple[1]
-            if x.get_right() == 'C':
-                port_v[Portfolio.TOTAL_DELTA_C] += x.get_port_field(PortfolioItem.POSITION_DELTA)
-                port_v[Portfolio.TOTAL_THETA_C] += x.get_port_field(PortfolioItem.POSITION_THETA)
-                ##
-                # hard coded logic
-                #
-                port_v[Portfolio.NUM_CALLS] += (
-                    x.get_quantity() * PortfolioRules.rule_map['option_structure'][x.get_symbol_id()]['multiplier'] / 50)
-
-            elif x.get_right() == 'P':
-                port_v[Portfolio.TOTAL_DELTA_P] += x.get_port_field(PortfolioItem.POSITION_DELTA)
-                port_v[Portfolio.TOTAL_THETA_P] += x.get_port_field(PortfolioItem.POSITION_THETA)
-                port_v[Portfolio.NUM_PUTS] += (
-                    x.get_quantity() * PortfolioRules.rule_map['option_structure'][x.get_symbol_id()]['multiplier'] / 50)
-            elif x.get_instrument_type() == 'FUT':
-                port_v[Portfolio.TOTAL_DELTA_F] += x.get_port_field(PortfolioItem.POSITION_DELTA)
-                
-            port_v[Portfolio.TOTAL_DELTA] += x.get_port_field(PortfolioItem.POSITION_DELTA)
-            port_v[Portfolio.TOTAL_THETA] += x.get_port_field(PortfolioItem.POSITION_THETA)
-            port_v[Portfolio.TOTAL_GAIN_LOSS] += x.get_port_field(PortfolioItem.UNREAL_PL)
-            port_v[Portfolio.TOTAL_POTENTIAL_GAIN] += x.get_port_field(PortfolioItem.POTENTIAL_GAIN)
             try:
-                port_v[Portfolio.TOTAL_GAMMA_PERCENT] += x.get_port_field(PortfolioItem.GAMMA_PERCENT)
+    
+                x = x_tuple[1]
+                if x.get_right() == 'C':
+                    port_v[Portfolio.TOTAL_DELTA_C] += x.get_port_field(PortfolioItem.POSITION_DELTA)
+                    port_v[Portfolio.TOTAL_THETA_C] += x.get_port_field(PortfolioItem.POSITION_THETA)
+                    ##
+                    # hard coded logic
+                    #
+                    port_v[Portfolio.NUM_CALLS] += (
+                        x.get_quantity() * PortfolioRules.rule_map['option_structure'][x.get_symbol_id()]['multiplier'] / 50)
+    
+                elif x.get_right() == 'P':
+                    port_v[Portfolio.TOTAL_DELTA_P] += x.get_port_field(PortfolioItem.POSITION_DELTA)
+                    port_v[Portfolio.TOTAL_THETA_P] += x.get_port_field(PortfolioItem.POSITION_THETA)
+                    port_v[Portfolio.NUM_PUTS] += (
+                        x.get_quantity() * PortfolioRules.rule_map['option_structure'][x.get_symbol_id()]['multiplier'] / 50)
+                elif x.get_instrument_type() == 'FUT':
+                    port_v[Portfolio.TOTAL_DELTA_F] += x.get_port_field(PortfolioItem.POSITION_DELTA)
+                    
+                port_v[Portfolio.TOTAL_DELTA] += x.get_port_field(PortfolioItem.POSITION_DELTA)
+                port_v[Portfolio.TOTAL_THETA] += x.get_port_field(PortfolioItem.POSITION_THETA)
+    
+                port_v[Portfolio.TOTAL_GAIN_LOSS] += x.get_port_field(PortfolioItem.UNREAL_PL)
+                port_v[Portfolio.TOTAL_POTENTIAL_GAIN] += x.get_port_field(PortfolioItem.POTENTIAL_GAIN)
+                
+                # not used for the time being
+                #port_v[Portfolio.TOTAL_GAMMA_PERCENT] += x.get_port_field(PortfolioItem.GAMMA_PERCENT)
+    
+                for k,v in port_v.iteritems():
+                    #logging.info('>>>>>>>>>CHECK>>>>> %s %0.2f %d' % (k, v, math.isnan(v)))
+                    port_v[k] = 0.0 if math.isnan(v) else port_v[k]
+                
+            
             except:
-                logging.error('Portfolio:calculate_port_pl. Error calcuting gamma percent %s' % traceback.format_exc())
+                logging.error('Portfolio:calculate_port_pl. **** ERROR %s' % traceback.format_exc())
+                
                 
             
         map(cal_port, p2_items)            
@@ -401,15 +423,17 @@ class Portfolio(AbstractTableModel):
 
         #df = pd.DataFrame(data = map(format_port_data, [x for x in self.port['port_items'].iteritems()]),
         #                  columns = format_port_header(self.port['port_items'].iteritems()[0].keys()))
-        data = map(format_port_data, [x for x in self.port['port_items'].iteritems()])
-        y = list(self.port['port_items'])[0]
-        z = self.port['port_items'][y]
-        columns = format_port_header(z)
-        pd.set_option('display.max_columns', 50)
-        df1 = pd.DataFrame(data = data, columns = columns)
+        try:
+
+            data = map(format_port_data, [x for x in self.port['port_items'].iteritems()])
+            y = list(self.port['port_items'])[0]
+            z = self.port['port_items'][y]
+            columns = format_port_header(z)
+            print columns
+            pd.set_option('display.max_columns', 50)
+            df1 = pd.DataFrame(data = data, columns = columns)
         
         # print portfolio items
-        try:
             print '\n\n--------- Portfolio %s --------\n' % self.get_object_name()['account']
             print df1
             
@@ -435,6 +459,7 @@ class Portfolio(AbstractTableModel):
                   ('delta', 'Delta', 'number'), ('theta', 'Theta', 'number'), ('gamma', 'Gamma', 'number'), 
                   ('pos_delta', 'P. Delta', 'number'), ('pos_theta', 'P. Theta', 'number'), ('gamma_percent', 'P. Gamma', 'number'), 
                   ('unreal_pl', 'Unreal P/L', 'number'), ('percent_gain_loss', '% gain/loss', 'number'),
+                  ('ivol', 'ivol', 'number'),
                   ('symbolid', 'Sym Id', 'string')
                   ]  
     def update_ckey_row_xref(self, contract_key, port_item):
@@ -502,6 +527,7 @@ class Portfolio(AbstractTableModel):
              {'v': handle_NaN(x[1].get_port_field(PortfolioItem.GAMMA_PERCENT))},
              {'v': handle_NaN(x[1].get_port_field(PortfolioItem.UNREAL_PL))},
              {'v': handle_NaN(x[1].get_port_field(PortfolioItem.PERCENT_GAIN_LOSS))},
+             {'v': handle_NaN(x[1].get_instrument().get_tick_value(Option.IMPL_VOL))},
              {'v': x[1].get_symbol_id()}
              ]
         return rf     

+ 52 - 11
src/rethink/portfolio_monitor.py

@@ -6,8 +6,12 @@ import copy
 from optparse import OptionParser
 from time import sleep
 import time
-from misc2.helpers import ContractHelper
-from finopt.instrument import Symbol, Option
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+from ib.ext.Execution import Execution
+from ib.ext.ExecutionFilter import ExecutionFilter
+from misc2.helpers import ContractHelper, LoggerNoBaseMessagingFilter, ExecutionFilterHelper
+from finopt.instrument import Symbol, Option, InstrumentIdMap
 from rethink.option_chain import OptionsChain
 from rethink.tick_datastore import TickDataStore
 from rethink.portfolio_item import PortfolioItem, PortfolioRules, Portfolio
@@ -55,6 +59,7 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
     def start_engine(self):
         self.twsc.start_manager()
         self.twsc.reqPositions()
+ 
      
         try:
             def print_menu():
@@ -66,6 +71,8 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
                 menu['5']="Table chart JSON"
                 menu['6']="Table index mapping"
                 menu['7']="Position Distribution JSON"
+                menu['8']="Update TDS table entries by inputting '8 <key> <field> <value>'"
+                menu['a']="request exeutions"
                 menu['9']="Exit"
         
                 choices=menu.keys()
@@ -78,7 +85,7 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
                     print_menu()
                     while 1:
                         resp = sys.stdin.readline()
-                        response[0]= resp.strip('\n')
+                        response[0] = resp.strip('\n')
                         #print response[0]
                                 
             
@@ -98,6 +105,7 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
                             
                             port.calculate_port_pl()
                             port.dump_portfolio()
+                            self.notify_port_values_updated(port.get_account(), port)
                             #print ''.join('%d:[%6.2f]\n' % (k, v) for k, v in port.calculate_port_pl().iteritems())
                     elif selection == '3': 
                         
@@ -116,7 +124,26 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
                             pc = PortfolioColumnChart(self.portfolios[acct])
                             print pc.get_JSON()
                             print pc.get_xy_array()
+                    elif selection == '8':
+                        try:
                             
+                            contract_key = response[1]
+                            field = int(response[2])
+                            if field not in (InstrumentIdMap.idmap.keys()):
+                                raise Exception('invalid field')
+                            price = float(response[3])
+                            logging.info('PortfolioMonitor: manual adjustment to tds table')
+                            logging.info('PortfolioMonitor: [%s] field[%s]:%s')
+                            self.tds.set_symbol_tick_price(self, contract_key, field, price)
+                        except:
+                            print "error in input values"
+                            continue
+                    elif selection == 'a':
+                        today = datetime.now()
+                        month = int(today.strftime('%m'))
+                        past =  today + relativedelta(months=-1)
+                        exec_filter = ExecutionFilterHelper.kv2object({'m_time': past.strftime('%Y%m%d %H:%M:%S')}, ExecutionFilter)
+                        self.twsc.reqExecutions(exec_filter)
                     elif selection == '9': 
                         self.twsc.gw_message_handler.set_stop()
                         break
@@ -299,7 +326,7 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
     def tds_event_tick_updated(self, event, contract_key, field, price, syms):
 
         
-        if field not in [Symbol.ASK, Symbol.BID, Symbol.LAST]:
+        if field not in [Symbol.ASK, Symbol.BID, Symbol.LAST, Symbol.CLOSE]:
             return
         
         
@@ -341,10 +368,12 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
                         self.tds.set_symbol_analytics(key_greeks[0], Option.GAMMA, key_greeks[1][Option.GAMMA])
                         self.tds.set_symbol_analytics(key_greeks[0], Option.THETA, key_greeks[1][Option.THETA])
                         self.tds.set_symbol_analytics(key_greeks[0], Option.VEGA, key_greeks[1][Option.VEGA])
+                        self.tds.set_symbol_analytics(key_greeks[0], Option.PREMIUM, key_greeks[1][Option.PREMIUM])
                         
-                        self.portfolios[acct].calculate_item_pl(key_greeks[0])
-                        self.notify_table_model_changes(acct, self.portfolios[acct], key_greeks[0], mode='U')
-                        logging.info('PortfolioMonitor:tds_event_tick_updated. Position updated: %s:[%d]' % (key_greeks[0], self.portfolios[acct].ckey_to_row(key_greeks[0])))
+                        if self.portfolios[acct].is_contract_in_portfolio(contract_key):
+                            self.portfolios[acct].calculate_item_pl(key_greeks[0])
+                            self.notify_table_model_changes(acct, self.portfolios[acct], key_greeks[0], mode='U')
+                            logging.info('PortfolioMonitor:tds_event_tick_updated. Position updated: %s:[%d]' % (key_greeks[0], self.portfolios[acct].ckey_to_row(key_greeks[0])))
                     
                     if results:
                         #logging.info('PortfolioMonitor:tds_event_tick_updated ....before map')
@@ -384,7 +413,13 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
         if not self.is_interested_contract_type(contract_key):
             return
 
-    
+            
+    def execDetails(self, event, req_id, contract_key, execution, end_batch):
+        logging.info("PortfolioMonitor:execDetails: [%s] received %s content:[%s]" % (event, contract_key, execution))
+        
+    def execDetailsEnd(self, event, req_id, end_batch):  # reqId):
+        logging.info("PortfolioMonitor:execDetailsEnd: [%s] received %d end:[%d]" % (event, req_id, end_batch))
+        
  
     def position(self, event, account, contract_key, position, average_cost, end_batch):
 
@@ -426,6 +461,11 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
             
     def updateAccountTime(self, event, timestamp):
         self.raw_dump(event, vars())
+        logging.info('PortfolioMonitor:updateAccountTime %s' % timestamp)
+        for port in self.portfolios.values():
+            port.calculate_port_pl()
+            self.notify_port_values_updated(port.get_account(), port)
+        
         
     def accountDownloadEnd(self, event, account):  # accountName):
         self.raw_dump(event, vars())
@@ -502,7 +542,8 @@ class PortfolioMonitor(AbstractGatewayListener, AbstractPortfolioTableModelListe
         try:
             self.get_kproducer().send_message(PortfolioMonitor.EVENT_PORT_VALUES_UPDATED,
                                                json.dumps({'account': account,
-                                                     'port_values': self.portfolios[account].get_potfolio_values()}))
+                                                     'port_values': self.portfolios[account].get_potfolio_values()}),
+                                               )
         except:
             logging.error('**** Error PortfolioMonitor:notify_port_values_updated. %s' % traceback.format_exc() )
         
@@ -525,7 +566,7 @@ if __name__ == '__main__':
       'session_timeout_ms': 10000,
       'clear_offsets':  False,
       'logconfig': {'level': logging.INFO, 'filemode': 'w', 'filename': '/tmp/pm.log'},
-      'topics': ['position', 'positionEnd', 'tickPrice', 'update_portfolio_account', 'event_tm_request_table_structure', AbstractTableModel.EVENT_TM_TABLE_STRUCTURE_CHANGED],
+      'topics': ['position', 'positionEnd', 'tickPrice', 'execDetails', 'execDetailsEnd', 'update_portfolio_account', 'event_tm_request_table_structure', AbstractTableModel.EVENT_TM_TABLE_STRUCTURE_CHANGED],
       'seek_to_end': ['*'],
       
       
@@ -557,7 +598,7 @@ if __name__ == '__main__':
     logconfig = kwargs['logconfig']
     logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'    
     logging.basicConfig(**logconfig)        
-    
+    logging.getLogger().addFilter(LoggerNoBaseMessagingFilter())
     
     server = PortfolioMonitor(kwargs)
     server.start_engine()

+ 0 - 11
src/sh/t1.sh~

@@ -1,11 +0,0 @@
-#!/bin/bash
-ROOT=/home/larry-13.04/workspace/finopt
-SRC=$ROOT/src
-KAFKA_ASSEMBLY_JAR=$ROOT/src/jar/spark-streaming-kafka-assembly_2.10-1.4.1.jar
-export PYTHONPATH=$SRC:$PYTHONPATH
-
-#spark-submit  --jars  $KAFKA_ASSEMBLY_JAR /home/larry-13.04/workspace/finopt/cep/momentum.py vsu-01:2181 hsi 1 cal_trend 
-#spark-submit --master spark://192.168.1.118:7077   --jars  $KAFKA_ASSEMBLY_JAR /home/larry-13.04/workspace/finopt/cep/momentum.py vsu-01:2181 hsi 1 simple 
-#spark-submit --total-executor-cores 2 --master spark://192.168.1.118:7077   --jars  $KAFKA_ASSEMBLY_JAR /home/larry-13.04/workspace/finopt/cep/momentum.py vsu-01:2181 hsi 1 cal_trend 
-spark-submit  --jars  $KAFKA_ASSEMBLY_JAR $SRC/cep/t1.py
-

+ 1 - 1
src/sh/ws.sh

@@ -18,4 +18,4 @@ fi
 
 export PYTHONPATH=$FINOPT_HOME:$PYTHONPATH
 #python $FINOPT_HOME/ws/ws_server.py  -c -g AE1  
-python $FINOPT_HOME/ws/ws_server.py   -g AE1  
+python $FINOPT_HOME/ws/ws_server.py   -g AE1 -f ../config/ws.cfg 

+ 118 - 16
src/ws/ws_server.py

@@ -5,10 +5,18 @@ from optparse import OptionParser
 from time import sleep
 from threading import Thread
 import copy, sys
-from misc2.observer import NotImplementedException, Subscriber, Publisher
+from misc2.observer import NotImplementedException, Subscriber
+from misc2.helpers import ContractHelper, LoggerNoBaseMessagingFilter, HelperFunctions, ConfigMap
 from rethink.table_model import AbstractTableModel
 from rethink.portfolio_monitor import PortfolioMonitor
 from comms.ibgw.base_messaging import BaseMessageListener, Prosumer
+from comms.ibc.tws_client_lib import TWS_client_manager, AbstractGatewayListener
+from finopt import optcal
+import datetime as dt
+from dateutil.relativedelta import relativedelta
+from ws_webserver import PortalServer, HTTPServe
+from finopt.instrument import Symbol
+
 # https://github.com/Pithikos/python-websocket-server
 
 
@@ -34,6 +42,7 @@ class BaseWebSocketServerWrapper(Subscriber):
         
         self.set_stop = False
         
+
         
     def set_server(self, server):
         self.server = server
@@ -221,7 +230,7 @@ class PortfolioTableModelListener(BaseMessageListener):
                                         json.dumps({'event': event, 'value': port_values, 'account': account})) 
 
 
-class MainWebSocketServer(BaseWebSocketServerWrapper):
+class MainWebSocketServer(BaseWebSocketServerWrapper, AbstractGatewayListener):
     '''
     
         MainWebSocketServer
@@ -253,6 +262,7 @@ class MainWebSocketServer(BaseWebSocketServerWrapper):
         self.message_handler.add_listeners([tbl_listener])
         self.message_handler.start_prosumer()        
         self.clients = {}
+        self.fut_months = {}
         
         # stores server side object id to request ws clients
         # this allows the server to determine
@@ -261,17 +271,64 @@ class MainWebSocketServer(BaseWebSocketServerWrapper):
         # 
         # server_handler_map: {<source_id>: client}
         self.server_handler_map ={}
+        #
+        # bug or limitation: the kwargs topics key/val assumes a program only uses one type of listener
+        # in this case there are 2 listeners (PortfolioTableModelListener and AbstractGatewayListener
+        # this problem has to be cleaned up at a later stage
+        # for now just do a hack to pop the value in the topics key and
+        # replace with tickPrice
+        kwargs['topics'].pop()
+        kwargs['topics'] = ['tickPrice']
+        self.twsc = TWS_client_manager(kwargs)
+        self.twsc.add_listener_topics(self, ['tickPrice'] )
+        self.subscribe_hsif_ticks()
+        self.www = HTTPServe(kwargs)
+        th = threading.Thread(target=self.www.start_server)
+        th.daemon = True 
+        th.start()
 
         
+    def subscribe_hsif_ticks(self):
+         
+
+        def req_tick(month, year):
+            expiry = optcal.get_HSI_last_trading_day_ex(month, year)
+            contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', expiry, 0, '')
+            contract = ContractHelper.makeContract(contractTuple)
+            logging.info('subscribe_hsi_futures_ticks: %s' % str(contractTuple))
+            self.twsc.reqMktData(contract, True)
+            return contract
+        
+        results = {}
+        today = dt.datetime.now()
+        month = int(today.strftime('%m'))
+        year = int(today.strftime('%Y'))
+        c1 = req_tick(month, year)
+        self.fut_months[ContractHelper.makeRedisKeyEx(c1)] = {'month_type' : 'current', 'sym': Symbol(c1)}
+        
+        
+        one_month_later = today + relativedelta(months=+1)
+        next_month = int(one_month_later.strftime('%m'))        
+        year = int(one_month_later.strftime('%Y'))
+        c2 = req_tick(next_month, year)
+        self.fut_months[ContractHelper.makeRedisKeyEx(c2)] = {'month_type' : 'next', 'sym': Symbol(c2)}
+        
+        
+        
+        
+        
+
         
     def loop_forever(self):
 
+
+        self.twsc.start_manager()
         try:
             def print_menu():
                 menu = {}
                 menu['1']="set dirty count1 limit" 
                 menu['2']="set flush timeout"
-                menu['3']=""
+                menu['3']="shows start up config"
                 menu['4']=""
                 menu['5']=""
                 menu['6']=""
@@ -305,7 +362,7 @@ class MainWebSocketServer(BaseWebSocketServerWrapper):
                     elif selection == '2':
                         pass 
                     elif selection == '3':
-                        pass                         
+                        print '\n'.join('[%s]:%s' % (k,v) for k,v in self.kwargs.iteritems())                    
                     elif selection == '4':
                         pass                         
                     elif selection == '5':
@@ -317,6 +374,9 @@ class MainWebSocketServer(BaseWebSocketServerWrapper):
                         sleep(1)
                         self.stop_server()
                         sleep(1)
+                        self.twsc.gw_message_handler.set_stop()
+                        sleep(1)
+                        self.www.stop_server()
                         break
                     else: 
                         pass                        
@@ -327,8 +387,14 @@ class MainWebSocketServer(BaseWebSocketServerWrapper):
         except (KeyboardInterrupt, SystemExit):
             logging.error('MainWebSocketServer: caught user interrupt. Shutting down...')
             self.message_handler.set_stop() 
+            self.twsc.gw_message_handler.set_stop()            
             logging.info('MainWebSocketServer: Service shut down complete...')   
 
+        except:
+            logging.error('MainWebSocketServer. caught user interrupt. Shutting down...%s' % traceback.format_exc())
+            self.message_handler.set_stop()
+            self.twsc.gw_message_handler.set_stop() 
+            logging.info('MainWebSocketServer: Service shut down complete...')               
 
 
             
@@ -350,26 +416,46 @@ class MainWebSocketServer(BaseWebSocketServerWrapper):
     
     # Called when a client sends a message1
     def message_received(self, client, server, message):
-        print 'message received %s' % message
+        #print 'message received %s' % message
         message = json.loads(message)
         if message['event'] == AbstractTableModel.EVENT_TM_REQUEST_TABLE_STRUCTURE:
             self.message_handler.send_message(AbstractTableModel.EVENT_TM_REQUEST_TABLE_STRUCTURE, 
                                           json.dumps({'request_id' : client['id'], 'target_resource': message['target_resource'], 'account': message['account']}))
 
 
-
+    def tickPrice(self, event, contract_key, field, price, canAutoExecute):
+        logging.info('received tickprice key[%s] [%s]=%f' % (contract_key, field, price))
+        # send only last price
+        if contract_key in self.fut_months.keys():
+            
+            self.fut_months[contract_key]['sym'].set_tick_value(field, price)
+            
+            
+            if field == Symbol.LAST:
+                change = None
+                current_or_next = 'current' if self.fut_months[contract_key]['month_type'] == 'current' else 'next'
+                try:
+                    close = self.fut_months[contract_key]['sym'].get_tick_value(Symbol.CLOSE)
+                    change = (price - close) / close * 100
+                    logging.info('ws_server:tickPrice close %0.2f change %0.2f' % (close, change))
+                except:
+                    pass
+                self.get_server().send_message_to_all( 
+                                        json.dumps({'event': event, 'current_or_next': current_or_next, 'price': price, 'change': change})) 
+    
     
 
+        
 def main():
     
     kwargs = {
       'name': 'WebSocketServer',
-      'bootstrap_host': 'vorsprung',
+      'bootstrap_host': 'localhost',
       'bootstrap_port': 9092,
       'redis_host': 'localhost',
       'redis_port': 6379,
       'redis_db': 0,
-      'tws_host': 'vsu-bison',
+      'tws_host': 'localhost',
       'group_id': 'WS',
       'session_timeout_ms': 10000,
       'clear_offsets':  False,
@@ -390,29 +476,45 @@ def main():
     parser.add_option("-g", "--group_id",
                       action="store", dest="group_id", 
                       help="assign group_id to this running instance")
-    parser.add_option("-e", "--evaluation_date",
-                     action="store", dest="evaluation_date", 
-                     help="specify evaluation date for option calculations")   
+    parser.add_option("-f", "--config_file",
+                      action="store", dest="config_file", 
+                      help="path to the config file")    
     
     (options, args) = parser.parse_args()
-    if options.evaluation_date == None:
-        options.evaluation_date = time.strftime('%Y%m%d') 
+    fargs = ConfigMap().kwargs_from_file(options.config_file)
     
+    # copy all command line options into fargs
     for option, value in options.__dict__.iteritems():
         if value <> None:
-            kwargs[option] = value
+            fargs[option] = value
+            
+    # compare default config with fargs
+    # replace the default values with values found in fargs
+    # update the final config into kwargs
+    temp_kwargs = copy.copy(fargs)            
+    for key in kwargs:
+        if key in temp_kwargs:
+            kwargs[key] = temp_kwargs.pop(key)        
+    kwargs.update(temp_kwargs)                
     
       
     logconfig = kwargs['logconfig']
     logconfig['format'] = '%(asctime)s %(levelname)-8s %(message)s'    
     logging.basicConfig(**logconfig)        
-        
+    logging.getLogger().addFilter(LoggerNoBaseMessagingFilter())  
     
     esw = MainWebSocketServer('ChartTableWS', kwargs)    
     esw.start_server()
-
     
     
+
+
+
+
+
+
+    
 if __name__ == "__main__":
     main()
+    sys.exit(0)
     

+ 91 - 0
src/ws/ws_webserver.py

@@ -0,0 +1,91 @@
+# -*- 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 json
+
+import ConfigParser
+
+
+from sets import Set
+import thread
+
+
+class PortalServer(object):
+    
+    config = None
+    
+    
+    def __init__(self, config):
+        super(PortalServer, self).__init__()
+        PortalServer.config = config
+      
+
+    
+    
+    @cherrypy.expose
+    def index(self):
+        
+        return self.ws()
+    
+ 
+ 
+    
+    @cherrypy.expose
+    def ws(self):
+        html = '%s%s/client_g.html' % (cherrypy.request.app.config['/']['tools.staticdir.root'], cherrypy.request.app.config['/static']['tools.staticdir.tmpl'])
+        f = open(html)
+        return f.read()
+    
+                 
+class HTTPServe():
+    
+    def __init__(self, config):
+        self.config = config
+        
+    def start_server(self):
+        cherrypy.quickstart(PortalServer(self.config), '/', self.config['ws_webserver_cfg_path'])
+    
+    def stop_server(self):
+        cherrypy.engine.exit()   
+                 
+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)            
+
+
+    cherrypy.quickstart(PortalServer(config), '/', cfg_path[0])
+    
+