Переглянути джерело

add put call parity computation
slight enhancements in analytics_engine

laxaurus 7 роки тому
батько
коміт
720475211e

+ 7 - 4
src/comms/ibc/tws_client_lib.py

@@ -116,15 +116,17 @@ class GatewayMessageListener(AbstractGatewayListener):
     def tickPrice(self, event, contract_key, field, price, canAutoExecute):  # tickerId, field, price, canAutoExecute):    
         logging.info('GatewayMessageListener:%s. val->[%s]' % (event, vars()))
 
-    def tickSize(self, event, message_value):  # tickerId, field, price, canAutoExecute):
-        logging.info('GatewayMessageListener:%s. val->[%s]' % (event, message_value))
+    #tickSize(self, event, contract_key, tickerId, field, size):
+    #def tickSize(self, event, message_value):  # tickerId, field, price, canAutoExecute):
+    def tickSize(self, event, contract_key, field, size):
+        logging.info('GatewayMessageListener:%s. val->[%s]' % (event, vars()))
         
     def error(self, event, message_value):
         logging.info('GatewayMessageListener:%s. val->[%s]' % (event, message_value))  
 
 def test_client(kwargs):
-    contractTuples = [('HSI', 'FUT', 'HKFE', 'HKD', '20170330', 0, '')]#,
-                      #('USD', 'CASH', 'IDEALPRO', 'JPY', '', 0, ''),]
+    contractTuples = [('HSI', 'FUT', 'HKFE', 'HKD', '20190130', 0, '')]#,
+    #contractTuples = [('USD', 'CASH', 'IDEALPRO', 'JPY', '', 0, ''),]
                       
         
     print kwargs 
@@ -134,6 +136,7 @@ def test_client(kwargs):
     cm.add_listener_topics(cl, kwargs['topics'])
     cm.start_manager()
     map(lambda c: cm.reqMktData(ContractHelper.makeContract(c)), contractTuples)
+    cm.reqPositions()
     try:
         logging.info('TWS_gateway:main_loop ***** accepting console input...')
         while True: 

+ 1 - 1
src/comms/ibgw/base_messaging.py

@@ -195,7 +195,7 @@ class BaseConsumer(threading.Thread, Publisher):
     
         
     def extract_message_content(self, message):
-        #logging.info('BaseConsumer: extract_message_content. %s %s' % (type(message), message))
+        logging.info('BaseConsumer: extract_message_content. %s %s' % (type(message), message))
         try:
             return json.loads(message.value)
         except ValueError:

+ 10 - 10
src/comms/sample_tws_client.py

@@ -397,7 +397,7 @@ def test3():
 #     m_secType = ""
 #     m_exchange = ""
 #     m_side = ""    
-    filter = ExecutionFilterHelper.kv2object({'m_time': '20151104  09:35:00'}, ExecutionFilter) 
+    filter = ExecutionFilterHelper.kv2object({'m_time': '20190122  09:35:00'}, ExecutionFilter) 
     c.get_command_handler().reqExecutions(filter)
     sleep(7)    
     
@@ -582,21 +582,21 @@ if __name__ == '__main__':
     choice= sys.argv[1]
            
     logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)-8s %(message)s',
-                        filename= '/home/larry-13.04/workspace/finopt/log/unitest.log')
+                        filename= '/tmp/unitest.log')
     
     
     # bootstrap server settings
-    host = 'localhost'
+    host = 'vorsprung'
     port = 9092
 
     print 'choice: %s' % choice
-    test4()
+    
     #test8()
-#     if choice == '2': 
-#         
-#         test2()
-#     else:
-#         
-#         test3()
+    if choice == '2': 
+         
+        test2()
+    else:
+         
+        test3()
     
     

+ 2 - 1
src/comms/tws_protocol_helper.py

@@ -16,7 +16,8 @@ class TWS_Protocol:
               'receiveFA', 'connectionClosed', 'position', 'updateMktDepthL2', 'fundamentalData', 'tickEFP')
     
     
-    
+    # 2019-01 dummy placeholder. I have forgotten how the variable was used but it is mentioned in other parts of the code
+    gatewayEvents = ()
 
     
     aeMethods = ('ae_req_greeks')

+ 1 - 1
src/config/app.cfg

@@ -111,7 +111,7 @@ msg_bot.redis_prefix: 'alert_bot'
 msg_bot.logconfig: "{'level': logging.INFO}"
 
 [epc]
-kafka.host: 'localhost'
+kafka.host: 'vorsprung'
 kafka.port: 9092
 
 [ib_mds]

+ 3 - 3
src/config/tws_client_lib.cfg

@@ -3,7 +3,7 @@ name: 'reference_client'
 #
 # kafka settings
 #
-bootstrap_host: 'localhost'
+bootstrap_host: 'vorsprung'
 bootstrap_port: 9092
 #
 # redis persistence
@@ -16,7 +16,7 @@ redis_db: 0
 #
 group_id: 'TWS_CLI'
 session_timeout_ms: 10000
-topics:['tickSize', 'tickPrice', ]
-seek_to_end:['tickSize', 'tickPrice']
+topics:['tickSize', 'tickPrice', 'position']
+seek_to_end:['tickSize', 'tickPrice', 'position']
 logconfig: { 'filemode': 'w', 'filename': '/tmp/tws_client_lib.log',  'level': logging.INFO}
 #logconfig: {'level': logging.INFO}

+ 6 - 5
src/config/tws_gateway.cfg

@@ -3,7 +3,8 @@ name: 'tws_gateway_server'
 #
 # kafka settings
 #
-bootstrap_host: 'localhost'
+#bootstrap_host: 'localhost'
+bootstrap_host: 'vorsprung'
 bootstrap_port: 9092
 #
 # redis persistence
@@ -16,8 +17,8 @@ redis_db: 0
 #
 # 7496 - production larry046, 7496 - development,  8496 production mchan927
 #
-tws_host: 'localhost'
-tws_api_port: 7496
+tws_host: 'vsu-bison'
+tws_api_port: 8496
 tws_app_id: 5567
 #
 #
@@ -37,8 +38,8 @@ order_transmit: False
 reset_db_subscriptions: False
 #
 #
-ib_heartbeat.ib_port: 7496
+ib_heartbeat.ib_port: 8496
 ib_heartbeat.appid.id: 9911
-ib_heartbeat.gateway: 'localhost'
+ib_heartbeat.gateway: 'vsu-bison'
 ib_heartbeat.try_interval: 60
 ib_heartbeat.suppress_msg_interval: 120

+ 16 - 0
src/finopt/instrument.py

@@ -5,9 +5,22 @@ from misc2.helpers import ContractHelper, dict2str
 class Symbol():
     key = None
     
+    
+    
+    '''
+        Available tick types
+        https://interactivebrokers.github.io/tws-api/tick_types.html
+    
+    '''
     LAST = 4
     BID  = 1
     ASK  = 2
+    BIDSIZE = 0
+    ASKSIZE = 3
+    LASTSIZE = 5
+    CLOSE = 9
+    VOLUME = 8
+    
     
     
     
@@ -100,6 +113,9 @@ class Option(Symbol):
         return self.analytics
     
     
+    def get_strike(self):
+        return self.get_contract().m_strike
+    
     def object2kvstring(self):
         
         raise Exception

+ 4 - 0
src/misc2/helpers.py

@@ -8,6 +8,7 @@ import ConfigParser
 from ib.ext.Contract import Contract
 from ib.ext.Order import Order
 from ib.ext.ExecutionFilter import ExecutionFilter
+import copy
 
 
 class BaseHelper():
@@ -137,6 +138,9 @@ class ContractHelper(BaseHelper):
         return json.dumps(contract.__dict__)
 
 
+    @staticmethod
+    def contract2kv(contract):
+        return copy.deepcopy(contract.__dict__)
 
     @staticmethod
     def kvstring2contract(sm_contract):

+ 31 - 6
src/rethink/analytics_engine.py

@@ -10,6 +10,7 @@ from finopt.instrument import Symbol, Option
 from rethink.option_chain import OptionsChain
 from rethink.tick_datastore import TickDataStore
 from comms.ibc.tws_client_lib import TWS_client_manager, AbstractGatewayListener
+import sys, traceback
 
 
 
@@ -34,7 +35,7 @@ class AnalyticsEngine(AbstractGatewayListener):
         
     
     def test_oc(self, oc2):
-        expiry = '20170830'
+        expiry = '20190328'
         contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', expiry, 0, '')
         contract = ContractHelper.makeContract(contractTuple)  
         
@@ -59,13 +60,13 @@ class AnalyticsEngine(AbstractGatewayListener):
         
     
     def test_oc3(self, oc3):
-        expiry = '20170928'
+        expiry = '20190227'
         contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', expiry, 0, '')
         contract = ContractHelper.makeContract(contractTuple)  
          
         oc3.set_option_structure(contract, 200, 50, 0.0012, 0.0328, expiry)        
          
-        oc3.build_chain(27000, 0.04, 0.22)
+        oc3.build_chain(27000, 0.06, 0.22)
 
 #         expiry = '20170331'
 #         contractTuple = ('QQQ', 'STK', 'SMART', 'USD', '', 0, '')
@@ -126,6 +127,7 @@ class AnalyticsEngine(AbstractGatewayListener):
             menu['2']="Display tick data store "
             menu['3']="Display option chain oc3"
             menu['4']="Generate oc3 gtable json"
+            menu['5']="Display oc3 put call parity"
             menu['9']="Exit"
             while True: 
                 choices=menu.keys()
@@ -142,6 +144,15 @@ class AnalyticsEngine(AbstractGatewayListener):
                     oc3.pretty_print()
                 elif selection == '4':
                     print oc3.g_datatable_json()
+                elif selection == '5':
+                    try:
+                        last_px = oc3.get_underlying().get_tick_value(4)
+                        results = oc3.cal_put_call_parity(last_px)
+                        print results
+                        print '\n'.join('%0.0f:%0.2f' % (k, v) for k, v in sorted(results.items()))
+                    except:
+                        logging.error(traceback.format_exc())
+                    
                 elif selection == '9': 
                     self.twsc.gw_message_handler.set_stop()
                     break
@@ -191,12 +202,24 @@ class AnalyticsEngine(AbstractGatewayListener):
             
             if OptionsChain.CHAIN_IDENTIFIER in s.get_extra_attributes():
                 results = {}
+                pc_errors = None
                 chain_id = s.get_extra_attributes()[OptionsChain.CHAIN_IDENTIFIER]
                 logging.info('AnalyticsEngine:tds_event_tick_updated chain_id %s' % chain_id)
                 if chain_id  in self.option_chains.keys():
                     if 'FUT' in contract_key or 'STK' in contract_key:
+                        
+
+                        # compute put call parity whnever the underlying changes
+#                         try:
+#                             pc_errors = self.option_chains[chain_id].cal_put_call_parity(price)
+#                         except:
+#                             pass
+                        #self.option_chains[chain_id].set_put_call_parity(pc_errors)
+                        #print pc_errors
+                        
                         results = self.option_chains[chain_id].cal_greeks_in_chain(self.kwargs['evaluation_date'], price)
                         
+                        
                     else:
                         results[ContractHelper.makeRedisKeyEx(s.get_contract())] = self.option_chains[chain_id].cal_option_greeks\
                                                                                         (s, self.kwargs['evaluation_date'], float('nan'), price)
@@ -212,7 +235,9 @@ class AnalyticsEngine(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])
                     
-                map(update_tds_analytics, list(results.iteritems()))                
+                map(update_tds_analytics, list(results.iteritems()))
+                
+                                
 
             else:
                 
@@ -259,12 +284,12 @@ if __name__ == '__main__':
     
     kwargs = {
       'name': 'analytics_engine',
-      'bootstrap_host': 'localhost',
+      'bootstrap_host': 'vorsprung',
       'bootstrap_port': 9092,
       'redis_host': 'localhost',
       'redis_port': 6379,
       'redis_db': 0,
-      'tws_host': 'localhost',
+      'tws_host': 'vsu-bison',
       'tws_api_port': 8496,
       'tws_app_id': 38868,
       'group_id': 'AE',

+ 55 - 0
src/rethink/option_chain.py

@@ -30,6 +30,9 @@ class OptionsChain(Publisher):
     trade_vol = None
     #iv = optcal.cal_implvol(spot, contract.m_strike, contract.m_right, today, contract.m_expiry, rate, div, vol, premium)
     
+    
+    put_call_parity = {}
+    
     CHAIN_IDENTIFIER = 'chain_identifier'
     
     
@@ -87,7 +90,16 @@ class OptionsChain(Publisher):
                       )
 
         
+    def get_rate(self):
+        return self.rate
+    
+    
+    def set_put_call_parity(self, parity_error):
+        self.put_call_parity = parity_error
         
+    def get_put_call_parity(self):
+        return self.put_call_parity
+    
     def set_spread_table(self, spd_size, multiplier):
         self.spd_size = spd_size
         self.multiplier = multiplier
@@ -212,6 +224,49 @@ class OptionsChain(Publisher):
     
     
     
+    def cal_put_call_parity(self, uspot):
+        '''
+            put call parity forumula:
+                
+                C + x / (1 + r)^t = Spot + P
+                
+            pc_error =     C + x / (1 + r)^t - (Spot + P)
+                
+        '''
+        logging.info('************* cal_put_call_parity uspot= %0.2f' % uspot)
+        all_results = {}
+        t = 1.0
+        puts = filter(lambda x: x.get_contract().m_right == 'P', self.options)
+        calls = filter(lambda x: x.get_contract().m_right == 'C', self.options)
+        pc_error = float('nan')        
+        for o in calls:
+            try:
+                key = ContractHelper.makeRedisKeyEx(o.get_contract())
+                C = (o.get_tick_value(Symbol.ASK) + o.get_tick_value(Symbol.BID)) / 2
+                
+                logging.info('Call %s mid price= %0.2f' % (key, C))
+                Ckv = ContractHelper.contract2kv(o.get_contract())
+                Ckv['m_right'] = 'P'
+                logging.info('Put key %s ' % (json.dumps(Ckv)))
+                Plist = filter(lambda x: ContractHelper.is_equal(x.get_contract(), ContractHelper.kv2contract(Ckv)), puts)
+                if len(Plist) > 0:
+                    P = (Plist[0].get_tick_value(Symbol.ASK) + Plist[0].get_tick_value(Symbol.BID)) / 2
+                    
+                    logging.info('Put mid price= %0.2f' % (P))
+                 
+                    pc_error = C + float(o.get_strike()) / (1 + self.get_rate())** t - (uspot + P)
+                    logging.info('pc_error %0.2f' % (pc_error))
+
+            except:
+                logging.error(traceback.format_exc())
+
+                
+            
+            all_results[o.get_strike()] = pc_error
+        
+          
+        return all_results   
+    
     def cal_greeks_in_chain(self, valuation_date, uspot):
         
         all_results = {}

+ 14 - 9
src/rethink/tick_datastore.py

@@ -5,7 +5,8 @@ from misc2.observer import Publisher
 from misc2.observer import NotImplementedException
 from misc2.helpers import ContractHelper
 from comms.ibc.base_client_messaging import AbstractGatewayListener
-import symbol, traceback
+import traceback
+from finopt.instrument import Symbol
 
 class TickDataStore(Publisher):
     """
@@ -65,7 +66,7 @@ class TickDataStore(Publisher):
             
             return fmt % (val) 
         
-        # last, bidq, bid, ask, askq, imvol, delta, theta
+        
         fmt_spec = '%8.2f'
         fmt_spec2 = '%8.4f'
         fmt_specq = '%8d'
@@ -78,15 +79,19 @@ class TickDataStore(Publisher):
                 return ''
 
         
-        fmt_sym = map(lambda x: (x[0], '%s,%s,%s,%s,%s,%s' % (
-                                            format_tick_val(get_field(x[1]['syms'],4), fmt_spec),
-                                            format_tick_val(get_field(x[1]['syms'],0), fmt_specq),                                                                                                                  
-                                            format_tick_val(get_field(x[1]['syms'],1), fmt_spec),
-                                            format_tick_val(get_field(x[1]['syms'],2), fmt_spec), 
-                                            format_tick_val(get_field(x[1]['syms'],3), fmt_specq),
-                                            format_tick_val(get_field(x[1]['syms'],9), fmt_spec),
+        
+        fmt_sym = map(lambda x: (x[0], '%s,%s,%s,%s,%s,%s,%s' % (
+                                            format_tick_val(get_field(x[1]['syms'],Symbol.LAST), fmt_spec),
+                                            format_tick_val(get_field(x[1]['syms'],Symbol.BIDSIZE), fmt_specq),                                                                                                                  
+                                            format_tick_val(get_field(x[1]['syms'],Symbol.BID), fmt_spec),
+                                            format_tick_val(get_field(x[1]['syms'],Symbol.ASK), fmt_spec), 
+                                            format_tick_val(get_field(x[1]['syms'],Symbol.ASKSIZE), fmt_specq),
+                                            format_tick_val(get_field(x[1]['syms'],Symbol.CLOSE), fmt_spec),
+                                            format_tick_val(get_field(x[1]['syms'],Symbol.VOLUME), fmt_specq),
                                             )), [(k,v) for k, v in self.symbols.iteritems()])        
         
+        print('%40s,%8s,%8s,%8s,%8s,%8s,%8s,%8s\n' % ('SYM', 'LAST', 'BIDSIZE','BID','ASK','ASKSIZE','CLOSE','VOLUME'
+                                             ))
 
         for e in fmt_sym:
             print('[%s]%s' % (e[0].ljust(40), e[1]))

+ 6 - 3
src/sh/ae.sh

@@ -1,15 +1,18 @@
 #!/bin/bash
 
 
+
 HOST=$(hostname)
 echo $HOST
 if [ $HOST == 'hkc-larryc-vm1' ]; then
 	FINOPT_HOME=~/ironfly-workspace/finopt/src
-elif [ $HOST == 'vorsprung' ]; then
-	FINOPT_HOME=~/workspace/finopt/src	
+elif [ $HOST == 'astron' ]; then
+	FINOPT_HOME=~/workspace/finopt/src
 else
 	FINOPT_HOME=~/l1304/workspace/finopt-ironfly/finopt/src
 fi
+
+
 export PYTHONPATH=$FINOPT_HOME:$PYTHONPATH
-python $FINOPT_HOME/rethink/analytics_engine.py  -c -g AE1  
+python $FINOPT_HOME/rethink/analytics_engine.py  -c -g AE111  
 #python $FINOPT_HOME/rethink/analytics_engine.py   -g AE1  

+ 13 - 1
src/sh/sample_tws3.sh

@@ -1,4 +1,16 @@
 #!/bin/bash
+
+
+HOST=$(hostname)
+echo $HOST
+if [ $HOST == 'hkc-larryc-vm1' ]; then
+	FINOPT_HOME=~/ironfly-workspace/finopt/src
+elif [ $HOST == 'astron' ]; then
+	FINOPT_HOME=~/workspace/finopt/src
+else
+	FINOPT_HOME=~/l1304/workspace/finopt-ironfly/finopt/src
+fi
 ROOT=$FINOPT_HOME
 export PYTHONPATH=$FINOPT_HOME:$PYTHONPATH
-python $FINOPT_HOME/comms/sample_tws_client.py 3
+
+python $FINOPT_HOME/comms/sample_tws_client.py 3

+ 3 - 0
src/sh/start_twscli.sh

@@ -5,8 +5,11 @@ HOST=$(hostname)
 echo $HOST
 if [ $HOST == 'hkc-larryc-vm1' ]; then
 	FINOPT_HOME=~/ironfly-workspace/finopt/src
+elif [ $HOST == 'astron' ]; then
+	FINOPT_HOME=~/workspace/finopt/src
 else
 	FINOPT_HOME=~/l1304/workspace/finopt-ironfly/finopt/src
+				
 fi
 export PYTHONPATH=$FINOPT_HOME:$PYTHONPATH
 python $FINOPT_HOME/comms/ibc/tws_client_lib.py -f "$FINOPT_HOME/config/tws_client_lib.cfg" -c

+ 5 - 2
src/sh/start_twsgw.sh

@@ -7,18 +7,21 @@ if [ $HOST == 'hkc-larryc-vm1' ]; then
 	FINOPT_HOME=~/ironfly-workspace/finopt/src
 elif [ $HOST == 'vorsprung' ]; then
 	FINOPT_HOME=~/workspace/finopt/src
+elif [ $HOST == 'astron' ]; then
+	FINOPT_HOME=~/workspace/finopt/src
+		
 else
 	FINOPT_HOME=~/l1304/workspace/finopt-ironfly/finopt/src
 fi
 export PYTHONPATH=$FINOPT_HOME:$PYTHONPATH
 #  
 # clear all topic offsets and erased saved subscriptions
-#python $FINOPT_HOME/comms/ibgw/tws_gateway.py -r -c -f $FINOPT_HOME/config/tws_gateway.cfg 
+python $FINOPT_HOME/comms/ibgw/tws_gateway.py -r -c -f $FINOPT_HOME/config/tws_gateway.cfg 
 
 
 #
 # clear offsets in redis / reload saved subscription entries
-python $FINOPT_HOME/comms/ibgw/tws_gateway.py  -c -f $FINOPT_HOME/config/tws_gateway.cfg 
+#python $FINOPT_HOME/comms/ibgw/tws_gateway.py  -c -f $FINOPT_HOME/config/tws_gateway.cfg 
 
 
 # restart gateway keep the redis offsets but erase the subscription entries