Wednesday, July 15, 2009

Proof Of Concept 8 of 8g: Complex Options Order

Complex (or combination) option orders are orders composed of more than one leg for the same underlying security. They are submitted and executed as a single transaction and price. TWS complex orders allow for a maximum of up to four legs. Examples of different options strategies and corresponding legs are:

# of Legs Strategies
2 vertical, diagonal, calendar, straddle, strangle
3 butterfly
4 iron condor, iron butterfly, double diagonal


In this coding example, I will create a market order to buy a vertical call spread which is composed of:
BUY 1 SPY AUG 50 CALL
SELL 1 SPY AUG 55 CALL

The steps of creating a complex order are:
1) Use EClientSocket.reqContractDetails() method to get contract id for each option leg.
2) Create a ComboLeg object for each option leg using contract id from step 1).
3) Create a Contract object and add ComboLeg objects created from step 2).
4) Create an Order object and submit it using Contract object from step 3).



# To change this template, choose Tools | Templates
# and open the template in the editor.
require 'java'
require 'TwsApi.jar'

include_class 'ib.client.EClientSocket'
include_class 'ib.client.Contract'
include_class 'ib.client.Order'
include_class 'ib.client.AnyWrapper'
include_class 'ib.client.EWrapper'
include_class 'ib.client.ComboLeg'

class TestComboOptionsOrder
include EWrapper #note "class TestMktData < EWrapper" syntax is no longer supported in JRuby 1.0

attr_accessor :order_id, :contractArray
@mySocket


def initialize
puts 'init'
@mySocket = EClientSocket.new(self)
@contractArray = Array.new # array of hash
end

def eConnect
@mySocket.eConnect("127.0.0.1", 7496, 0)
end

# Request all open orders that were placed from all API clients linked to one TWS,
# and also from the TWS
# This is a one time shot call. When its done you DO NOT continue to receive
# information about new placed orders
def reqAllOpenOrders
@mySocket.reqAllOpenOrders
end

def submitOptionsOrder(order_id, symbol, call_put, expiry, strike,
buy_sell, qty, order_type, limit_price, tif)
# --------------------------------
myContract = Contract.new
myContract.m_symbol = symbol
myContract.m_secType = 'OPT'
myContract.m_exchange = 'SMART'
myContract.m_currency = 'USD'
# options specific below ---
myContract.m_expiry = expiry # string format YYYYMM
myContract.m_right = call_put # CALL, PUT
myContract.m_strike = strike # number

myOrder = Order.new
myOrder.m_action = buy_sell # BUY, SELL
myOrder.m_totalQuantity = qty
myOrder.m_orderType = order_type # MKT, LMT, STP, SPLMT, TRAIL, etc
myOrder.m_lmtPrice = limit_price
myOrder.m_tif = tif # DAY, GTC
@mySocket.placeOrder(order_id, myContract, myOrder)
end

# new method to submit order
def submitOrder(order_id, myContract, myOrder)
@mySocket.placeOrder(order_id, myContract, myOrder)
end

def cancelOrder(order_id)
@mySocket.cancelOrder(order_id)
end

# contract details will be received via the contractDetails() function on the EWrapper
def reqContractDetails (req_id, contract)
@mySocket.reqContractDetails(req_id, contract)
end

def eDisconnect
@mySocket.eDisconnect
end


def contract_msg(contract)
puts ' contract_msg:'
puts ' conid = ' + contract.m_conId.to_s
puts ' symbol = ' + contract.m_symbol
puts ' secType = ' + contract.m_secType
puts ' exchange = ' + contract.m_exchange.to_s
puts ' currency = ' + contract.m_currency
puts ' localSymbol = ' + contract.m_localSymbol.to_s
puts ' multiplier = ' + contract.m_multiplier.to_s
puts ' expiry = ' + contract.m_expiry.to_s
puts ' strike = ' + contract.m_strike.to_s
puts ' right = ' + contract.m_right.to_s
end

def contract_details_msg(contract_detail)
puts ' contract_details_msg:'
puts ' marketName = ' + contract_detail.m_marketName.to_s
puts ' tradingClass = ' + contract_detail.m_tradingClass.to_s
puts ' minTick = ' + contract_detail.m_minTick.to_s
puts ' price magnifier = ' + contract_detail.m_priceMagnifier.to_s
puts ' orderTypes = ' + contract_detail.m_orderTypes.to_s
puts ' validExchanges = ' + contract_detail.m_validExchanges.to_s
puts ' underConId = ' + contract_detail.m_underConId.to_s
end

def order_msg(order)
puts ' order_msg:'
puts ' order id =' + order.m_orderId.to_s
puts ' client id =' + order.m_clientId.to_s
puts ' action =' + order.m_action.to_s
puts ' quantity =' + order.m_totalQuantity.to_s
puts ' type =' + order.m_orderType.to_s
puts ' lmtPrice =' + order.m_lmtPrice.to_s
puts ' TIF =' + order.m_tif.to_s
puts ' parent Id =' + order.m_parentId.to_s
puts ' permId =' + order.m_permId.to_s


end

def order_state_msg(order_state)
puts ' order_state_msg:'
puts ' status =' + order_state.m_status.to_s
puts ' initMargin =' + order_state.m_initMargin.to_s
puts ' maintMargin =' + order_state.m_maintMargin.to_s
puts ' commission =' + order_state.m_commission.to_s

end

#///////////////////////////////////////////////////////////////////////
#// Interface methods
#///////////////////////////////////////////////////////////////////////

def tickPrice( ticker_id, field, price, can_auto_execute)
end

def tickSize( ticker_id, field, size)
end

def tickOptionComputation( ticker_id, field, implied_vol,
delta, model_price, pv_dividend)
end

def tickGeneric( ticker_id, tick_type, value)
end

def tickString( ticker_id, tick_type, value)
end

def tickEFP( ticker_id, tick_type, basis_points,
formatted_basis_points, implied_future, hold_days,
future_expiry, dividend_impact, dividends_to_expiry)
end

def orderStatus( order_id, status, filled, remaining,
avg_fill_price, perm_id, parent_id, last_fill_price,
client_id, why_held)
puts '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
puts 'orderStatus>: orderId=' + order_id.to_s + ', clientId=' + client_id.to_s + ', permId=' + perm_id.to_s +
', status=' + status + ' filled=' + filled.to_s + ', remaining=' + remaining.to_s + ', '
puts ' avgFillPrice=' + avg_fill_price.to_s + ', lastFillPrice=' + last_fill_price.to_s +
', parent Id=' + parent_id.to_s + ', whyHeld=' + why_held.to_s
end

def openOrder( order_id, contract, order, order_state)
puts '--------------------------------------------------------'
puts 'openOrder> open order: orderId=' + order_id.to_s + ' --- '
contract_msg(contract)
order_msg(order)
order_state_msg(order_state)
end

def updateAccountValue( key, value, currency, account_name)
end

def updatePortfolio( contract, position, market_price, market_value,
average_cost, unrealized_pnl, realized_pnl, account_name)
end

def updateAccountTime( time_stamp)
end

# triggered right after logging into TWS
def nextValidId( order_id)
@order_id = order_id #storing order id to be used when placing order
puts 'nextValidId> Next Valid Order Id = ' + @order_id.to_s
end

# call back with details. determine which request it was and store details
# data with the associated request.
def contractDetails( req_id, contract_details)
aContract = contract_details.m_summary
# find the hash with matching :req_id and set :conid
@contractArray.find{ |contract| contract[:req_id] == req_id } [:conid] = aContract.m_conId

puts 'contractDetails()> reqId = ' + req_id.to_s + ' -------------------'
contract_msg(aContract)
contract_details_msg(contract_details)
puts ' --------'
end

def bondContractDetails( req_id, contract_details)
end

def contractDetailsEnd( req_id)
puts 'contractDetailsEnd() ' + req_id.to_s + '> Ended ------------------------'
end

def execDetails( order_id, contract, execution)
end

def updateMktDepth( ticker_id, position, operation, side, price, size)
end

def updateMktDepthL2( ticker_id, position, market_maker, operation,
side, price, size)
end

def updateNewsBulletin( msg_id, msgType, message, orig_exchange)
end

def managedAccounts( accounts_list)
end

def receiveFA( fa_data_type, xml)
end

def historicalData( req_id, date, open, high, low,
close, volume, count, eWAP, has_gaps)
end

def scannerParameters( xml)
end

def scannerData( req_id, rank, contract_details, distance,
benchmark, projection, legs_str)
end

def scannerDataEnd( req_id)
end

def realtimeBar( req_id, time, open, high, low, close, volume, wap, count)
end

def currentTime( time)
end

def fundamentalData( req_id, data)
end

# ---------------------------------------------------
# new callback methods added in API 9.60
# ---------------------------------------------------
def openOrderEnd() # invoked when all orders are sent to a client as a response to reqOpenOrders(),
# reqAllOpenOrders and right after connected to TWS
puts 'openOrderEnd> Ended ------------------------'
end

def accountDownloadEnd(account_name) #invoked when a complete snapshot of an account state is sent to a client as a response to reqAccountUpdates()
end

def execDetailsEnd( req_id) # invoked when all executions are sent to a client as a response to reqExecutions()
end

def deltaNeutralValidation(req_id, under_comp) # for Delta-Neutral DN RFQ
end

# ---------------------------------------------------
# -- methods from AnyWrapper -- must be declared
# ---------------------------------------------------

def connectionClosed()
puts ' [API.connectionClosed] Closed connection with TWS'
end

def error(err_obj_str)
puts ' [API.msg1] ' + err_obj_str if err_obj_str.is_a?(String)
puts ' [API.msg3] ' + err_obj_str.getMessage() if err_obj_str.is_a?(Exception)
end

def error(one, two, str)
puts ' [API.msg2] ' + str + ' {' + one.to_s + ', ' + two.to_s + '}'
end

end # class

# *********************************************************************************************
# Running the code
# *********************************************************************************************
x = TestComboOptionsOrder.new()
begin
x.eConnect
puts 'connected ...'
sleep(2) # need this here to give time for nextValidId() to be called
puts '******************************'
puts '** STEP 1) ******************'
puts '.. Defining contract for Aug 90 C '
spyAug90c = Contract.new
spyAug90c.m_symbol = 'SPY'
spyAug90c.m_secType = 'OPT'
spyAug90c.m_exchange = 'SMART'
spyAug90c.m_currency = 'USD'
spyAug90c.m_expiry = '20090821' # string format YYYYMM or YYYYMMDD -- if YYYYMM used and normal and quarterly
# options exist, will return contract details for both
spyAug90c.m_right = 'CALL' # CALL, PUT
spyAug90c.m_strike = 90 # number

puts '.. Adding Aug 90 C to contract array'
request_id = 1
x.contractArray << {:req_id => request_id, :contract => spyAug90c, :conid => '' }

puts '.. Requesting contract detail and id info'
x.reqContractDetails(request_id, spyAug90c)
sleep(5);
puts ' '
puts '.. Defining contract for Aug 95 C '
spyAug95c = Contract.new
spyAug95c.m_symbol = 'SPY'
spyAug95c.m_secType = 'OPT'
spyAug95c.m_exchange = 'SMART'
spyAug95c.m_currency = 'USD'
spyAug95c.m_expiry = '20090821' # string format YYYYMM or YYYYMMDD -- if YYYYMM used and normal and quarterly
# options exist, will return contract details for both
spyAug95c.m_right = 'CALL' # CALL, PUT
spyAug95c.m_strike = 95 # number
puts '.. Adding Jul 95 C to contract array'
request_id = 2
x.contractArray << {:req_id => request_id, :contract => spyAug95c, :conid => '' }
puts 'Requesting contract detail and id info'
x.reqContractDetails(request_id, spyAug95c)
sleep(5);

puts '******************************'
puts '** STEP 2 ******************'
puts '.. Now have info on both contracts, create combo leg for each'
puts '.. Create leg #1'
leg1 = ComboLeg.new(x.contractArray.find{ |id| id[:req_id] == 1 } [:conid], # contract id
1, # ratio
'BUY', # BUY or SELL
'SMART', # p_exchange
0 # p_openClose- always 0 for retail customers
)
puts '.. Create leg #2'
leg2 = ComboLeg.new(x.contractArray.find{ |id| id[:req_id] == 2 } [:conid], # contract id
1, # ratio
'SELL', # BUY or SELL
'SMART', # p_exchange
0 # p_openClose- always 0 for retail customers
)
puts '******************************'
puts '** STEP 3 ******************'
puts '.. Create Contract'

myContract = Contract.new
myContract.m_symbol = 'SPY'
myContract.m_secType = 'BAG' # BAG is a combination type
myContract.m_exchange = 'SMART'
myContract.m_currency = 'USD'

puts '.. Add leg 1 to combo contract'
myContract.m_comboLegs.add(leg1)
puts '.. Add leg 2 to combo contract'
myContract.m_comboLegs.add(leg2)
puts '******************************'
puts '** STEP 4 ******************'
puts '.. Create combo Order'
myOrder = Order.new
myOrder.m_action = 'BUY' # BUY, SELL
myOrder.m_totalQuantity = 1
myOrder.m_orderType = 'MKT' # MKT, LMT, STP, SPLMT, TRAIL, etc
myOrder.m_tif = 'DAY' # DAY, GTC

puts '.. submitting order'
x.submitOrder(x.order_id += 1, myContract, myOrder)
sleep(5);
puts '==========================='
puts '*** requesting open orders ***'
x.reqAllOpenOrders
sleep(5)
x.eDisconnect
puts 'disconnected'
rescue Exception => e
puts 'Exception ' + e.message
x.eDisconnect
end





Running the code produces the following output:


init
Server Version:44TWS Time at connection:20090712 16:51:47 ESTconnected ...
nextValidId> Next Valid Order Id = 81
openOrderEnd> Ended ------------------------
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
******************************
** STEP 1) ******************

.. Defining contract for Aug 90 C
.. Adding Aug 90 C to contract array
.. Requesting contract detail and id info
contractDetails()> reqId = 1 -------------------
contract_msg:
conid = 60388362
symbol = SPY
secType = OPT
exchange = SMART
currency = USD
localSymbol = SWGHL
multiplier = 100
expiry = 20090821
strike = 90.0
right = C
contract_details_msg:
marketName = SWG
tradingClass = SWG
minTick = 0.01
price magnifier = 1
orderTypes = ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,COND,CONDORDER,DAY,DEACT,DEACTEOD,FOK,GAT,GTC,GTD,GTT,HID,HPENNY,ICE,IOC,LIT,LMT,MIT,MKT,MTL,NONALGO,OCA,PAON,RELSTK,SCALE,SCALERST,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,VOLAT,WHATIF,
validExchanges = SMART,AMEX,BOX,CBOE,IBSX,ISE,MIBSX,NASDAQOM,PHLX,PSE
underConId = 756733
--------
contractDetailsEnd() 1> Ended ------------------------

.. Defining contract for Aug 95 C
.. Adding Jul 95 C to contract array
Requesting contract detail and id info
contractDetails()> reqId = 2 -------------------
contract_msg:
conid = 60388382
symbol = SPY
secType = OPT
exchange = SMART
currency = USD
localSymbol = SWGHQ
multiplier = 100
expiry = 20090821
strike = 95.0
right = C
contract_details_msg:
marketName = SWG
tradingClass = SWG
minTick = 0.01
price magnifier = 1
orderTypes = ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,COND,CONDORDER,DAY,DEACT,DEACTEOD,FOK,GAT,GTC,GTD,GTT,HID,HPENNY,ICE,IOC,LIT,LMT,MIT,MKT,MTL,NONALGO,OCA,PAON,RELSTK,SCALE,SCALERST,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,VOLAT,WHATIF,
validExchanges = SMART,AMEX,BOX,CBOE,IBSX,ISE,MIBSX,NASDAQOM,PHLX,PSE
underConId = 756733
--------
contractDetailsEnd() 2> Ended ------------------------
******************************
** STEP 2 ******************

.. Now have info on both contracts, create combo leg for each
.. Create leg #1
.. Create leg #2
******************************
** STEP 3 ******************

.. Create Contract
.. Add leg 1 to combo contract
.. Add leg 2 to combo contract
******************************
** STEP 4 ******************

.. Create combo Order
.. submitting order
[API.msg2] Order Message:
Warning: your order will not be placed at the exchange until 2009-07-13 09:30:00 US/Eastern {82, 399}
--------------------------------------------------------
openOrder> open order: orderId=82 ---
contract_msg:
conid = 28812380
symbol = SPY
secType = BAG
exchange = SMART
currency = USD
localSymbol = SPY
multiplier =
expiry =
strike = 0.0
right = ?
order_msg:
order id =82
client id =0
action =BUY
quantity =1
type =MKT
lmtPrice =0.0
TIF =DAY
parent Id =0
permId =1009111242
order_state_msg:
status =PreSubmitted
initMargin =
maintMargin =
commission =1.7976931348623157e+308
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
orderStatus>: orderId=82, clientId=0, permId=1009111242, status=PreSubmitted filled=0, remaining=1,
avgFillPrice=0.0, lastFillPrice=0.0, parent Id=0, whyHeld=
===========================
*** requesting open orders ***
--------------------------------------------------------
openOrder> open order: orderId=82 ---
contract_msg:
conid = 28812380
symbol = SPY
secType = BAG
exchange = SMART
currency = USD
localSymbol = SPY
multiplier =
expiry =
strike = 0.0
right = ?
order_msg:
order id =82
client id =0
action =BUY
quantity =1
type =MKT
lmtPrice =0.0
TIF =DAY
parent Id =0
permId =1009111242
order_state_msg:
status =PreSubmitted
initMargin =
maintMargin =
commission =1.7976931348623157e+308
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
orderStatus>: orderId=82, clientId=0, permId=1009111242, status=PreSubmitted filled=0, remaining=1,
avgFillPrice=0.0, lastFillPrice=0.0, parent Id=0, whyHeld=
openOrderEnd> Ended ------------------------
disconnected


I ran the code after hours and was able to confirm that the order had been placed in TWS.


To see the individual legs of the order, just right click on the order and select 'Show Legs'



Individual legs will be displayed.



When the market opens the next day, my vertical call spread order executed at market price.



That's all there is to it. Complex option orders are not all that complex after all.

10 comments:

Anonymous said...

Interesting - thanks.
Commission seems a little high :) How should one work out when to ignore a value > 0 ?

Maverick said...

Sorry, what language is this? I thought you were planning to code in Java?

System Trader said...

Anonymous,
I don't understand your question. Please elaborate. thanks

System Trader said...

Maverick,
The language is Ruby, which is much easier to use than Java. I'm using a Ruby implementation called JRuby which allows me to write Ruby code while having access to Java routines. Best of both worlds.

Maverick said...

Does JRuby support multi-threading?

System Trader said...

I believe it does. To what extent, I'm not sure

Anonymous said...

Please can you send me the syntax for placeorder( they are many things which may not be applibale to me)if I want to Buy 1 IBM limit=90 GTC.
Thanks

Anonymous said...

Please can you send me the syntax for placeorder( they are many things which may not be applibale to me)if I want to Buy 100 Stock IBM limit=90 GTC. ( wanted to be clear that it is not a option just plain stock)
Thanks

System Trader said...

Stock order is covered is this post:

http://tradingbot.blogspot.com/2009/03/proof-of-concept-8-of-8e-stock-order.html

This should take you 90% there.

Anonymous said...

It is very interesting for me to read this article. Thank author for it. I like such topics and anything connected to this matter. I would like to read a bit more on that blog soon.
Alex
Phone jammer