Browse Source

optimize portfolio calculations

- only handle bid, ask, last prices
- fixes a few bugs in handling price and position updates
- change websocket server to run in a separate thread within the main
class
laxaurus 9 years ago
parent
commit
ef2706fe22

+ 21 - 14
src/rethink/option_chain.py

@@ -8,7 +8,7 @@ from finopt.instrument import Symbol, Option
 from comms.ibc.base_client_messaging import AbstractGatewayListener
 from misc2.observer import Publisher, Subscriber 
 from misc2.observer import NotImplementedException
-
+import math
 
 from time import sleep
 from finopt.optcal import cal_implvol, cal_option
@@ -212,20 +212,27 @@ class OptionsChain(Publisher):
     
     
     
-    def cal_greeks_in_chain(self, valuation_date):
+    def cal_greeks_in_chain(self, valuation_date, uspot):
         
         all_results = {}
         for o in self.options:
             key = ContractHelper.makeRedisKeyEx(o.get_contract())
-            greeks = self.cal_option_greeks(o, valuation_date)
+            # pass a non number in the ospot param, forcing cal_option_greeks to use the option's last price
+            greeks = self.cal_option_greeks(o, valuation_date, uspot, float('nan'))
             all_results[key] = greeks
 
         return all_results
     
-    def cal_option_greeks(self, option, valuation_date):
-        
-        uspot_last = self.get_underlying().get_tick_value(4)
-        if uspot_last is None:
+    def cal_option_greeks(self, option, valuation_date, uspot, premium):
+        '''
+            if underlying spot is a non-number, attempt to get uspot last value in the symbol
+            if ospot is a non-number, attempt to get option's last px in the Option 
+        '''
+        
+        uspot = uspot if not math.isnan(uspot) else self.get_underlying().get_tick_value(4)
+        premium = premium if not math.isnan(premium) else option.get_tick_value(4)
+        #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:
             return OptionsChain.EMPTY_GREEKS
         o = option.get_contract()
         
@@ -236,17 +243,17 @@ class OptionsChain(Publisher):
             #logging.info('OptionChain:cal_option_greeks. ulspot->%8.4f, premium last->%8.4f ' % (uspot_last, 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_last, o.m_strike, o.m_right, valuation_date, 
-                                  o.m_expiry, self.rate, self.div, self.trade_vol, option.get_tick_value(4))
+            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))
         except RuntimeError:
-            logging.warn('OptionChain:cal_option_greeks. Quantlib threw an error while calculating implied vol: use intrinsic: last->%8.2f strike->%8.2f right->%s sym->%s' % 
-                         (uspot_last, o.m_strike, o.m_right, o.m_symbol))
-            iv = cal_implvol(uspot_last, o.m_strike, o.m_right, valuation_date, 
-                                  o.m_expiry, self.rate, self.div, self.trade_vol, abs(uspot_last - o.m_strike))
+            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))
+            iv = cal_implvol(uspot, o.m_strike, o.m_right, valuation_date, 
+                                  o.m_expiry, self.rate, self.div, self.trade_vol, abs(uspot - o.m_strike))
 
         try:                
-            greeks = cal_option(uspot_last, o.m_strike, o.m_right, valuation_date, 
+            greeks = cal_option(uspot, o.m_strike, o.m_right, valuation_date, 
                                   o.m_expiry, self.rate, self.div, iv[Option.IMPL_VOL])
             greeks.update(iv)
             #logging.info('OptionChain:cal_option_greeks. %s' % greeks)

+ 19 - 12
src/rethink/portfolio_item.py

@@ -2,6 +2,7 @@
 import sys, traceback
 import logging
 import json
+import math
 import time, datetime
 import copy
 from optparse import OptionParser
@@ -325,21 +326,27 @@ class Portfolio(AbstractTableModel):
         return self.port_item_to_row_fields(p_item)
     
     def port_item_to_row_fields(self, x):
+        
+        def handle_NaN(n):
+            # the function JSON.parse will fail at the javascript side if it encounters
+            # a NaN value in the json string. Convert Nan to null to circumvent the issue 
+            return None if math.isnan(n) else n 
+        
         rf = [{'v': '%s-%s-%s' % (x[1].get_symbol_id(), x[1].get_expiry(), x[1].get_strike())}, 
              {'v': x[1].get_right()},
-             {'v': x[1].get_port_field(PortfolioItem.AVERAGE_COST)},
-             {'v': x[1].get_port_field(PortfolioItem.MARKET_VALUE)},
-             {'v': x[1].get_port_field(PortfolioItem.AVERAGE_PRICE)},
-             {'v': self.get_spot_px(x[1])},
+             {'v': handle_NaN(x[1].get_port_field(PortfolioItem.AVERAGE_COST))},
+             {'v': handle_NaN(x[1].get_port_field(PortfolioItem.MARKET_VALUE))},
+             {'v': handle_NaN(x[1].get_port_field(PortfolioItem.AVERAGE_PRICE))},
+             {'v': handle_NaN(self.get_spot_px(x[1]))},
              {'v': x[1].get_quantity()},
-             {'v': x[1].get_instrument().get_tick_value(Option.DELTA)},
-             {'v': x[1].get_instrument().get_tick_value(Option.THETA)},
-             {'v': x[1].get_instrument().get_tick_value(Option.GAMMA)},
-             {'v': x[1].get_port_field(PortfolioItem.POSITION_DELTA)},
-             {'v': x[1].get_port_field(PortfolioItem.POSITION_THETA)},
-             {'v': x[1].get_port_field(PortfolioItem.POSITION_GAMMA)},
-             {'v': x[1].get_port_field(PortfolioItem.UNREAL_PL)},
-             {'v': x[1].get_port_field(PortfolioItem.PERCENT_GAIN_LOSS)}]
+             {'v': handle_NaN(x[1].get_instrument().get_tick_value(Option.DELTA))},
+             {'v': handle_NaN(x[1].get_instrument().get_tick_value(Option.THETA))},
+             {'v': handle_NaN(x[1].get_instrument().get_tick_value(Option.GAMMA))},
+             {'v': handle_NaN(x[1].get_port_field(PortfolioItem.POSITION_DELTA))},
+             {'v': handle_NaN(x[1].get_port_field(PortfolioItem.POSITION_THETA))},
+             {'v': handle_NaN(x[1].get_port_field(PortfolioItem.POSITION_GAMMA))},
+             {'v': handle_NaN(x[1].get_port_field(PortfolioItem.UNREAL_PL))},
+             {'v': handle_NaN(x[1].get_port_field(PortfolioItem.PERCENT_GAIN_LOSS))}]
         return rf     
     
     

+ 12 - 12
src/rethink/portfolio_monitor.py

@@ -207,6 +207,9 @@ class PortfolioMonitor(AbstractGatewayListener):
     
     def tds_event_tick_updated(self, event, contract_key, field, price, syms):
         
+        if field not in [Symbol.ASK, Symbol.BID, Symbol.LAST]:
+            return
+        
         for s in syms:
             
             if OptionsChain.CHAIN_IDENTIFIER in s.get_extra_attributes():
@@ -221,11 +224,12 @@ class PortfolioMonitor(AbstractGatewayListener):
                         #logging.info('PortfolioMonitor:tds_event_tick_updated --> portfolio opt_chains: [  %s  ] ' % 
                         #             str(self.portfolios[acct]['opt_chains'].keys()))
                         if 'FUT' in contract_key or 'STK' in contract_key:
-                            #results = self.portfolios[acct]['opt_chains'][chain_id].cal_greeks_in_chain(self.kwargs['evaluation_date'])
-                            results = self.portfolios[acct].get_option_chain(chain_id).cal_greeks_in_chain(self.kwargs['evaluation_date'])
+                            
+                            results = self.portfolios[acct].get_option_chain(chain_id).cal_greeks_in_chain(self.kwargs['evaluation_date'], price)
                         else:
-                            #results[ContractHelper.makeRedisKeyEx(s.get_contract())] =  self.portfolios[acct]['opt_chains'][chain_id].cal_option_greeks(s, self.kwargs['evaluation_date'])
-                            results[ContractHelper.makeRedisKeyEx(s.get_contract())] =  self.portfolios[acct].get_option_chain(chain_id).cal_option_greeks(s, self.kwargs['evaluation_date'])
+                            
+                            results[ContractHelper.makeRedisKeyEx(s.get_contract())] =  self.portfolios[acct].get_option_chain(chain_id)\
+                                                                                            .cal_option_greeks(s, self.kwargs['evaluation_date'], float('nan'), price)
                     #logging.info('PortfolioMonitor:tds_event_tick_updated. compute greek results %s' % results)
                         
                         #underlying_px = self.portfolios[acct]['opt_chains'][chain_id].get_underlying().get_tick_value(4)
@@ -238,14 +242,10 @@ class PortfolioMonitor(AbstractGatewayListener):
                         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])
                         
-                        #if contract_key in self.portfolios[acct]['port_items']:
-                        if self.portfolios[acct].is_contract_in_portfolio(contract_key):
-                            #self.portfolios[acct]['port_items'][contract_key].calculate_pl(key_greeks[0]) #, underlying_px)
-                            self.portfolios[acct].calculate_item_pl(contract_key)
-                        
-                            
-                            self.portfolios[acct].fire_table_row_updated(self.portfolios[acct].ckey_to_row(contract_key))
-                            logging.info('PortfolioMonitor:tds_event_tick_updated. Position updated: %s:[%d]' % (contract_key, self.portfolios[acct].ckey_to_row(contract_key)))
+                        self.portfolios[acct].calculate_item_pl(key_greeks[0])
+                        self.portfolios[acct].fire_table_row_updated(self.portfolios[acct].ckey_to_row(key_greeks[0]))
+                        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')
                         map(update_portfolio_fields, list(results.iteritems()))

+ 3 - 1
src/ws/websocket_server/websocket_server.py

@@ -13,7 +13,7 @@ if sys.version_info[0] < 3:
 else:
     from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler
 
-#logger = logging.getLogger(__name__)
+logger = logging.getLogger(__name__)
 #logging.basicConfig()
 
 '''
@@ -51,6 +51,8 @@ OPCODE_PONG         = 0xA
 
 class API():
 
+    
+    
     def run_forever(self):
         try:
             logger.info("Listening on port %d for clients.." % self.port)

File diff suppressed because it is too large
+ 23 - 7
src/ws/ws_server.py


Some files were not shown because too many files changed in this diff