Tuesday, February 24, 2009

Proof Of Concept 8 of 8c : Account Information

Account information such as account value, margin requirements, cash balance, and current portfolio holdings may be retrieve by calling EClientSocket.reqAccountUpdates(). Data are returned in EWrapper's updateAccountTime(), updateAccountValue(), and updatePortfolio() methods. The code is as follows:

require 'java'
require 'TwsApi.jar'

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


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

@mySocket

def initialize
@mySocket = EClientSocket.new(self)
end

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

def requestAccount(subscribe, acct_code)
# --------------------------------
@mySocket.reqAccountUpdates(subscribe,
acct_code)
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 ' expiry = ' + contract.m_expiry.to_s
puts ' strike = ' + contract.m_strike.to_s
puts ' right = ' + contract.m_right
puts ' multiplier = ' + contract.m_multiplier.to_s
puts ' exchange = ' + contract.m_exchange.to_s
puts ' primaryExch = ' + contract.m_primaryExch
puts ' currency = ' + contract.m_currency
puts ' localSymbol = ' + contract.m_localSymbol
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)
end

def openOrder( order_id, contract, order, order_state)
end

def updateAccountValue( key, value, currency, account_name)
puts 'updateAccountValue: ' + key + ' ' + value + ' ' + currency.to_s + ' ' + account_name
end

def updatePortfolio( contract, position, market_price, market_value,
average_cost, unrealized_pnl, realized_pnl, account_name)
puts 'updatePortfolio: --- '
contract_msg(contract)
puts ' position '+position.to_s + ', mkt price ' + market_price.to_s + ', mkt value ' + market_value.to_s +
', avg cost ' + average_cost.to_s + ', unrealized p/l ' + unrealized_pnl.to_s + ', realized p/l ' + realized_pnl.to_s + ' ' + account_name
end

def updateAccountTime( time_stamp)
puts 'updateAccountTime: ' + time_stamp.to_s
end

def nextValidId( order_id)
end

def contractDetails( req_id, contract_details)
end

def bondContractDetails( req_id, contract_details)
end

def contractDetailsEnd( req_id)
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, pWAP, 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

# ---------------------------------------------------
# -- 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

# *********************************************************************************************
# Running the code
# *********************************************************************************************
x = TestAccount.new()
begin
x.eConnect
puts 'connected ...'
x.requestAccount(true, '') # subscribe to Account data
sleep(5)
x.requestAccount(false, '') # unsubscribe to Account data
x.eDisconnect
puts 'disconnected'
rescue Exception => e
puts 'can not connect'
puts 'Exception' + e.message
x.eDisconnect
end

Lines 81-83: updateAccountValue() returns key-pair values of all the different account value types.
Lines 85-91: updatePortfolio() returns each individual equity positions held in the account.
Lines 93-95: updateAccountTime() returns last update time of the account information.

This is a screen shot of my paper trading account's View Account Information in TWS. Information returned from the three interface methods should match what is displayed here.



Run output contains contains various account values, account time, and all positions (GE, RUT March 490 Call option, and SPY). Note I've color coded the output for better visibility.

Server Version:43TWS Time at connection:20090224 12:01:06 ESTconnected ...
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
updateAccountValue: AccountCode DU58265 DU58265
updateAccountValue: AccountReady true DU58265
updateAccountValue: AccountType UNIVERSAL DU58265
updateAccountValue: AccruedCash 0.00 BASE DU58265
updateAccountValue: AccruedCash 0.00 USD DU58265
updateAccountValue: AccruedCash-S 0.00 USD DU58265
updateAccountValue: AccruedDividend 0.00 USD DU58265
updateAccountValue: AccruedDividend-S 0.00 USD DU58265
updateAccountValue: AvailableFunds 984986.06 USD DU58265
updateAccountValue: AvailableFunds-S 984986.06 USD DU58265
updateAccountValue: Billable 0.00 USD DU58265
updateAccountValue: Billable-S 0.00 USD DU58265
updateAccountValue: BuyingPower 3939894.24 USD DU58265
updateAccountValue: CashBalance 978048.56 BASE DU58265
updateAccountValue: CashBalance 978048.56 USD DU58265
updateAccountValue: Currency BASE BASE DU58265
updateAccountValue: Currency USD USD DU58265
updateAccountValue: Cushion 0.997617 DU58265
updateAccountValue: DayTradesRemaining -1 DU58265
updateAccountValue: DayTradesRemainingT+1 -1 DU58265
updateAccountValue: DayTradesRemainingT+2 -1 DU58265
updateAccountValue: DayTradesRemainingT+3 -1 DU58265
updateAccountValue: DayTradesRemainingT+4 -1 DU58265
updateAccountValue: EquityWithLoanValue 987298.56 USD DU58265
updateAccountValue: EquityWithLoanValue-S 987298.56 USD DU58265
updateAccountValue: ExcessLiquidity 984986.06 USD DU58265
updateAccountValue: ExcessLiquidity-S 984986.06 USD DU58265
updateAccountValue: ExchangeRate 1.00 BASE DU58265
updateAccountValue: ExchangeRate 1.00 USD DU58265
updateAccountValue: FullAvailableFunds 984986.06 USD DU58265
updateAccountValue: FullAvailableFunds-S 984986.06 USD DU58265
updateAccountValue: FullExcessLiquidity 984986.06 USD DU58265
updateAccountValue: FullExcessLiquidity-S 984986.06 USD DU58265
updateAccountValue: FullInitMarginReq 2312.50 USD DU58265
updateAccountValue: FullInitMarginReq-S 2312.50 USD DU58265
updateAccountValue: FullMaintMarginReq 2312.50 USD DU58265
updateAccountValue: FullMaintMarginReq-S 2312.50 USD DU58265
updateAccountValue: FutureOptionValue 0.00 BASE DU58265
updateAccountValue: FutureOptionValue 0.00 USD DU58265
updateAccountValue: FuturesPNL 0.00 BASE DU58265
updateAccountValue: FuturesPNL 0.00 USD DU58265
updateAccountValue: GrossPositionValue 9290.00 USD DU58265
updateAccountValue: GrossPositionValue-S 9290.00 USD DU58265
updateAccountValue: InitMarginReq 2312.50 USD DU58265
updateAccountValue: InitMarginReq-S 2312.50 USD DU58265
updateAccountValue: Leverage-S 0.01 DU58265
updateAccountValue: LookAheadAvailableFunds 984986.06 USD DU58265
updateAccountValue: LookAheadAvailableFunds-S 984986.06 USD DU58265
updateAccountValue: LookAheadExcessLiquidity 984986.06 USD DU58265
updateAccountValue: LookAheadExcessLiquidity-S 984986.06 USD DU58265
updateAccountValue: LookAheadInitMarginReq 2312.50 USD DU58265
updateAccountValue: LookAheadInitMarginReq-S 2312.50 USD DU58265
updateAccountValue: LookAheadMaintMarginReq 2312.50 USD DU58265
updateAccountValue: LookAheadMaintMarginReq-S 2312.50 USD DU58265
updateAccountValue: LookAheadNextChange 0 DU58265
updateAccountValue: MaintMarginReq 2312.50 USD DU58265
updateAccountValue: MaintMarginReq-S 2312.50 USD DU58265
updateAccountValue: NetLiquidation 987338.56 USD DU58265
updateAccountValue: NetLiquidation-S 987338.56 USD DU58265
updateAccountValue: NetLiquidationByCurrency 987338.56 BASE DU58265
updateAccountValue: NetLiquidationByCurrency 987338.56 USD DU58265
updateAccountValue: OptionMarketValue 40.00 BASE DU58265
updateAccountValue: OptionMarketValue 40.00 USD DU58265
updateAccountValue: PNL true DU58265
updateAccountValue: PreviousDayEquityWithLoanValue 987286.06 USD DU58265
updateAccountValue: PreviousDayEquityWithLoanValue-S 987286.06 USD DU58265
updateAccountValue: RealizedPnL 0.00 BASE DU58265
updateAccountValue: RealizedPnL 0.00 USD DU58265
updateAccountValue: RegTEquity 987298.56 USD DU58265
updateAccountValue: RegTEquity-S 987298.56 USD DU58265
updateAccountValue: RegTMargin 4625.00 USD DU58265
updateAccountValue: RegTMargin-S 4625.00 USD DU58265
updateAccountValue: SMA 984635.56 USD DU58265
updateAccountValue: SMA-S 984635.56 USD DU58265
updateAccountValue: StockMarketValue 9250.00 BASE DU58265
updateAccountValue: StockMarketValue 9250.00 USD DU58265
updateAccountValue: TotalCashBalance 978048.56 BASE DU58265
updateAccountValue: TotalCashBalance 978048.56 USD DU58265
updateAccountValue: TotalCashValue 978048.56 USD DU58265
updateAccountValue: TotalCashValue-S 978048.56 USD DU58265
updateAccountValue: UnalteredInitMarginReq -1.00 USD DU58265
updateAccountValue: UnalteredMaintMarginReq -1.00 USD DU58265
updateAccountValue: UnrealizedPnL -8555.88 BASE DU58265
updateAccountValue: UnrealizedPnL -8555.88 USD DU58265
updateAccountValue: WhatIfPMEnabled true DU58265
updatePortfolio: ---
contract_msg:
conid = 7516
symbol = GE
secType = STK
expiry =
strike = 0.0
right = 0
multiplier =
exchange =
primaryExch = ARCA
currency = USD
localSymbol = GE
position 200, mkt price 8.5550003, mkt value 1711.0, avg cost 16.745, unrealized p/l -1638.0, realized p/l 0.0 DU58265

updateAccountTime: 11:58
updatePortfolio: ---
contract_msg:
conid = 54528396
symbol = RUT
secType = OPT
expiry = 20090319
strike = 490.0
right = C
multiplier = 100
exchange =
primaryExch = AMEX
currency = USD
localSymbol = RUWCA
position 1, mkt price 0.4, mkt value 40.0, avg cost 5180.88, unrealized p/l -5140.88, realized p/l 0.0 DU58265

updateAccountTime: 11:58
updatePortfolio: ---
contract_msg:
conid = 756733
symbol = SPY
secType = STK
expiry =
strike = 0.0
right = 0
multiplier =
exchange =
primaryExch = ARCA
currency = USD
localSymbol = SPY
position 100, mkt price 75.3899994, mkt value 7539.0, avg cost 93.16, unrealized p/l -1777.0, realized p/l 0.0 DU58265

updateAccountTime: 11:58
updateAccountTime: 11:58
disconnected





03/09/09 Update

Upgrading to API v. 9.60
requires addition of these new callback methods.

 
# ---------------------------------------------------
# new callback methods added in API 9.60
# ---------------------------------------------------
def openOrderEnd() # invoked when all orders are sent to a client as a response to reqOpenOrders()
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

Monday, February 16, 2009

Proof Of Concept 8 of 8b : Historical Quote

The process of retrieving historical data is very much like getting market data. A call to reqHistoricalData() is made with specifications on how far back to request data and how that data is to be seen (also known as bar size settings). A call back function called historicalData() is used to display the data returned. Going into this, I thought it would be a snap to implement, but I encountered some technical difficulties. The problem this time is that Java code works, but JRuby code doesn't - results returned to historicalData() are incorrect.

Here is the Java version. Note that it's pretty straight forward. HistoricalQuote implements EWrapper interface and historicalData() is where the call back happens:

package ibjava;

import ib.client.*;


public class HistoricalQuote implements EWrapper {

public HistoricalQuote() throws InterruptedException {
int i = 0;
EClientSocket m_client = new EClientSocket(this);
m_client.eConnect("127.0.0.1",7496, 0);
System.out.println("Connected.");

// preparing to quote SPY
Contract contract;
contract = new Contract();

contract.m_symbol = "SPY";
contract.m_secType = "STK";
contract.m_expiry = "";
contract.m_right = "";
contract.m_multiplier = "";
contract.m_exchange = "SMART";
contract.m_primaryExch = "";
contract.m_currency = "USD";
contract.m_localSymbol = "";
// submit historical data for SPY.
// look for 3 days worth of trades ending on 02/13/2009,
// grouping results to 1 day at a time
// results will come in through historicalData() methods
m_client.reqHistoricalData(2,
contract,
"20090213 20:00:0", //yyyymmdd hh:mm:ss
"3 D", // duration,
"1 DAY", // barSizeSetting,
"TRADES", // whatToShow,
1, // useRTH,
1) ; // formatDate);
Thread.sleep(4000); // sleep for 4 seconds

m_client.eDisconnect();
System.out.println("Disconnected.");
}

public static void main(String[] args) throws InterruptedException {
System.out.println("Hello");
HistoricalQuote myBot= new HistoricalQuote();
}

// ---------------------------------------------------
// -- methods from EWrapper -- must be declared
// ---------------------------------------------------
public void tickPrice( int tickerId, int field, double price, int canAutoExecute){}
public void tickSize( int tickerId, int field, int size){}
public void tickOptionComputation( int tickerId, int field, double impliedVol, double delta, double modelPrice, double pvDividend){}
public void tickGeneric(int tickerId, int tickType, double value){}
public void tickString(int tickerId, int tickType, String value){}
public void tickEFP(int tickerId, int tickType, double basisPoints, String formattedBasisPoints, double impliedFuture, int holdDays, String futureExpiry, double dividendImpact, double dividendsToExpiry){}
public void orderStatus( int orderId, String status, int filled, int remaining, double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, String whyHeld){}
public void openOrder( int orderId, Contract contract, Order order, OrderState orderState){}
public void updateAccountValue(String key, String value, String currency, String accountName){}
public void updatePortfolio(Contract contract, int position, double marketPrice, double marketValue, double averageCost, double unrealizedPNL, double realizedPNL, String accountName){}
public void updateAccountTime(String timeStamp){}
public void nextValidId( int orderId){}
public void contractDetails(int reqId, ContractDetails contractDetails){}
public void bondContractDetails(int reqId, ContractDetails contractDetails){}
public void contractDetailsEnd(int reqId){}
public void execDetails( int orderId, Contract contract, Execution execution){}
public void updateMktDepth( int tickerId, int position, int operation, int side, double price, int size){}
public void updateMktDepthL2( int tickerId, int position, String marketMaker, int operation, int side, double price, int size){}
public void updateNewsBulletin( int msgId, int msgType, String message, String origExchange){}
public void managedAccounts( String accountsList){}
public void receiveFA(int faDataType, String xml){}

public void historicalData(int reqId, String date, double open, double high, double low, double close, int volume, int count, double WAP, boolean hasGaps)
{ System.out.println("Java> id=" + reqId + " date = " + date +
" open=" + open + " high=" + high +
" low=" + low + " close=" + close +
" volume=" + volume + " count=" + count +
" WAP=" + WAP + " hasGaps=" + hasGaps);
}

public void scannerParameters(String xml){}
public void scannerData(int reqId, int rank, ContractDetails contractDetails, String distance, String benchmark, String projection, String legsStr){}
public void scannerDataEnd(int reqId){}
public void realtimeBar(int reqId, long time, double open, double high, double low, double close, long volume, double wap, int count){}
public void currentTime(long time){}
public void fundamentalData(int reqId, String data){}

// ---------------------------------------------------
// -- methods from AnyWrapper -- must be declared
// ---------------------------------------------------

public void connectionClosed() {
System.out.println(" [API.connectionClosed] Closed connection with TWS");
}
public void error(String str) {
System.out.println(" [API.msg1] " + str);
}

public void error(int one, int two, String str) {
System.out.println(" [API.msg2] " + str + " {" + one + ", " + two + "}");
}

public void error(Exception e) {
System.out.println(" [API.msg3] " + e.getMessage());
}

}


Running this produces the following output:

Hello
Server Version:43
TWS Time at connection:20090215 22:48:29 EST
Connected.
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
[API.msg2] HMDS data farm connection is OK:ushmds2a {-1, 2106}
Java> id=2 date = 20090211 open=83.44 high=84.05 low=82.4 close=83.53 volume=2950030 count=855129 WAP=83.4 hasGaps=false
Java> id=2 date = 20090212 open=82.17 high=84.1 low=81.05 close=83.98 volume=4193652 count=1201155 WAP=82.25 hasGaps=false
Java> id=2 date = 20090213 open=83.55 high=84.24 low=82.35 close=82.46 volume=2681140 count=795253 WAP=83.44 hasGaps=false
Java> id=2 date = finished-20090210 20:00:00-20090213 20:00:00 open=-1.0 high=-1.0 low=-1.0 close=-1.0 volume=-1 count=-1 WAP=-1.0 hasGaps=false
Disconnected.


Everything is ok at this point. I checked open, high, low, close for each day and the data is accurate.
Implementing this same code in JRuby is as follows:


require 'java'
require 'TwsApi.jar'

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


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

@mySocket

def initialize
@mySocket = EClientSocket.new(self)
end

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

def requestHistStockQuote(id, ticker)
# --------------------------------
myContract = Contract.new
myContract.m_symbol = ticker
myContract.m_secType = 'STK'
myContract.m_exchange = 'SMART'
myContract.m_currency = 'USD'
request_id = id
ticklist = ''
# submit historical data for ticker
# look for 3 days worth of trades ending on 02/13/2009,
# grouping results to 1 day at a time
# results will come in through historicalData() methods
@mySocket.reqHistoricalData(request_id,
myContract,
'20090213 20:00:0', #yyyymmdd hh:mm:ss
'3 D', # duration,
'1 DAY', # barSizeSetting,
'TRADES', # whatToShow,
1, # useRTH,
1) ; # formatDate);
end

def eDisconnect
@mySocket.eDisconnect
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)
end

def openOrder( order_id, contract, order, 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

def nextValidId( order_id)
end

def contractDetails( req_id, contract_details)
end

def bondContractDetails( req_id, contract_details)
end

def contractDetailsEnd( req_id)
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, pWAP, has_gaps)
# using straight printing
puts 'ruby> id=' + req_id.to_s + ' date = ' + date +
' open=' + open.to_s + ' high=' + high.to_s +
' low=' + low.to_s + ' close=' + close.to_s +
' volume=' + volume.to_s + ' count=' + count.to_s +
' WAP=' + pWAP.to_s + ' hasGaps=' + has_gaps.to_s
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

# ---------------------------------------------------
# -- 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 TestHistData

# *********************************************************************************************
# Running the code
# *********************************************************************************************
x = TestHistData.new()
begin
x.eConnect
puts 'connected ...'
req_id = 0
x.requestHistStockQuote(req_id += 1, 'SPY') # stock
puts 'wait 5 sec'
sleep(5)
x.eDisconnect

rescue Exception => e
puts 'can not connect'
puts 'Exception' + e.message
x.eDisconnect
end


Running it produces the following output :

Server Version:43TWS Time at connection:20090215 23:08:09 ESTconnected ...
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
wait 5 sec
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
[API.msg2] HMDS data farm connection is OK:ushmds2a {-1, 2106}
ruby> id=1 date = 20090211 open=83.44 high=-1.783545891855626e+259 low=84.05 close=4.667261648644354e-62 volume=1079286169 count=-1717986918 WAP=83.53 hasGaps=true
ruby> id=1 date = 20090212 open=82.17 high=1.9991592822007892e+37 low=84.1 close=1.9035983734140052e+185 volume=1079264051 count=858993459 WAP=83.98 hasGaps=true
ruby> id=1 date = 20090213 open=83.55 high=4.667261648670389e-62 low=84.24 close=2.262040992568159e-111 volume=1079285350 count=1717986918 WAP=82.46 hasGaps=true
ruby> id=1 date = finished-20090210 20:00:00-20090213 20:00:00 open=-1.0 high=1.590978778e-314 low=-1.0 close=1.590978778e-314 volume=-1074790400 count=0 WAP=-1.0 hasGaps=false


Compare this with Java version's output (same as displayed earlier on the post, I'm just repeating it here to make it more convenient to compare).

Java> id=2 date = 20090211 open=83.44 high=84.05 low=82.4 close=83.53 volume=2950030 count=855129 WAP=83.4 hasGaps=false
Java> id=2 date = 20090212 open=82.17 high=84.1 low=81.05 close=83.98 volume=4193652 count=1201155 WAP=82.25 hasGaps=false
Java> id=2 date = 20090213 open=83.55 high=84.24 low=82.35 close=82.46 volume=2681140 count=795253 WAP=83.44 hasGaps=false
Java> id=2 date = finished-20090210 20:00:00-20090213 20:00:00 open=-1.0 high=-1.0 low=-1.0 close=-1.0 volume=-1 count=-1 WAP=-1.0 hasGaps=false


Note that id, date, and open values are the same for both Java and JRuby versions. From then on, beginning with high value, the numbers in the JRuby version just look wrong. What's interesting is that JRuby's low value matches up to Java's high value. Maybe there's something wrong with the way JRuby handles all these Java parameters with double and int datatypes?




02/17/09 Update


After some research, I suspect that the incorrect double values passed into historicalData() are a JRuby bug. It appears substantially similar to a reported bug that was fixed in version 1.1.5.

I have opened an issue on the JRuby site http://jira.codehaus.org/browse/JRUBY-3409 and will wait to see what they say...




02/22/09 08:00 Update


There has been no progress on my issue on the JRuby site. To make it easier for the developers there to see the problem, I have reconstructed the error using very simple classes and uploaded the files to their site. Hopefully this will give them what they need to come up with a fix...




02/22/09 22:40 Update


Problem has been solved! The issue has been on my end and I feel bad about having wasted all this time, but hey, lesson learned. The problem was that I had been using NetBeans IDE to perform my development and the JRuby which comes bundled with it is an old version (v 1.1.4). Although I had downloaded and installed JRuby v. 1.1.6, this was NOT the version being run from NetBeans. It was definitely a surprising moment when I discovered the same JRuby code fails when running in NetBeans (JRuby v. 1.1.4), but succeeds when I ran it at the command prompt (JRuby 1.1.6). I have closed the issue on the JRuby site explaining the situation.

To configure NetBeans to use JRuby 1.1.6, go to Tools, Ruby Platforms menu. It will open a new window showing current JRuby version. Here it is showing that it's version 1.1.4:


Click on Add Platform and select 1.1.6 version of jruby.bat that was previously installed. This version should now appear on the Ruby Platform Manager:


Last step is to change the project property by right clicking on the project in Projects window and select Properties. Click on Run in Categories, and change Ruby Platform to 1.1.6.


Finally, here is the JRuby output with correct historical quote:

Server Version:43

TWS Time at connection:20090222 19:41:12 EST

connected ...
wait 5 sec
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
[API.msg2] HMDS data farm connection is OK:ushmds2a {-1, 2106}
ruby> id=1 date = 20090211 open=83.44 high=84.05 low=82.4 close=83.53 volume=2950030 count=855129 WAP=83.4 hasGaps=false
ruby> id=1 date = 20090212 open=82.17 high=84.1 low=81.05 close=83.98 volume=4193652 count=1201155 WAP=82.25 hasGaps=false
ruby> id=1 date = 20090213 open=83.55 high=84.24 low=82.35 close=82.46 volume=2681140 count=795253 WAP=83.44 hasGaps=false
ruby> id=1 date = finished-20090210 20:00:00-20090213 20:00:00 open=-1.0 high=-1.0 low=-1.0 close=-1.0 volume=-1 count=-1 WAP=-1.0 hasGaps=false



03/09/09 Update

Upgrading to API v. 9.60
requires addition of these new callback methods.

 
# ---------------------------------------------------
# new callback methods added in API 9.60
# ---------------------------------------------------
def openOrderEnd() # invoked when all orders are sent to a client as a response to reqOpenOrders()
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

Friday, February 13, 2009

Proof Of Concept 8 of 8a : Market Quote

Lessons learned from going native have paid off. I was able to fix JRuby errors and use TWS API's market quote functionality. As I have written in the prior post, errors in the JRuby code were due to the fact that interface methods from AnyWrapper were not implemented. Once I implemented connectionClosed() and overloaded error() methods, errors immediately went away .

Obtaining market quote is performed with reqMktData() method. I was able to successfully get quotes for ETF, stock, index, and options. The code to request SPY ETF is as follows:


require 'java'
require 'TwsApi.jar'

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

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

@mySocket

def initialize
puts 'init'
@mySocket = EClientSocket.new(self)
end

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

def requestStockQuote(id, ticker)
# --------------------------------
myContract = Contract.new
myContract.m_symbol = ticker
myContract.m_secType = 'STK'
myContract.m_exchange = 'SMART'
myContract.m_currency = 'USD'
request_id = id
ticklist = ''

puts '>requestStockQuote - calling reqMktData id='+id.to_s+', ticker='+ticker
@mySocket.reqMktData(request_id, myContract, ticklist, true)
end

def requestIdxQuote(id, ticker)
# --------------------------------
myContract = Contract.new
myContract.m_symbol = ticker
myContract.m_secType = 'IND'
myContract.m_exchange = 'CBOE'
myContract.m_currency = 'USD'
request_id = id
ticklist = ''

puts '>requestIdxQuote - calling reqMktData id='+id.to_s+', ticker='+ticker
@mySocket.reqMktData(request_id, myContract, ticklist, true)
end

# expiry - string format YYYYMM
# strike - number
# call_put - CALL or PUT
def requestOptionQuote(id, ticker, expiry, strike, call_put)
# --------------------------------
myContract = Contract.new
myContract.m_symbol = ticker
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
request_id = id
ticklist = ''

puts '>requestOptionQuote - calling reqMktData id='+id.to_s+', ticker='+ticker+', '+expiry+', '+strike.to_s+', '+call_put
@mySocket.reqMktData(request_id, myContract, ticklist, true)
end


def eDisconnect
@mySocket.eDisconnect
end


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

def tickPrice( ticker_id, field, price, can_auto_execute)
puts 'tickPrice() Price Update: Id: ' + ticker_id.to_s + ' Type: '+field.to_s+' Price: ' + price.to_s
end

def tickSize( ticker_id, field, size)
puts 'tickSize() Volume Update: Id: '+ticker_id.to_s+' Type: '+field.to_s+' Size: '+size.to_s
end

def tickOptionComputation( ticker_id, field, implied_vol,
delta, model_price, pv_dividend)
puts 'tickOptionComputation() Update: Id:'+ticker_id.to_s+' Type: '+field.to_s+' IV: '+implied_vol.to_s+' Delta: '+delta.to_s
end

def tickGeneric( ticker_id, tick_type, value)
puts 'tickGeneric() Update: Id: '+ticker_id.to_s+' Type: '+tick_type.to_s+' Value: '+value.to_s
end

def tickString( ticker_id, tick_type, value)
puts 'tickString() Update: Id: '+ticker_id.to_s+' Type: '+tick_type.to_s+' Value: '+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)
end

def openOrder( order_id, contract, order, 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

def nextValidId( order_id)
end

def contractDetails( req_id, contract_details)
end

def bondContractDetails( req_id, contract_details)
end

def contractDetailsEnd( req_id)
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

# ---------------------------------------------------
# -- 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 TestMktData

# *********************************************************************************************
# Running the code
# *********************************************************************************************
x = TestMktData.new()

begin
x.eConnect
puts 'connected ...'
req_id = 0

x.requestStockQuote(req_id += 1, 'SPY') # ETF

puts 'wait 5 sec'
sleep(5)
x.eDisconnect
puts 'disconnected'
rescue Exception => e
puts 'can not connect'
puts 'Exception' + e.message
x.eDisconnect
end

Lines 184-195: contain methods that were missing from my initial code. Note that in the Java version, there were actually three error() methods, with two of them having just one parameter (of different types). Since Ruby is a loosely typed language, creating two error() methods with one parameter will generate an error. To get around this, Ruby's version of error() actually handles the job of Java's two methods. Look at lines 189-190 to see how the parameter's types are determined.
Line 209: illustrates the calling method to request quote for SPY exchange traded fund

Running this produces the following output:

Server Version:43
TWS Time at connection:20090213 14:13:43 EST
connected ...
>requestStockQuote - calling reqMktData id=1, ticker=SPY [API.msg2] Market data farm connection is OK:usopt {-1, 2104}

wait 5 sec
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
tickPrice() Price Update: Id: 1 Type: 1 Price: 83.68
tickSize() Volume Update: Id: 1 Type: 0 Size: 258
tickPrice() Price Update: Id: 1 Type: 2 Price: 83.69
tickSize() Volume Update: Id: 1 Type: 3 Size: 68
tickPrice() Price Update: Id: 1 Type: 4 Price: 83.69
tickSize() Volume Update: Id: 1 Type: 5 Size: 1
tickSize() Volume Update: Id: 1 Type: 8 Size: 1881804
tickPrice() Price Update: Id: 1 Type: 6 Price: 84.24
tickPrice() Price Update: Id: 1 Type: 7 Price: 82.75
tickPrice() Price Update: Id: 1 Type: 9 Price: 83.66
tickPrice() Price Update: Id: 1 Type: 14 Price: 83.55
tickString() Update: Id: 1 Type: 45 Value: 1234552423
disconnected



Line 209 can also be modified to request quote for American Express

x.requestStockQuote(req_id += 1, 'AXP') # stock

produces the following output:

Server Version:43TWS Time at connection:20090213 22:13:09 ESTconnected ...
>requestStockQuote - calling reqMktData id=1, ticker=AXP
wait 5 sec
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
tickSize() Volume Update: Id: 1 Type: 8 Size: 0
tickString() Update: Id: 1 Type: 45 Value: 1234562108
tickPrice() Price Update: Id: 1 Type: 4 Price: 15.69
tickSize() Volume Update: Id: 1 Type: 5 Size: 1
tickPrice() Price Update: Id: 1 Type: 6 Price: 16.33
tickPrice() Price Update: Id: 1 Type: 7 Price: 15.58
tickPrice() Price Update: Id: 1 Type: 9 Price: 16.19
tickPrice() Price Update: Id: 1 Type: 14 Price: 16.05
tickPrice() Price Update: Id: 1 Type: 1 Price: 15.56
tickSize() Volume Update: Id: 1 Type: 0 Size: 2
tickPrice() Price Update: Id: 1 Type: 2 Price: 15.95
tickSize() Volume Update: Id: 1 Type: 3 Size: 1
disconnected



To request Russell 2000 small cap index from Chicago Board Options Exchange

x.requestIdxQuote(req_id += 1, 'RUT') # index

produces the following output:

Server Version:43
TWS Time at connection:20090213 14:14:19 EST
connected ...
>requestIdxQuote - calling reqMktData id=1, ticker=RUT
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
wait 5 sec
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
tickPrice() Price Update: Id: 1 Type: 4 Price: 451.93
tickSize() Volume Update: Id: 1 Type: 5 Size: 0
tickSize() Volume Update: Id: 1 Type: 8 Size: 0
tickPrice() Price Update: Id: 1 Type: 6 Price: 0.0
tickPrice() Price Update: Id: 1 Type: 7 Price: 0.0
tickPrice() Price Update: Id: 1 Type: 9 Price: 450.42
tickString() Update: Id: 1 Type: 45 Value: 1234552454
disconnected



To request options quote for GE September, 2009 15 Call

x.requestOptionQuote(req_id += 1, 'GE', '200909', 15, 'CALL' )

produces the following output:

Server Version:43
TWS Time at connection:20090213 14:14:44 EST
connected ...
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
>requestOptionQuote - calling reqMktData id=1, ticker=GE, 200909, 15, CALL
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
wait 5 sec
tickPrice() Price Update: Id: 1 Type: 1 Price: 0.9
tickSize() Volume Update: Id: 1 Type: 0 Size: 1200
tickPrice() Price Update: Id: 1 Type: 2 Price: 0.94
tickSize() Volume Update: Id: 1 Type: 3 Size: 648
tickPrice() Price Update: Id: 1 Type: 4 Price: 0.94
tickSize() Volume Update: Id: 1 Type: 5 Size: 9
tickSize() Volume Update: Id: 1 Type: 8 Size: 116
tickPrice() Price Update: Id: 1 Type: 6 Price: 1.0
tickPrice() Price Update: Id: 1 Type: 7 Price: 0.88
tickPrice() Price Update: Id: 1 Type: 9 Price: 0.97
tickOptionComputation() Update: Id:1 Type: 10 IV: 0.6091767213831093 Delta: 3.3175946061912392e-87
tickOptionComputation() Update: Id:1 Type: 11 IV: 0.6211516922231228 Delta: -0.0
tickOptionComputation() Update: Id:1 Type: 12 IV: 0.6211516922231228 Delta: -0.0
tickString() Update: Id: 1 Type: 32 Value: CIXPB
tickString() Update: Id: 1 Type: 33 Value: CIXPB
tickString() Update: Id: 1 Type: 45 Value: 1234551366
tickString() Update: Id: 1 Type: 33 Value: CIPB
tickOptionComputation() Update: Id:1 Type: 10 IV: 0.6091767213831093 Delta: 3.3175946061941284e-87
tickOptionComputation() Update: Id:1 Type: 11 IV: 0.6211516922231228 Delta: -0.0
tickOptionComputation() Update: Id:1 Type: 12 IV: 0.6211516922231228 Delta: -0.0
tickOptionComputation() Update: Id:1 Type: 13 IV: 0.615161765120708 Delta: -0.0
tickOptionComputation() Update: Id:1 Type: 10 IV: 0.606880041775532 Delta: -0.0
tickOptionComputation() Update: Id:1 Type: 11 IV: 0.6188416326918191 Delta: -5.379410170061903e+268
tickOptionComputation() Update: Id:1 Type: 12 IV: 0.6188416326918191 Delta: -5.379410170061903e+268
tickOptionComputation() Update: Id:1 Type: 13 IV: 0.6128581636676679 Delta: -0.0
disconnected



To request options quote for Russell 2000 index December, 2009 450 Put

x.requestOptionQuote(req_id += 1, 'RUT', '200912', 450, 'PUT' )

produces the following output:

Server Version:43
TWS Time at connection:20090213 14:15:15 EST
connected ...
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
>requestOptionQuote - calling reqMktData id=1, ticker=RUT, 200912, 450, PUT
wait 5 sec
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
tickPrice() Price Update: Id: 1 Type: 1 Price: 71.2
tickSize() Volume Update: Id: 1 Type: 0 Size: 311
tickPrice() Price Update: Id: 1 Type: 2 Price: 73.2
tickSize() Volume Update: Id: 1 Type: 3 Size: 201
tickSize() Volume Update: Id: 1 Type: 8 Size: 0
tickPrice() Price Update: Id: 1 Type: 9 Price: 71.85
tickOptionComputation() Update: Id:1 Type: 10 IV: 0.4371941774336616 Delta: 1.5035109594887676e+46
tickOptionComputation() Update: Id:1 Type: 11 IV: 0.44973728365842186 Delta: -1.7139093657526427e+170
tickOptionComputation() Update: Id:1 Type: 12 IV: 0.44126908872009857 Delta: -0.0
tickString() Update: Id: 1 Type: 32 Value: CIX
tickString() Update: Id: 1 Type: 33 Value: CIB
tickOptionComputation() Update: Id:1 Type: 10 IV: 0.4371941774336616 Delta: 1.503510959488021e+46
tickOptionComputation() Update: Id:1 Type: 11 IV: 0.44973728365842186 Delta: -1.713909365751876e+170
tickOptionComputation() Update: Id:1 Type: 12 IV: 0.44126908872009857 Delta: -0.0
tickOptionComputation() Update: Id:1 Type: 13 IV: 0.4434638974508833 Delta: 8.778371095955947e+183
tickOptionComputation() Update: Id:1 Type: 10 IV: 0.438042894164516 Delta: 8.899823344244101e-231
tickOptionComputation() Update: Id:1 Type: 11 IV: 0.45058242706938006 Delta: -0.0
tickOptionComputation() Update: Id:1 Type: 12 IV: 0.4421166435405054 Delta: -0.0
tickOptionComputation() Update: Id:1 Type: 13 IV: 0.44431082241353415 Delta: -0.0
tickString() Update: Id: 1 Type: 32 Value: CIXB
tickString() Update: Id: 1 Type: 32 Value: CIX
disconnected



03/09/09 Update

Upgrading to API v. 9.60
requires addition of these new callback methods.

 
# ---------------------------------------------------
# new callback methods added in API 9.60
# ---------------------------------------------------
def openOrderEnd() # invoked when all orders are sent to a client as a response to reqOpenOrders()
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

Wednesday, February 11, 2009

Proof Of Concept 8 of 8 : Yeah!

If you keep at it long enough, something's bound to work. I had success today with requesting market quote. Building on my prior experience (ignoring the fake 'error' messages), code was added to instantiate Contract object and request quote for SPY ETF.


package ibjava;

import com.ib.client.*;


public class StockBot implements EWrapper {

public StockBot() throws InterruptedException {
int i = 0;
EClientSocket m_client = new EClientSocket(this);
m_client.eConnect("127.0.0.1",7496, 0);
System.out.println("Connected.");

// preparing to quote SPY
Contract contract;
contract = new Contract();

contract.m_symbol = "SPY";
contract.m_secType = "STK";
contract.m_expiry = "";
contract.m_right = "";
contract.m_multiplier = "";
contract.m_exchange = "SMART";
contract.m_primaryExch = "";
contract.m_currency = "USD";
contract.m_localSymbol = "";
// submit quote for SPY. results will come in through
// tickPrice() and tickSize() methods
m_client.reqMktData(1,contract,null , true);

Thread.sleep(4000); // sleep for 4 seconds

m_client.eDisconnect();
System.out.println("Disconnected.");
}

public static void main(String[] args) throws InterruptedException {

System.out.println("Hello");
StockBot myBot= new StockBot();
}

// ---------------------------------------------------
// -- methods from EWrapper -- must be declared
// ---------------------------------------------------
public void tickPrice( int tickerId, int field, double price, int canAutoExecute){
System.out.println("Price Update: Id: "+tickerId+" Type: "+field+" Price: "+price);
}
public void tickSize( int tickerId, int field, int size){
System.out.println("Volume Update: Id: "+tickerId+" Type: "+field+" Size: "+size);
}
public void tickOptionComputation( int tickerId, int field, double impliedVol, double delta, double modelPrice, double pvDividend){}
public void tickGeneric(int tickerId, int tickType, double value){}
public void tickString(int tickerId, int tickType, String value){}
public void tickEFP(int tickerId, int tickType, double basisPoints, String formattedBasisPoints, double impliedFuture, int holdDays, String futureExpiry, double dividendImpact, double dividendsToExpiry){}
public void orderStatus( int orderId, String status, int filled, int remaining, double avgFillPrice, int permId, int parentId, double lastFillPrice, int clientId, String whyHeld){}
public void openOrder( int orderId, Contract contract, Order order, OrderState orderState){}

public void updateAccountValue(String key, String value, String currency, String accountName){}
public void updatePortfolio(Contract contract, int position, double marketPrice, double marketValue, double averageCost, double unrealizedPNL, double realizedPNL, String accountName){}
public void updateAccountTime(String timeStamp){}
public void nextValidId( int orderId){}
public void contractDetails(int reqId, ContractDetails contractDetails){}
public void bondContractDetails(int reqId, ContractDetails contractDetails){}
public void contractDetailsEnd(int reqId){}
public void execDetails( int orderId, Contract contract, Execution execution){}
public void updateMktDepth( int tickerId, int position, int operation, int side, double price, int size){}
public void updateMktDepthL2( int tickerId, int position, String marketMaker, int operation, int side, double price, int size){}
public void updateNewsBulletin( int msgId, int msgType, String message, String origExchange){}
public void managedAccounts( String accountsList){}
public void receiveFA(int faDataType, String xml){}
public void historicalData(int reqId, String date, double open, double high, double low, double close, int volume, int count, double WAP, boolean hasGaps){}
public void scannerParameters(String xml){}
public void scannerData(int reqId, int rank, ContractDetails contractDetails, String distance, String benchmark, String projection, String legsStr){}
public void scannerDataEnd(int reqId){}
public void realtimeBar(int reqId, long time, double open, double high, double low, double close, long volume, double wap, int count){}
public void currentTime(long time){}
public void fundamentalData(int reqId, String data){}

// ---------------------------------------------------
// -- methods from AnyWrapper -- must be declared
// ---------------------------------------------------

public void connectionClosed() {
System.out.println(" [API.connectionClosed] Closed connection with TWS");
}
public void error(String str) {
System.out.println(" [API.msg1] " + str);
}

public void error(int one, int two, String str) {
System.out.println(" [API.msg2] " + str + " {" + one + ", " + two + "}");
}

public void error(Exception e) {
System.out.println(" [API.msg3] " + e.getMessage());
}

}


Running the code above with 'Incoming Connection' dialog ON and OFF works the same way. The output is:


Hello
Server Version:43
TWS Time at connection:20090211 22:58:59 EST
Connected.
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
Price Update: Id: 1 Type: 1 Price: 83.6
Volume Update: Id: 1 Type: 0 Size: 5
Price Update: Id: 1 Type: 2 Price: 83.62
Volume Update: Id: 1 Type: 3 Size: 9
Price Update: Id: 1 Type: 4 Price: 83.61
Volume Update: Id: 1 Type: 5 Size: 1
Volume Update: Id: 1 Type: 8 Size: 3213164
Price Update: Id: 1 Type: 6 Price: 84.05
Price Update: Id: 1 Type: 7 Price: 82.4
Price Update: Id: 1 Type: 9 Price: 83.11
Price Update: Id: 1 Type: 14 Price: 83.45
Disconnected.
BUILD SUCCESSFUL (total time: 13 seconds)



So I have proven that Java code works. What was missing from JRuby code were the methods from AnyWrapper. Adding them in will be the next step.

Sunday, February 8, 2009

Proof Of Concept - 8 of 8 : Going Native

After playing around with JRuby and TWS API trying to resolve the problems described in my prior post, there has been no real progress. I have decided to go native and try out the sample code in Java to ascertain if the problem is with Java or JRuby. In NetBeans, I created a new Java Application called IBJava, created a new class and copied TWS API classes under the IBJava\Src folder. As with everything in this project, nothing is as easy as it seems. The final code to just connect and disconnect from TWS is as follows:


package ibjava;

import com.ib.client.*;


public class StockBot implements EWrapper {

public StockBot() {
EClientSocket m_client = new EClientSocket(this);
m_client.eConnect("127.0.0.1",7496, 0);
System.out.println("Connected.");
while (i<990000) i++;
m_client.eDisconnect();
}

public static void main(String[] args) {

System.out.println("Hello");
StockBot myBot= new StockBot();
}
// ---------------------------------------------------
// -- methods from EWrapper -- must be declared
// ---------------------------------------------------
public void tickPrice( int tickerId, int field, double price, int canAutoExecute){
System.out.println("Price Update: Id: "+tickerId+" Type: "+field+" Price: "+price);
}
public void tickSize( int tickerId, int field, int size){
System.out.println("Volume Update: Id: "+tickerId+" Type: "+field+" Size: "+size);
}
public void tickOptionComputation( int tickerId, int field, double impliedVol,
double delta, double modelPrice, double pvDividend){
}
public void tickGeneric(int tickerId, int tickType, double value){
}
public void tickString(int tickerId, int tickType, String value){
}
public void tickEFP(int tickerId, int tickType, double basisPoints,
String formattedBasisPoints, double impliedFuture, int holdDays,
String futureExpiry, double dividendImpact, double dividendsToExpiry){}
public void orderStatus( int orderId, String status, int filled, int remaining,
double avgFillPrice, int permId, int parentId, double lastFillPrice,
int clientId, String whyHeld){
}
public void openOrder( int orderId, Contract contract, Order order, OrderState orderState){
}

public void updateAccountValue(String key, String value, String currency, String accountName){}
public void updatePortfolio(Contract contract, int position, double marketPrice, double marketValue,
double averageCost, double unrealizedPNL, double realizedPNL, String accountName){}
public void updateAccountTime(String timeStamp){}
public void nextValidId( int orderId){}
public void contractDetails(int reqId, ContractDetails contractDetails){}
public void bondContractDetails(int reqId, ContractDetails contractDetails){}
public void contractDetailsEnd(int reqId){}
public void execDetails( int orderId, Contract contract, Execution execution){}
public void updateMktDepth( int tickerId, int position, int operation, int side, double price, int size){}
public void updateMktDepthL2( int tickerId, int position, String marketMaker, int operation,
int side, double price, int size){}
public void updateNewsBulletin( int msgId, int msgType, String message, String origExchange){}
public void managedAccounts( String accountsList){}
public void receiveFA(int faDataType, String xml){}
public void historicalData(int reqId, String date, double open, double high, double low,
double close, int volume, int count, double WAP, boolean hasGaps){}
public void scannerParameters(String xml){}
public void scannerData(int reqId, int rank, ContractDetails contractDetails, String distance,
String benchmark, String projection, String legsStr){}
public void scannerDataEnd(int reqId){}
public void realtimeBar(int reqId, long time, double open, double high, double low, double close, long volume, double wap, int count){}
public void currentTime(long time){}
public void fundamentalData(int reqId, String data){}

// ---------------------------------------------------
// -- methods from AnyWrapper -- must be declared
// ---------------------------------------------------

public void connectionClosed() {
System.out.println(" [API.connectionClosed] Closed connection with TWS");
}
public void error(String str) {
System.out.println(" [API.msg1] " + str);
}

public void error(int one, int two, String str) {
System.out.println(" [API.msg2] " + str + " {" + one + ", " + two + "}");
}

public void error(Exception e) {
System.out.println(" [API.msg3] " + e.getMessage());
}

}


Some interesting things to point out here:

1) If lines 76-90 are omitted, the following error is generated:


java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Uncompilable source code - ibjava.StockBot is not abstract and does not override abstract method connectionClosed() in com.ib.client.AnyWrapper
at ibjava.StockBot.(StockBot.java:11)
Exception in thread "main"
Exception in thread "main" Java Result: 1


This is due to the fact that EWrapper interface is a subclass of AnyWrapper interface, which means that I have to create AnyWrapper interface methods in addition to EWrapper interface methods. I did not do this in my JRuby code due to my limited Java knowledge. This error needs to be fixed in the JRuby version.


2) Running the code above with 'Incoming Connection' dialog ON seems to work. The output is:


run:
Hello
Server Version:43
TWS Time at connection:20090208 21:45:55 EST
Connected.
[API.connectionClosed] Closed connection with TWS
BUILD SUCCESSFUL (total time: 2 seconds)



3) Running the code above with 'Incoming Connection' dialog OFF generates the error:

run:
Hello
Server Version:43
TWS Time at connection:20090208 21:49:48 EST
Connected.
[API.msg2] Market data farm connection is OK:usopt {-1, 2104}
[API.msg2] Market data farm connection is OK:usfarm {-1, 2104}
[API.msg3] socket closed
[API.connectionClosed] Closed connection with TWS
BUILD SUCCESSFUL (total time: 0 seconds)


It's clear to me now that there are issues in Java that I need to understand and resolve before even attempting to get JRuby to work. By working at Java level, I at least get some useful error messages instead of JRuby's cryptic ones.

C'est la vie! My initial approach to this project was to be free from working with Java, but that turns out not to be the case. I obviously need to get my hands 'dirty' and this process will take a little more time. The bright side to this is that Java resources are everywhere and I'm sure I'll be able to get this fixed. Stay tuned...




02/11/09 Update
It occurred to me that the 'errors' I experienced may not actually be errors at all. After some research, it appears that just because the error() methods were triggered, doesn't actually mean errors occurred. They may just be messages that I can ignore. This is good news. With this in mind, I will proceed with Java coding.