|
|
@@ -1,90 +1,25 @@
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+import sys, traceback
|
|
|
import logging
|
|
|
import json
|
|
|
import time, datetime
|
|
|
import copy
|
|
|
from optparse import OptionParser
|
|
|
from time import sleep
|
|
|
-from misc2.observer import Subscriber
|
|
|
+from misc2.observer import Subscriber, Publisher
|
|
|
from misc2.helpers import ContractHelper
|
|
|
from finopt.instrument import Symbol, Option
|
|
|
from rethink.option_chain import OptionsChain
|
|
|
from rethink.tick_datastore import TickDataStore
|
|
|
+from rethink.portfolio_item import PortfolioItem, PortfolioRules, Portfolio
|
|
|
from comms.ibc.tws_client_lib import TWS_client_manager, AbstractGatewayListener
|
|
|
-from numpy import average
|
|
|
|
|
|
|
|
|
|
|
|
-class PortfolioItem():
|
|
|
- """
|
|
|
- Set up some constant variables
|
|
|
-
|
|
|
- position
|
|
|
- average cost
|
|
|
-
|
|
|
- """
|
|
|
- POSITION = 6001
|
|
|
- AVERAGE_COST = 6002
|
|
|
- POSITION_DELTA = 6003
|
|
|
- POSITION_THETA = 6004
|
|
|
- UNREAL_PL = 6005
|
|
|
- PERCENT_GAIN_LOSS = 6006
|
|
|
- AVERAGE_PRICE = 6007
|
|
|
- ACCOUNT_ID = 6008
|
|
|
-
|
|
|
-
|
|
|
- def __init__(self, account, contract_key, position, average_cost):
|
|
|
-
|
|
|
- self.contract_key = contract_key
|
|
|
- self.quantity = position
|
|
|
- self.average_cost = average_cost
|
|
|
- self.account_id = account
|
|
|
-
|
|
|
- contract = ContractHelper.makeContractfromRedisKeyEx(contract_key)
|
|
|
- if contract.m_secType == 'OPT':
|
|
|
- self.instrument = Option(contract)
|
|
|
- else:
|
|
|
- self.instrument = Symbol(contract)
|
|
|
-
|
|
|
-
|
|
|
- def get_instrument(self):
|
|
|
- return self.instrument
|
|
|
-
|
|
|
- def get_instrument_type(self):
|
|
|
- return self.instrument.get_contract().m_secType
|
|
|
-
|
|
|
- def get_account(self):
|
|
|
- return self.account_id
|
|
|
-
|
|
|
- def calculate_pl(self):
|
|
|
- pass
|
|
|
-
|
|
|
- def set_position_cost(self, position, average_cost):
|
|
|
- self.quantity = position
|
|
|
- self.average_cost = average_cost
|
|
|
-
|
|
|
-
|
|
|
class PortfolioMonitor(AbstractGatewayListener):
|
|
|
|
|
|
-
|
|
|
- '''
|
|
|
- portfolios :
|
|
|
- {
|
|
|
- <account_id>: {'port_items': {<contract_key>, instrument}, 'opt_chains': {<oc_id>: option_chain}}
|
|
|
- }
|
|
|
-
|
|
|
- '''
|
|
|
- rule_map = {
|
|
|
- 'symbol': {'HSI' : 'FUT', 'MHI' : 'FUT', 'QQQ' : 'STK'},
|
|
|
- 'expiry': {'HSI' : 'same_month', 'MHI': 'same_month', 'STK': 'leave_blank'},
|
|
|
- 'option_structure': {
|
|
|
- {'HSI':
|
|
|
- {'spd_size': 200, 'multiplier': 50, 'rate': 0.0012, 'div': 0}
|
|
|
- },
|
|
|
- {'MHI':
|
|
|
- {'spd_size': 200, 'multiplier': 10, 'rate': 0.0012, 'div': 0}
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+
|
|
|
+
|
|
|
|
|
|
def __init__(self, kwargs):
|
|
|
self.kwargs = copy.copy(kwargs)
|
|
|
@@ -95,52 +30,25 @@ class PortfolioMonitor(AbstractGatewayListener):
|
|
|
self.tds.register_listener(self)
|
|
|
self.twsc.add_listener_topics(self, kwargs['topics'])
|
|
|
|
|
|
+ '''
|
|
|
+ portfolios: {<account>: <portfolio>}
|
|
|
+ '''
|
|
|
self.portfolios = {}
|
|
|
- self.option_chains = {}
|
|
|
-
|
|
|
-
|
|
|
- def test_oc(self, oc2):
|
|
|
- expiry = '20170427'
|
|
|
- contractTuple = ('HSI', 'FUT', 'HKFE', 'HKD', expiry, 0, '')
|
|
|
- contract = ContractHelper.makeContract(contractTuple)
|
|
|
|
|
|
- oc2.set_option_structure(contract, 200, 50, 0.0012, 0.0328, expiry)
|
|
|
-
|
|
|
- oc2.build_chain(24172, 0.04, 0.22)
|
|
|
-
|
|
|
-# expiry='20170324'
|
|
|
-# contractTuple = ('QQQ', 'STK', 'SMART', 'USD', '', 0, '')
|
|
|
-# contract = ContractHelper.makeContract(contractTuple)
|
|
|
-#
|
|
|
-# oc2.set_option_structure(contract, 0.5, 100, 0.0012, 0.0328, expiry)
|
|
|
-#
|
|
|
-# oc2.build_chain(132.11, 0.02, 0.22)
|
|
|
-
|
|
|
-
|
|
|
- oc2.pretty_print()
|
|
|
-
|
|
|
- for o in oc2.get_option_chain():
|
|
|
- self.tds.add_symbol(o)
|
|
|
- self.tds.add_symbol(oc2.get_underlying())
|
|
|
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
def start_engine(self):
|
|
|
self.twsc.start_manager()
|
|
|
- oc2 = OptionsChain('oc2')
|
|
|
- oc2.register_listener(self)
|
|
|
- self.test_oc(oc2)
|
|
|
- self.option_chains[oc2.name] = oc2
|
|
|
-
|
|
|
+ self.twsc.reqPositions()
|
|
|
+ self.starting_engine = True
|
|
|
try:
|
|
|
logging.info('PortfolioMonitor:main_loop ***** accepting console input...')
|
|
|
menu = {}
|
|
|
- menu['1']="Display option chain oc2"
|
|
|
- menu['2']="Display tick data store "
|
|
|
- menu['3']="Display option chain oc3"
|
|
|
- menu['4']="Generate oc3 gtable json"
|
|
|
+ menu['1']="Request position"
|
|
|
+ menu['2']="Portfolio dump dtj"
|
|
|
+ menu['3']="TDS dump"
|
|
|
+ menu['4']="Request account updates"
|
|
|
+ menu['5']="Table chart JSON"
|
|
|
menu['9']="Exit"
|
|
|
while True:
|
|
|
choices=menu.keys()
|
|
|
@@ -150,9 +58,19 @@ class PortfolioMonitor(AbstractGatewayListener):
|
|
|
|
|
|
selection = raw_input("Enter command:")
|
|
|
if selection =='1':
|
|
|
- oc2.pretty_print()
|
|
|
+ self.twsc.reqPositions()
|
|
|
elif selection == '2':
|
|
|
- self.tds.dump()
|
|
|
+ for acct in self.portfolios.keys():
|
|
|
+ #print self.dump_portfolio(acct)
|
|
|
+ print self.portfolios[acct]['g_table']
|
|
|
+ elif selection == '3':
|
|
|
+ print self.tds.dump()
|
|
|
+ elif selection == '4':
|
|
|
+ for acct in self.portfolios.keys():
|
|
|
+ self.twsc.reqAccountUpdates(True, acct)
|
|
|
+ elif selection == '5':
|
|
|
+ for acct in self.portfolios.keys():
|
|
|
+ print self.g_datatable_json(acct)
|
|
|
elif selection == '9':
|
|
|
self.twsc.gw_message_handler.set_stop()
|
|
|
break
|
|
|
@@ -166,57 +84,32 @@ class PortfolioMonitor(AbstractGatewayListener):
|
|
|
self.twsc.gw_message_handler.set_stop()
|
|
|
logging.info('PortfolioMonitor: Service shut down complete...')
|
|
|
|
|
|
- def is_contract_in_portfolio(self, account, contract_key):
|
|
|
- return self.get_portfolio_port_items(account, contract_key)
|
|
|
-
|
|
|
- def get_portfolio_port_items(self, account, contract_key):
|
|
|
- try:
|
|
|
- return self.portfolios[account]['port_items'][contract_key]
|
|
|
- except KeyError:
|
|
|
- return None
|
|
|
-
|
|
|
- def set_portfolio_port_items(self, account, contract_key, port_item):
|
|
|
- self.portfolios[account]['port_items'][contract_key] = port_item
|
|
|
|
|
|
-
|
|
|
- def create_empty_portfolio(self, account):
|
|
|
- port = self.portfolios[account] = {}
|
|
|
- self.portfolios[account]['port_items']= {}
|
|
|
- self.portfolios[account]['opt_chains']= {}
|
|
|
- return port
|
|
|
|
|
|
def get_portfolio(self, account):
|
|
|
try:
|
|
|
return self.portfolios[account]
|
|
|
except KeyError:
|
|
|
- self.portfolios[account] = self.create_empty_portfolio(account)
|
|
|
+ self.portfolios[account] = Portfolio(account)
|
|
|
return self.portfolios[account]
|
|
|
|
|
|
def deduce_option_underlying(self, option):
|
|
|
'''
|
|
|
given an Option object, return the underlying Symbol object
|
|
|
'''
|
|
|
-
|
|
|
-
|
|
|
try:
|
|
|
symbol_id = option.get_contract().m_symbol
|
|
|
- underlying_sectype = self.rule_map['symbol'][symbol_id]
|
|
|
+ underlying_sectype = PortfolioRules.rule_map['symbol'][symbol_id]
|
|
|
exchange = option.get_contract().m_exchange
|
|
|
currency = option.get_contract().m_currency
|
|
|
- expiry = option.get_contract().m_expiry if self.rule_map['expiry'][symbol_id] == 'same_month' else ''
|
|
|
+ expiry = option.get_contract().m_expiry if PortfolioRules.rule_map['expiry'][symbol_id] == 'same_month' else ''
|
|
|
contractTuple = (symbol_id, underlying_sectype, exchange, currency, expiry, 0, '')
|
|
|
logging.info('PortfolioMonitor:deduce_option_underlying. Deduced underlying==> %s' %
|
|
|
- ContractHelper.printContract(contractTuple))
|
|
|
+ str(contractTuple))
|
|
|
return Symbol(ContractHelper.makeContract(contractTuple))
|
|
|
except KeyError:
|
|
|
logging.error('PortfolioMonitor:deduce_option_underlying. Unable to deduce the underlying for the given option %s' %
|
|
|
ContractHelper.printContract(option.get_contract))
|
|
|
-
|
|
|
-
|
|
|
- def is_oc_in_portfolio(self, account, oc_id):
|
|
|
- try:
|
|
|
- return self.portfolios[account]['opt_chains'][oc_id]
|
|
|
- except KeyError:
|
|
|
return None
|
|
|
|
|
|
|
|
|
@@ -229,45 +122,40 @@ class PortfolioMonitor(AbstractGatewayListener):
|
|
|
underlying_id = underlying.get_contract().m_symbol
|
|
|
month = underlying.get_contract().m_expiry
|
|
|
oc_id = create_oc_id(account, underlying_id, month)
|
|
|
- oc = self.is_oc_in_portfolio(account, oc_id)
|
|
|
+ oc = self.portfolios[account].is_oc_in_portfolio(oc_id)
|
|
|
if oc == None:
|
|
|
oc = OptionsChain(oc_id)
|
|
|
- oc.set_option_structure(underlying,
|
|
|
- self.rule_map['option_structure'][underlying_id]['spd_size'],
|
|
|
- self.rule_map['option_structure'][underlying_id]['multiplier'],
|
|
|
- self.rule_map['option_structure'][underlying_id]['rate'],
|
|
|
- self.rule_map['option_structure'][underlying_id]['div'],
|
|
|
- month)
|
|
|
+ oc.register_listener(self)
|
|
|
+ oc.set_option_structure(underlying.get_contract(),
|
|
|
+ PortfolioRules.rule_map['option_structure'][underlying_id]['spd_size'],
|
|
|
+ PortfolioRules.rule_map['option_structure'][underlying_id]['multiplier'],
|
|
|
+ PortfolioRules.rule_map['option_structure'][underlying_id]['rate'],
|
|
|
+ PortfolioRules.rule_map['option_structure'][underlying_id]['div'],
|
|
|
+ month,
|
|
|
+ PortfolioRules.rule_map['option_structure'][underlying_id]['trade_vol'])
|
|
|
|
|
|
- self.portfolios[account]['opt_chains'][oc_id] = oc
|
|
|
+ self.portfolios[account].set_option_chain(oc_id, oc)
|
|
|
|
|
|
|
|
|
return oc
|
|
|
|
|
|
+
|
|
|
|
|
|
-
|
|
|
- def process_position(self, account, contract_key, position, average_cost):
|
|
|
+ def process_position(self, account, contract_key, position, average_cost, extra_info=None):
|
|
|
|
|
|
- # look up the portfolio from the account code
|
|
|
+ # obtain a reference to the portfolio, if not exist create a new one
|
|
|
port = self.get_portfolio(account)
|
|
|
- port_item = None
|
|
|
- if port:
|
|
|
- # look up the position in the portfolio
|
|
|
- port_item = self.is_contract_in_portfolio(account, contract_key)
|
|
|
- else:
|
|
|
- # create a new portfolio
|
|
|
- port = self.create_empty_portfolio(account)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
+ port_item = port.is_contract_in_portfolio(contract_key)
|
|
|
if port_item:
|
|
|
# update the values and recalculate p/l
|
|
|
- port_item.set_position(position, average_cost)
|
|
|
- port_item.calculate_pl()
|
|
|
+ port_item.update_position(position, average_cost, extra_info)
|
|
|
+ port_item.calculate_pl(contract_key)
|
|
|
+
|
|
|
# new position
|
|
|
else:
|
|
|
port_item = PortfolioItem(account, contract_key, position, average_cost)
|
|
|
- port['port_items'][contract_key] = port_item
|
|
|
+ #port['port_items'][contract_key] = port_item
|
|
|
+ port.set_portfolio_port_item(contract_key, port_item)
|
|
|
instrument = port_item.get_instrument()
|
|
|
self.tds.add_symbol(instrument)
|
|
|
self.twsc.reqMktData(instrument.get_contract(), True)
|
|
|
@@ -281,17 +169,19 @@ class PortfolioMonitor(AbstractGatewayListener):
|
|
|
underlying = self.deduce_option_underlying(instrument)
|
|
|
if underlying:
|
|
|
oc = self.get_portfolio_option_chain(account, underlying)
|
|
|
- oc.add_option(instrument)
|
|
|
instrument.set_extra_attributes(OptionsChain.CHAIN_IDENTIFIER, oc.get_name())
|
|
|
+ oc.add_option(instrument)
|
|
|
else:
|
|
|
- logging.error('PortfolioMonitor:process_position. Error in adding the new position %s' % contract_key)
|
|
|
+ logging.error('PortfolioMonitor:process_position. **** Error in adding the new position %s' % contract_key)
|
|
|
# non options. stocks, futures that is...
|
|
|
else:
|
|
|
- port['port_items'][contract_key] = port_item
|
|
|
-
|
|
|
+ logging.info('PortfolioMonitor:process_position. Adding a new non-option position into the portfolio [%s]' % port_item.dump())
|
|
|
+ #port['port_items'][contract_key] = port_item
|
|
|
+ port.set_portfolio_port_item(contract_key, port_item)
|
|
|
|
|
|
-
|
|
|
-
|
|
|
+ #self.dump_portfolio(account)
|
|
|
+ port.dump_portfolio()
|
|
|
+
|
|
|
|
|
|
# EVENT_OPTION_UPDATED = 'oc_option_updated'
|
|
|
# EVENT_UNDERLYING_ADDED = 'oc_underlying_added
|
|
|
@@ -307,16 +197,9 @@ class PortfolioMonitor(AbstractGatewayListener):
|
|
|
self.tds.add_symbol(instrument)
|
|
|
self.twsc.reqMktData(instrument.get_contract(), True)
|
|
|
|
|
|
- #
|
|
|
- # tds call backs
|
|
|
- #
|
|
|
- #
|
|
|
- # EVENT_TICK_UPDATED = 'tds_event_tick_updated'
|
|
|
- # EVENT_SYMBOL_ADDED = 'tds_event_symbol_added'
|
|
|
- # EVENT_SYMBOL_DELETED = 'tds_event_symbol_deleted'
|
|
|
|
|
|
def tds_event_symbol_added(self, event, update_mode, name, instrument):
|
|
|
- pass
|
|
|
+ pass
|
|
|
#logging.info('tds_event_new_symbol_added. %s' % ContractHelper.object2kvstring(symbol.get_contract()))
|
|
|
|
|
|
|
|
|
@@ -327,27 +210,48 @@ class PortfolioMonitor(AbstractGatewayListener):
|
|
|
if OptionsChain.CHAIN_IDENTIFIER in s.get_extra_attributes():
|
|
|
results = {}
|
|
|
chain_id = s.get_extra_attributes()[OptionsChain.CHAIN_IDENTIFIER]
|
|
|
- logging.info('PortfolioMonitor: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:
|
|
|
- results = self.option_chains[chain_id].cal_greeks_in_chain(self.kwargs['evaluation_date'])
|
|
|
- else:
|
|
|
- results[ContractHelper.makeRedisKeyEx(s.get_contract())] = self.option_chains[chain_id].cal_option_greeks(s, self.kwargs['evaluation_date'])
|
|
|
- logging.info('AnalysticsEngine:tds_event_tick_updated. compute greek results %s' % results)
|
|
|
- # set_analytics(self, imvol=None, delta=None, gamma=None, theta=None, vega=None, npv=None):
|
|
|
- #
|
|
|
- def update_tds_analytics(key_greeks):
|
|
|
+ #logging.info('PortfolioMonitor:tds_event_tick_updated chain_id %s' % chain_id)
|
|
|
+
|
|
|
+ for acct in self.portfolios:
|
|
|
|
|
|
- self.tds.set_symbol_analytics(key_greeks[0], Option.IMPL_VOL, key_greeks[1][Option.IMPL_VOL])
|
|
|
- self.tds.set_symbol_analytics(key_greeks[0], Option.DELTA, key_greeks[1][Option.DELTA])
|
|
|
- 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])
|
|
|
+ #if chain_id in self.portfolios[acct]['opt_chains'].keys():
|
|
|
+ if chain_id in self.portfolios[acct].get_option_chains():
|
|
|
+ #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'])
|
|
|
+ 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'])
|
|
|
+ #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)
|
|
|
+
|
|
|
+ def update_portfolio_fields(key_greeks):
|
|
|
+
|
|
|
+ self.tds.set_symbol_analytics(key_greeks[0], Option.IMPL_VOL, key_greeks[1][Option.IMPL_VOL])
|
|
|
+ self.tds.set_symbol_analytics(key_greeks[0], Option.DELTA, key_greeks[1][Option.DELTA])
|
|
|
+ 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])
|
|
|
+
|
|
|
+ #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)
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if results:
|
|
|
+ #logging.info('PortfolioMonitor:tds_event_tick_updated ....before map')
|
|
|
+ map(update_portfolio_fields, list(results.iteritems()))
|
|
|
+ #logging.info('PortfolioMonitor:tds_event_tick_updated ....after map')
|
|
|
+
|
|
|
+
|
|
|
|
|
|
- map(update_tds_analytics, list(results.iteritems()))
|
|
|
-
|
|
|
else:
|
|
|
-
|
|
|
+ logging.info('PortfolioMonitor:tds_event_tick_updated ignoring uninterested ticks %s' % contract_key)
|
|
|
continue
|
|
|
|
|
|
|
|
|
@@ -355,42 +259,64 @@ class PortfolioMonitor(AbstractGatewayListener):
|
|
|
|
|
|
def tds_event_symbol_deleted(self, event, update_mode, name, instrument):
|
|
|
pass
|
|
|
- #
|
|
|
- # external ae requests
|
|
|
- #
|
|
|
- def ae_req_greeks(self, event, message_value):
|
|
|
- #(int tickerId, int field, double impliedVol, double delta, double optPrice, double pvDividend, double gamma, double vega, double theta, double undPrice)
|
|
|
- pass
|
|
|
-
|
|
|
- def ae_req_tds_internal(self, event, message_value):
|
|
|
- logging.info('received ae_req_tds_internal')
|
|
|
- self.tds.dump()
|
|
|
-
|
|
|
- #
|
|
|
- # gateway events
|
|
|
- #
|
|
|
+
|
|
|
|
|
|
def tickPrice(self, event, contract_key, field, price, canAutoExecute):
|
|
|
- logging.debug('MessageListener:%s. %s %d %8.2f' % (event, contract_key, field, price))
|
|
|
self.tds.set_symbol_tick_price(contract_key, field, price, canAutoExecute)
|
|
|
|
|
|
|
|
|
def tickSize(self, event, contract_key, field, size):
|
|
|
- self.tds.set_symbol_tick_size(contract_key, field, size)
|
|
|
+ #self.tds.set_symbol_tick_size(contract_key, field, size)
|
|
|
#logging.info('MessageListener:%s. %s: %d %8.2f' % (event, contract_key, field, size))
|
|
|
+ pass
|
|
|
|
|
|
def position(self, event, account, contract_key, position, average_cost, end_batch):
|
|
|
- self.process_position(account, contract_key, position, average_cost)
|
|
|
+ if not end_batch:
|
|
|
+ #logging.info('PortfolioMonitor:position. received position message contract=%s' % contract_key)
|
|
|
+ self.process_position(account, contract_key, position, average_cost)
|
|
|
|
|
|
- def positionEnd(self, event): #, message_value):
|
|
|
- """ generated source for method positionEnd """
|
|
|
- logging.info('%s [[ %s ]]' % (event, vars()))
|
|
|
+ else:
|
|
|
+ # to be run once during start up
|
|
|
+ # subscribe to automatic account updates
|
|
|
+ if self.starting_engine:
|
|
|
+ for acct in self.portfolios.keys():
|
|
|
+ self.portfolios[acct].g_datatable_json()
|
|
|
+ logging.info('PortfolioMonitor:position. generate gtable for ac: [%s]' % acct)
|
|
|
+ self.twsc.reqAccountUpdates(True, acct)
|
|
|
+ logging.info('PortfolioMonitor:position. subscribing to auto updates for ac: [%s]' % acct)
|
|
|
+ self.starting_engine = False
|
|
|
+
|
|
|
+ '''
|
|
|
+ the 4 account functions below are invoked by AbstractListener.update_portfolio_account.
|
|
|
+ the original message from TWS is first wrapped into update_portfolio_account event in
|
|
|
+ class TWS_event_handler and then expanded by AbstractListener.update_portfolio_account
|
|
|
+ (check tws_event_hander)
|
|
|
+ '''
|
|
|
|
|
|
+
|
|
|
+ def updateAccountValue(self, event, key, value, currency, account): # key, value, currency, accountName):
|
|
|
+ self.raw_dump(event, vars())
|
|
|
|
|
|
+ def updatePortfolio(self, event, contract_key, position, market_price, market_value, average_cost, unrealized_PNL, realized_PNL, account):
|
|
|
+ self.raw_dump(event, vars())
|
|
|
+ self.process_position(account, contract_key, position, average_cost,
|
|
|
+ {'market_price':market_price, 'market_value':market_value, 'unrealized_PNL': unrealized_PNL, 'realized_PNL': realized_PNL})
|
|
|
+
|
|
|
+
|
|
|
+ def updateAccountTime(self, event, timestamp):
|
|
|
+ self.raw_dump(event, vars())
|
|
|
+
|
|
|
+ def accountDownloadEnd(self, event, account): # accountName):
|
|
|
+ self.raw_dump(event, vars())
|
|
|
+
|
|
|
|
|
|
def error(self, event, message_value):
|
|
|
logging.info('PortfolioMonitor:%s. val->[%s]' % (event, message_value))
|
|
|
|
|
|
+ def raw_dump(self, event, items):
|
|
|
+ del(items['self'])
|
|
|
+ logging.info('%s [[ %s ]]' % (event, items))
|
|
|
+
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
@@ -406,12 +332,13 @@ if __name__ == '__main__':
|
|
|
'tws_host': 'localhost',
|
|
|
'tws_api_port': 8496,
|
|
|
'tws_app_id': 38868,
|
|
|
- 'group_id': 'AE',
|
|
|
+ 'group_id': 'PM',
|
|
|
'session_timeout_ms': 10000,
|
|
|
'clear_offsets': False,
|
|
|
'logconfig': {'level': logging.INFO, 'filemode': 'w', 'filename': '/tmp/pm.log'},
|
|
|
- 'topics': ['tickPrice', 'tickSize'],
|
|
|
- 'seek_to_end': ['*']
|
|
|
+ 'topics': ['position', 'positionEnd', 'tickPrice', 'update_portfolio_account'],
|
|
|
+ 'seek_to_end': ['*'],
|
|
|
+ 'interested_position_types': {'symbol': ['HSI', 'MHI'], 'instrument_type': ['OPT', 'FUT']}
|
|
|
|
|
|
|
|
|
}
|