portfolio_item.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. # -*- coding: utf-8 -*-
  2. import sys, traceback
  3. import logging
  4. import json
  5. import math
  6. import time, datetime
  7. import copy
  8. from optparse import OptionParser
  9. from time import sleep
  10. from misc2.helpers import ContractHelper
  11. from finopt.instrument import Symbol, Option
  12. from rethink.option_chain import OptionsChain
  13. from rethink.tick_datastore import TickDataStore
  14. from numpy import average
  15. from rethink.table_model import AbstractTableModel
  16. from gtk.keysyms import percent
  17. class PortfolioRules():
  18. rule_map = {
  19. 'symbol': {'HSI' : 'FUT', 'MHI' : 'FUT', 'QQQ' : 'STK'},
  20. 'expiry': {'HSI' : 'same_month', 'MHI': 'same_month', 'STK': 'leave_blank'},
  21. 'option_structure': {
  22. 'HSI': {'spd_size': 200, 'multiplier': 50, 'rate': 0.0012, 'div': 0, 'trade_vol':0.15},
  23. 'MHI': {'spd_size': 200, 'multiplier': 10, 'rate': 0.0012, 'div': 0, 'trade_vol':0.15}
  24. },
  25. 'exchange': {'HSI': 'HKFE', 'MHI': 'HKFE'},
  26. 'interested_position_types': {'symbol': ['HSI', 'MHI'], 'instrument_type': ['OPT', 'FUT']}
  27. }
  28. class PortfolioItem():
  29. """
  30. """
  31. POSITION = 7001
  32. AVERAGE_COST = 7002
  33. POSITION_DELTA = 7003
  34. POSITION_THETA = 7004
  35. POSITION_GAMMA = 7009
  36. UNREAL_PL = 7005
  37. PERCENT_GAIN_LOSS = 7006
  38. AVERAGE_PRICE = 7007
  39. MARKET_VALUE = 7008
  40. def __init__(self, account, contract_key, position, average_cost):
  41. self.contract_key = contract_key
  42. self.account_id = account
  43. self.port_fields = {PortfolioItem.POSITION: position,
  44. PortfolioItem.AVERAGE_COST: average_cost,
  45. PortfolioItem.POSITION_DELTA: float('nan'),
  46. PortfolioItem.POSITION_THETA: float('nan'),
  47. PortfolioItem.UNREAL_PL: float('nan'),
  48. PortfolioItem.PERCENT_GAIN_LOSS: float('nan'),
  49. PortfolioItem.AVERAGE_PRICE: float('nan'),
  50. PortfolioItem.MARKET_VALUE: float('nan')
  51. }
  52. contract = ContractHelper.makeContractfromRedisKeyEx(contract_key)
  53. if contract.m_secType == 'OPT':
  54. self.instrument = Option(contract)
  55. else:
  56. self.instrument = Symbol(contract)
  57. def set_port_field(self, id, value):
  58. self.port_fields[id] = value
  59. def get_port_field(self, id):
  60. try:
  61. return self.port_fields[id]
  62. except:
  63. return None
  64. def get_port_fields(self):
  65. return self.port_fields
  66. def get_contract_key(self):
  67. return self.contract_key
  68. def get_right(self):
  69. return self.instrument.get_contract().m_right
  70. def get_symbol_id(self):
  71. return self.instrument.get_contract().m_symbol
  72. def get_expiry(self):
  73. return self.instrument.get_contract().m_expiry
  74. def get_strike(self):
  75. return self.instrument.get_contract().m_strike
  76. def get_quantity(self):
  77. return self.port_fields[PortfolioItem.POSITION]
  78. def get_average_cost(self):
  79. return self.port_fields[PortfolioItem.AVERAGE_COST]
  80. def get_market_value(self):
  81. return self.port_fields[PortfolioItem.MARKET_VALUE]
  82. def get_instrument(self):
  83. return self.instrument
  84. def get_instrument_type(self):
  85. return self.instrument.get_contract().m_secType
  86. def get_account(self):
  87. return self.account_id
  88. def calculate_pl(self, contract_key):
  89. #logging.info('PortfolioItem:calculate_pl. %s' % self.dump())
  90. '''
  91. POSITION = 7001
  92. AVERAGE_COST = 7002
  93. POSITION_DELTA = 7003
  94. POSITION_THETA = 7004
  95. UNREAL_PL = 7005
  96. PERCENT_GAIN_LOSS = 7006
  97. AVERAGE_PRICE = 7007
  98. MARKET_VALUE = 7008
  99. '''
  100. try:
  101. assert contract_key == self.contract_key
  102. if self.get_instrument_type() == 'OPT':
  103. spot_px = self.instrument.get_tick_value(4)
  104. multiplier = PortfolioRules.rule_map['option_structure'][self.get_symbol_id()]['multiplier']
  105. pos_delta = self.get_quantity() * self.instrument.get_tick_value(Option.DELTA) * multiplier
  106. pos_theta = self.get_quantity() * self.instrument.get_tick_value(Option.THETA) * multiplier
  107. pos_gamma = self.get_quantity() * self.instrument.get_tick_value(Option.GAMMA) * multiplier
  108. #(spot premium * multiplier - avgcost) * pos)
  109. try:
  110. unreal_pl = (spot_px * multiplier - self.get_average_cost()) * self.get_quantity()
  111. #print "%f %f %d" % (spot_px, self.get_average_cost(), multiplier)
  112. percent_gain_loss = (1 - spot_px / (self.get_average_cost() / multiplier)) * 100 \
  113. if self.get_quantity() < 0 else \
  114. (spot_px - self.get_average_cost() / multiplier) / (self.get_average_cost() / multiplier) * 100
  115. average_px = self.get_average_cost() / multiplier
  116. except ZeroDivisionError, TypeError:
  117. # caught error for cases where get_average_cost and quantity may be None
  118. unreal_pl = float('nan')
  119. percent_gain_loss = float('nan')
  120. average_px = float('nan')
  121. else:
  122. pos_delta = self.get_quantity() * 1.0 * \
  123. PortfolioRules.rule_map['option_structure'][self.get_symbol_id()]['multiplier']
  124. pos_theta = 0
  125. pos_gamma = 0
  126. # (S - X) * pos * multiplier
  127. unreal_pl = (self.instrument.get_tick_value(4) - self.get_average_cost() ) * self.get_quantity() * \
  128. PortfolioRules.rule_map['option_structure'][self.get_symbol_id()]['multiplier']
  129. sign = abs(self.get_quantity()) / self.get_quantity()
  130. percent_gain_loss = sign * (spot_px - self.get_average_cost() / multiplier) / (self.get_average_cost() / multiplier) * 100
  131. average_px = self.get_average_cost() / multiplier
  132. self.set_port_field(PortfolioItem.POSITION_DELTA, pos_delta)
  133. self.set_port_field(PortfolioItem.POSITION_THETA, pos_theta)
  134. self.set_port_field(PortfolioItem.POSITION_GAMMA, pos_gamma)
  135. self.set_port_field(PortfolioItem.UNREAL_PL, unreal_pl)
  136. self.set_port_field(PortfolioItem.AVERAGE_PRICE, average_px)
  137. self.set_port_field(PortfolioItem.PERCENT_GAIN_LOSS, percent_gain_loss)
  138. except Exception, err:
  139. logging.error(traceback.format_exc())
  140. #logging.info('PortfolioItem:calculate_pl. %s' % self.dump())
  141. def update_position(self, position, average_cost, extra_info):
  142. self.set_port_field(PortfolioItem.POSITION, position)
  143. self.set_port_field(PortfolioItem.AVERAGE_COST, average_cost)
  144. if extra_info:
  145. self.set_port_field(PortfolioItem.MARKET_VALUE, extra_info['market_value'])
  146. def dump(self):
  147. s= ", ".join('[%s:%8.2f]' % (k, v) for k,v in self.port_fields.iteritems())
  148. return 'PortfolioItem contents: %s %s %s' % (self.contract_key, self.account_id, s)
  149. class Portfolio(AbstractTableModel):
  150. '''
  151. portfolio :
  152. {
  153. 'port_items': {<contract_key>, PortItem},
  154. 'opt_chains': {<oc_id>: option_chain},
  155. 'g_table':{'rows':{...} , 'cols':{...},
  156. 'header':{...},
  157. 'row_index': <curr_index>,
  158. 'ckey_to_row_index':{<contract_key>: <row_id>},
  159. 'row_to_ckey_index':{<row_id>: <contract_key>}
  160. }
  161. '''
  162. def __init__(self, account):
  163. self.account = account
  164. self.create_empty_portfolio()
  165. AbstractTableModel.__init__(self)
  166. def is_contract_in_portfolio(self, contract_key):
  167. return self.get_portfolio_port_item(contract_key)
  168. def get_portfolio_port_item(self, contract_key):
  169. try:
  170. return self.port['port_items'][contract_key]
  171. except KeyError:
  172. return None
  173. def create_empty_portfolio(self):
  174. self.port = {}
  175. self.port['port_items']= {}
  176. self.port['opt_chains']= {}
  177. self.port['g_table']= {'row_index': 0, 'ckey_to_row_index': {}, 'row_to_ckey_index': {}}
  178. self.init_table()
  179. return self.port
  180. def set_portfolio_port_item(self, contract_key, port_item):
  181. self.port['port_items'][contract_key] = port_item
  182. '''
  183. update the gtable contract_key to row number index
  184. '''
  185. self.update_ckey_row_xref(contract_key)
  186. def is_oc_in_portfolio(self, oc_id):
  187. try:
  188. return self.port['opt_chains'][oc_id]
  189. except KeyError:
  190. return None
  191. def get_option_chain(self, oc_id):
  192. return self.is_oc_in_portfolio(oc_id)
  193. def set_option_chain(self, oc_id, oc):
  194. self.port['opt_chains'][oc_id] = oc
  195. def get_option_chains(self):
  196. return self.port['opt_chains']
  197. def calculate_item_pl(self, contract_key):
  198. self.port['port_items'][contract_key].calculate_pl(contract_key)
  199. def dump_portfolio(self):
  200. #<account_id>: {'port_items': {<contract_key>, instrument}, 'opt_chains': {<oc_id>: option_chain}}
  201. def print_port_items(x):
  202. return '[%s]: %s %s' % (x[0], ', '.join('%s: %s' % (k,str(v)) for k, v in x[1].get_port_fields().iteritems()),
  203. ', '.join('%s: %s' % (k,str(v)) for k, v in x[1].get_instrument().get_tick_values().iteritems()))
  204. p_items = map(print_port_items, [x for x in self.port['port_items'].iteritems()])
  205. logging.info('PortfolioMonitor:dump_portfolio %s' % ('\n'.join(p_items)))
  206. return '\n'.join(p_items)
  207. '''
  208. implement AbstractTableModel methods and other routines
  209. '''
  210. def init_table(self):
  211. self.port['g_table']['header'] = [('symbol', 'Symbol', 'string'), ('right', 'Right', 'string'), ('avgcost', 'Avg Cost', 'number'), ('market_value', 'Market Value', 'number'),
  212. ('avgpx', 'Avg Price', 'number'), ('spotpx', 'Spot Price', 'number'), ('pos', 'Quantity', 'number'),
  213. ('delta', 'Delta', 'number'), ('theta', 'Theta', 'number'), ('gamma', 'Gamma', 'number'),
  214. ('pos_delta', 'P. Delta', 'number'), ('pos_theta', 'P. Theta', 'number'), ('pos_gamma', 'P. Gamma', 'number'),
  215. ('unreal_pl', 'Unreal P/L', 'number'), ('percent_gain_loss', '% gain/loss', 'number')
  216. ]
  217. def update_ckey_row_xref(self, contract_key):
  218. row_id = self.port['g_table']['row_index']
  219. self.port['g_table']['ckey_to_row_index'][contract_key] = row_id
  220. self.port['g_table']['row_to_ckey_index'][row_id] = contract_key
  221. self.port['g_table']['row_index'] += 1
  222. def ckey_to_row(self, contract_key):
  223. return self.port['g_table']['ckey_to_row_index'][contract_key]
  224. def get_column_count(self):
  225. return len(self.port['g_table']['header'])
  226. def get_row_count(self):
  227. p_items = [x for x in self.port['port_items'].iteritems()]
  228. p1_items = filter(lambda x: x[1].get_symbol_id() in PortfolioRules.rule_map['interested_position_types']['symbol'], p_items)
  229. p2_items = filter(lambda x: x[1].get_instrument_type() in PortfolioRules.rule_map['interested_position_types']['instrument_type'], p1_items)
  230. return len(p2_items)
  231. def get_column_name(self, col):
  232. return self.port['g_table']['header'][col][1]
  233. def get_column_id(self, col):
  234. return self.port['g_table']['header'][col][0]
  235. def get_value_at(self, row, col):
  236. ckey = self.port['g_table']['row_to_ckey_index'][row]
  237. p_item = self.port['port_items'][ckey]
  238. raise NotImplementedError
  239. def get_values_at(self, row):
  240. ckey = self.port['g_table']['row_to_ckey_index'][row]
  241. p_item = self.port['port_items'][ckey]
  242. return self.port_item_to_row_fields(p_item)
  243. def port_item_to_row_fields(self, x):
  244. def handle_NaN(n):
  245. # the function JSON.parse will fail at the javascript side if it encounters
  246. # a NaN value in the json string. Convert Nan to null to circumvent the issue
  247. return None if math.isnan(n) else n
  248. rf = [{'v': '%s-%s-%s' % (x[1].get_symbol_id(), x[1].get_expiry(), x[1].get_strike())},
  249. {'v': x[1].get_right()},
  250. {'v': handle_NaN(x[1].get_port_field(PortfolioItem.AVERAGE_COST))},
  251. {'v': handle_NaN(x[1].get_port_field(PortfolioItem.MARKET_VALUE))},
  252. {'v': handle_NaN(x[1].get_port_field(PortfolioItem.AVERAGE_PRICE))},
  253. {'v': handle_NaN(self.get_spot_px(x[1]))},
  254. {'v': x[1].get_quantity()},
  255. {'v': handle_NaN(x[1].get_instrument().get_tick_value(Option.DELTA))},
  256. {'v': handle_NaN(x[1].get_instrument().get_tick_value(Option.THETA))},
  257. {'v': handle_NaN(x[1].get_instrument().get_tick_value(Option.GAMMA))},
  258. {'v': handle_NaN(x[1].get_port_field(PortfolioItem.POSITION_DELTA))},
  259. {'v': handle_NaN(x[1].get_port_field(PortfolioItem.POSITION_THETA))},
  260. {'v': handle_NaN(x[1].get_port_field(PortfolioItem.POSITION_GAMMA))},
  261. {'v': handle_NaN(x[1].get_port_field(PortfolioItem.UNREAL_PL))},
  262. {'v': handle_NaN(x[1].get_port_field(PortfolioItem.PERCENT_GAIN_LOSS))}]
  263. return rf
  264. def set_value_at(self, row, col, value):
  265. pass
  266. def get_spot_px(self, x):
  267. px = float('nan')
  268. if x.get_quantity() > 0:
  269. px= x.get_instrument().get_tick_value(Symbol.BID)
  270. elif x.get_quantity() < 0:
  271. px= x.get_instrument().get_tick_value(Symbol.ASK)
  272. if px == -1:
  273. return x.get_instrument().get_tick_value(Symbol.LAST)
  274. return px
  275. def get_JSON(self):
  276. dtj = {'cols':[], 'rows':[], 'ckey_to_row_index':{}}
  277. # header fields
  278. map(lambda hf: dtj['cols'].append({'id': hf[0], 'label': hf[1], 'type': hf[2]}), self.port['g_table']['header'])
  279. p_items = sorted([x for x in self.port['port_items'].iteritems()])
  280. p1_items = filter(lambda x: x[1].get_symbol_id() in PortfolioRules.rule_map['interested_position_types']['symbol'], p_items)
  281. p2_items = filter(lambda x: x[1].get_instrument_type() in PortfolioRules.rule_map['interested_position_types']['instrument_type'], p1_items)
  282. map(lambda p: dtj['rows'].append({'c': self.port_item_to_row_fields(p)}), p2_items)
  283. return json.dumps(dtj) #, indent=4)