ib-ruby 0.4.3 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +32 -0
- data/HISTORY +68 -0
- data/README.rdoc +9 -6
- data/VERSION +1 -1
- data/bin/account_info +29 -0
- data/bin/contract_details +37 -0
- data/bin/depth_of_market +43 -0
- data/bin/historic_data +62 -0
- data/bin/{RequestHistoricData → historic_data_cli} +46 -91
- data/bin/market_data +49 -0
- data/bin/option_data +45 -0
- data/bin/template +21 -0
- data/bin/time_and_sales +63 -0
- data/lib/ib-ruby/connection.rb +166 -0
- data/lib/ib-ruby/constants.rb +91 -0
- data/lib/ib-ruby/messages/incoming.rb +807 -0
- data/lib/ib-ruby/messages/outgoing.rb +573 -0
- data/lib/ib-ruby/messages.rb +8 -1445
- data/lib/ib-ruby/models/bar.rb +26 -0
- data/lib/ib-ruby/models/contract.rb +335 -0
- data/lib/ib-ruby/models/execution.rb +55 -0
- data/lib/ib-ruby/models/model.rb +20 -0
- data/lib/ib-ruby/models/order.rb +262 -0
- data/lib/ib-ruby/models.rb +11 -0
- data/lib/ib-ruby/socket.rb +50 -0
- data/lib/ib-ruby/symbols/forex.rb +32 -72
- data/lib/ib-ruby/symbols/futures.rb +47 -68
- data/lib/ib-ruby/symbols/options.rb +30 -0
- data/lib/ib-ruby/symbols/stocks.rb +23 -0
- data/lib/ib-ruby/symbols.rb +9 -0
- data/lib/ib-ruby.rb +7 -8
- data/lib/legacy/bin/account_info_old +36 -0
- data/lib/legacy/bin/historic_data_old +81 -0
- data/lib/legacy/bin/market_data_old +68 -0
- data/lib/legacy/datatypes.rb +485 -0
- data/lib/legacy/ib-ruby.rb +10 -0
- data/lib/legacy/ib.rb +226 -0
- data/lib/legacy/messages.rb +1458 -0
- data/lib/version.rb +2 -3
- data/spec/ib-ruby/models/contract_spec.rb +261 -0
- data/spec/ib-ruby/models/order_spec.rb +64 -0
- data/spec/ib-ruby_spec.rb +0 -131
- metadata +106 -76
- data/bin/AccountInfo +0 -67
- data/bin/HistoricToCSV +0 -111
- data/bin/RequestMarketData +0 -78
- data/bin/SimpleTimeAndSales +0 -98
- data/bin/ib-ruby +0 -8
- data/lib/ib-ruby/datatypes.rb +0 -400
- data/lib/ib-ruby/ib.rb +0 -242
data/lib/ib-ruby.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module IbRuby
|
4
|
-
end # module IbRuby
|
1
|
+
module IB
|
2
|
+
end # module IbRuby
|
5
3
|
|
6
|
-
require '
|
7
|
-
require 'ib-ruby/
|
4
|
+
require 'version'
|
5
|
+
require 'ib-ruby/constants'
|
6
|
+
require 'ib-ruby/connection'
|
7
|
+
require 'ib-ruby/models'
|
8
|
+
require 'ib-ruby/symbols'
|
8
9
|
require 'ib-ruby/messages'
|
9
|
-
require 'ib-ruby/symbols/forex'
|
10
|
-
require 'ib-ruby/symbols/futures'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script connects to IB API, subscribes to account info and prints out
|
4
|
+
# messages received from IB (update every 3 minute or so)
|
5
|
+
|
6
|
+
require 'pathname'
|
7
|
+
LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
|
8
|
+
$LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'bundler/setup'
|
12
|
+
require 'ib-ruby'
|
13
|
+
|
14
|
+
# First, connect to IB TWS.
|
15
|
+
ib = IB::IB.new
|
16
|
+
|
17
|
+
# Uncomment this for verbose debug messages:
|
18
|
+
# IB::IBLogger.level = Logger::Severity::DEBUG
|
19
|
+
|
20
|
+
## Subscribe to the messages that TWS sends in response to account data request
|
21
|
+
ib.subscribe(IB::IncomingMessages::AccountValue) { |msg| puts msg.to_human }
|
22
|
+
|
23
|
+
ib.subscribe(IB::IncomingMessages::PortfolioValue) { |msg| puts msg.to_human }
|
24
|
+
|
25
|
+
ib.subscribe(IB::IncomingMessages::AccountUpdateTime) { |msg| puts msg.to_human }
|
26
|
+
|
27
|
+
ib.dispatch(IB::OutgoingMessages::RequestAccountData.new(:subscribe => true,
|
28
|
+
:account_code => ''))
|
29
|
+
|
30
|
+
puts "\nSubscribing to IB account data"
|
31
|
+
puts "\n******** Press <Enter> to cancel... *********\n\n"
|
32
|
+
gets
|
33
|
+
puts "Cancelling account data subscription.."
|
34
|
+
|
35
|
+
ib.dispatch(IB::OutgoingMessages::RequestAccountData.new(:subscribe => false,
|
36
|
+
:account_code => ''))
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script downloads historic data for specific symbol from IB
|
4
|
+
#
|
5
|
+
# TODO: Fix the Historical command line client
|
6
|
+
|
7
|
+
require 'pathname'
|
8
|
+
LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
|
9
|
+
$LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'bundler/setup'
|
13
|
+
require 'ib-ruby'
|
14
|
+
|
15
|
+
### Configurable Options
|
16
|
+
Quiet = false # if Quiet == false, status data will be printed to STDERR
|
17
|
+
Timeout = 10 # How long to wait when no messages are received from TWS before exiting, in seconds
|
18
|
+
SymbolToRequest = IB::Symbols::Forex[:gbpusd]
|
19
|
+
|
20
|
+
# Definition of what we want market data for. We have to keep track
|
21
|
+
# of what ticker id corresponds to what symbol ourselves, because the
|
22
|
+
# ticks don't include any other identifying information.
|
23
|
+
#
|
24
|
+
# The choice of ticker ids is, as far as I can tell, arbitrary.
|
25
|
+
#
|
26
|
+
# Note that as of 4/07 there is no historical data available for forex spot.
|
27
|
+
#
|
28
|
+
@market = {123 => SymbolToRequest}
|
29
|
+
|
30
|
+
# To determine when the timeout has passed.
|
31
|
+
@last_msg_time = Time.now.to_i + 2
|
32
|
+
|
33
|
+
# Connect to IB TWS.
|
34
|
+
ib = IB::IB.new
|
35
|
+
|
36
|
+
# Uncomment this for verbose debug messages:
|
37
|
+
# IB::IBLogger.level = Logger::Severity::DEBUG
|
38
|
+
|
39
|
+
# Now, subscribe to HistoricalData incoming events. The code passed in the block
|
40
|
+
# will be executed when a message of that type is received, with the received
|
41
|
+
# message as its argument. In this case, we just print out the data.
|
42
|
+
#
|
43
|
+
# Note that we have to look the ticker id of each incoming message
|
44
|
+
# up in local memory to figure out what it's for.
|
45
|
+
#
|
46
|
+
# (N.B. The description field is not from IB TWS. It is defined
|
47
|
+
# locally in forex.rb, and is just arbitrary text.)
|
48
|
+
|
49
|
+
ib.subscribe(IB::IncomingMessages::HistoricalData, lambda { |msg|
|
50
|
+
|
51
|
+
STDERR.puts @market[msg.data[:req_id]].description + ": " + msg.data[:item_count].to_s("F") + " items:" unless Quiet
|
52
|
+
|
53
|
+
msg.data[:history].each { |datum|
|
54
|
+
|
55
|
+
@last_msg_time = Time.now.to_i
|
56
|
+
|
57
|
+
STDERR.puts " " + datum.to_s("F") unless Quiet
|
58
|
+
STDOUT.puts "#{datum.date},#{datum.open.to_s("F")},#{datum.high.to_s("F")},#{datum.low.to_s("F")},#{datum.close.to_s("F")},#{datum.volume}"
|
59
|
+
}
|
60
|
+
})
|
61
|
+
|
62
|
+
# Now we actually request historical data for the symbols we're
|
63
|
+
# interested in. TWS will respond with a HistoricalData message,
|
64
|
+
# which will be received by the code above.
|
65
|
+
|
66
|
+
@market.each_pair do |id, contract|
|
67
|
+
msg = IB::OutgoingMessages::RequestHistoricalData.new(:ticker_id => id,
|
68
|
+
:contract => contract,
|
69
|
+
:end_date_time => Time.now.to_ib,
|
70
|
+
:duration => (360).to_s, # how long before end_date_time to request in seconds - this means 1 day
|
71
|
+
:bar_size => IB::OutgoingMessages::RequestHistoricalData::BarSizes.index(:hour),
|
72
|
+
:what_to_show => :trades,
|
73
|
+
:use_RTH => 0,
|
74
|
+
:format_date => 2)
|
75
|
+
ib.dispatch(msg)
|
76
|
+
end
|
77
|
+
|
78
|
+
while true
|
79
|
+
exit(0) if Time.now.to_i > @last_msg_time + Timeout
|
80
|
+
sleep 1
|
81
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script connects to IB API, subscribes to market data for specific symbols
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'pathname'
|
7
|
+
require 'bundler/setup'
|
8
|
+
|
9
|
+
LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
|
10
|
+
$LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
|
11
|
+
|
12
|
+
require 'ib-ruby'
|
13
|
+
|
14
|
+
# Definition of what we want market data for. We have to keep track
|
15
|
+
# of what ticker id corresponds to what symbol ourselves, because the
|
16
|
+
# ticks don't include any other identifying information.
|
17
|
+
#
|
18
|
+
# The choice of ticker ids is, as far as I can tell, arbitrary.
|
19
|
+
#
|
20
|
+
@market = {123 => IB::Symbols::Forex[:gbpusd],
|
21
|
+
456 => IB::Symbols::Forex[:eurusd],
|
22
|
+
789 => IB::Symbols::Forex[:usdcad]}
|
23
|
+
|
24
|
+
# First, connect to IB TWS.
|
25
|
+
ib = IB::IB.new
|
26
|
+
|
27
|
+
# Now, subscribe to TickerPrice and TickerSize events. The code
|
28
|
+
# passed in the block will be executed when a message of that type is
|
29
|
+
# received, with the received message as its argument. In this case,
|
30
|
+
# we just print out the tick.
|
31
|
+
#
|
32
|
+
# Note that we have to look the ticker id of each incoming message
|
33
|
+
# up in local memory to figure out what it's for.
|
34
|
+
#
|
35
|
+
# (N.B. The description field is not from IB TWS. It is defined
|
36
|
+
# locally in forex.rb, and is just arbitrary text.)
|
37
|
+
|
38
|
+
ib.subscribe(IB::IncomingMessages::TickPrice, lambda { |msg|
|
39
|
+
puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
|
40
|
+
})
|
41
|
+
|
42
|
+
ib.subscribe(IB::IncomingMessages::TickSize, lambda { |msg|
|
43
|
+
puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
|
44
|
+
})
|
45
|
+
|
46
|
+
|
47
|
+
# Now we actually request market data for the symbols we're interested in.
|
48
|
+
|
49
|
+
@market.each_pair { |id, contract|
|
50
|
+
msg = IB::OutgoingMessages::RequestMarketData.new({
|
51
|
+
:ticker_id => id,
|
52
|
+
:contract => contract
|
53
|
+
})
|
54
|
+
ib.dispatch(msg)
|
55
|
+
}
|
56
|
+
|
57
|
+
puts "\nSubscribed to market data"
|
58
|
+
puts "\n******** Press <Enter> to cancel... *********\n\n"
|
59
|
+
gets
|
60
|
+
puts "Cancelling market data subscription.."
|
61
|
+
|
62
|
+
@market.each_pair { |id, contract|
|
63
|
+
msg = IB::OutgoingMessages::CancelMarketData.new({
|
64
|
+
:ticker_id => id,
|
65
|
+
:contract => contract
|
66
|
+
})
|
67
|
+
ib.dispatch(msg)
|
68
|
+
}
|
@@ -0,0 +1,485 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2006 Blue Voodoo Magic LLC.
|
3
|
+
#
|
4
|
+
# This library is free software; you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Lesser General Public License as
|
6
|
+
# published by the Free Software Foundation; either version 2.1 of the
|
7
|
+
# License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This library is distributed in the hope that it will be useful, but
|
10
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
12
|
+
# Lesser General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU Lesser General Public
|
15
|
+
# License along with this library; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
17
|
+
# 02110-1301 USA
|
18
|
+
#
|
19
|
+
|
20
|
+
#
|
21
|
+
# TODO: Implement equals() according to the criteria in IB's Java client.
|
22
|
+
#
|
23
|
+
|
24
|
+
module IB
|
25
|
+
|
26
|
+
module Datatypes
|
27
|
+
attr_reader :created_at
|
28
|
+
|
29
|
+
class AbstractDatum
|
30
|
+
|
31
|
+
def init
|
32
|
+
@created_at = Time.now
|
33
|
+
end
|
34
|
+
|
35
|
+
# If a hash is given, keys are taken as attribute names, values as data.
|
36
|
+
# The attrs of the instance are set automatically from the attributeHash.
|
37
|
+
#
|
38
|
+
# If no hash is given, #init is called in the instance. #init
|
39
|
+
# should set the datum up in a generic state.
|
40
|
+
#
|
41
|
+
def initialize(attributeHash=nil)
|
42
|
+
if attributeHash.nil?
|
43
|
+
init
|
44
|
+
else
|
45
|
+
raise ArgumentError.new("Argument must be a Hash") unless attributeHash.is_a?(Hash)
|
46
|
+
attributeHash.keys.each { |key|
|
47
|
+
self.send((key.to_s + "=").to_sym, attributeHash[key])
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end # AbstractDatum
|
52
|
+
|
53
|
+
|
54
|
+
# This is used within HistoricData messages.
|
55
|
+
# Instantiate with a Hash of attributes, to be auto-set via initialize in AbstractDatum.
|
56
|
+
class Bar < AbstractDatum
|
57
|
+
attr_accessor :date, :open, :high, :low, :close, :volume, :wap, :has_gaps
|
58
|
+
# :bar_count => @socket.read_int
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
"<Bar: #{@date}; OHLC: #{@open.to_s}, #{@high.to_s}, #{@low.to_s}, #{@close.to_s}; volume: #{@volume}; wap: #{@wap.to_s}; has_gaps: #{@has_gaps}>"
|
62
|
+
end
|
63
|
+
end # Bar
|
64
|
+
|
65
|
+
|
66
|
+
class Order < AbstractDatum
|
67
|
+
# Constants used in Order objects. Drawn from Order.java
|
68
|
+
Origin_Customer = 0
|
69
|
+
Origin_Firm = 1
|
70
|
+
|
71
|
+
Opt_Unknown = '?'
|
72
|
+
Opt_Broker_Dealer = 'b'
|
73
|
+
Opt_Customer = 'c'
|
74
|
+
Opt_Firm = 'f'
|
75
|
+
Opt_Isemm = 'm'
|
76
|
+
Opt_Farmm = 'n'
|
77
|
+
Opt_Specialist = 'y'
|
78
|
+
|
79
|
+
OCA_Cancel_with_block = 1
|
80
|
+
OCA_Reduce_with_block = 2
|
81
|
+
OCA_Reduce_non_block = 3
|
82
|
+
|
83
|
+
# Box orders consts:
|
84
|
+
Box_Auction_Match = 1
|
85
|
+
Box_Auction_Improvement = 2
|
86
|
+
Box_Auction_Transparent = 3
|
87
|
+
|
88
|
+
# Volatility orders consts:
|
89
|
+
Volatility_Type_Daily = 1
|
90
|
+
Volatility_Type_Annual = 2
|
91
|
+
Volatility_Ref_Price_Average = 1
|
92
|
+
Volatility_Ref_Price_BidOrAsk = 2
|
93
|
+
|
94
|
+
# No idea why IB uses a large number as the default for some fields
|
95
|
+
Max_Value = 99999999
|
96
|
+
|
97
|
+
# Main order fields
|
98
|
+
attr_accessor :id, # int m_orderId; ?
|
99
|
+
:client_id, # int
|
100
|
+
:perm_id, # int
|
101
|
+
:action, # String
|
102
|
+
:total_quantity, # int
|
103
|
+
:order_type, # String
|
104
|
+
:limit_price, # double
|
105
|
+
:aux_price, # double
|
106
|
+
#:shares_allocation, # deprecated sharesAllocation field
|
107
|
+
|
108
|
+
# Extended order fields
|
109
|
+
:tif, # String: Time in Force - DAY, GTC, etc.
|
110
|
+
:oca_group, # String: one cancels all group name
|
111
|
+
:oca_type, # 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK
|
112
|
+
:order_ref, # String
|
113
|
+
:transmit, # bool:if false, order will be created but not transmitted.
|
114
|
+
:parent_id, # int: Parent order id, to associate auto STP or TRAIL orders with the original order.
|
115
|
+
:block_order, # bool
|
116
|
+
:sweep_to_fill, # bool
|
117
|
+
:display_size, # int
|
118
|
+
:trigger_method, # 0=Default, 1=Double_Bid_Ask, 2=Last, 3=Double_Last,
|
119
|
+
# 4=Bid_Ask, 7=Last_or_Bid_Ask, 8=Mid-point
|
120
|
+
:outside_rth, # bool: WAS ignore_rth
|
121
|
+
:hidden, # bool
|
122
|
+
:good_after_time, # FORMAT: 20060505 08:00:00 {time zone}
|
123
|
+
:good_till_date, # FORMAT: 20060505 08:00:00 {time zone}
|
124
|
+
:override_percentage_constraints, # bool
|
125
|
+
:rule_80a, # Individual = 'I', Agency = 'A', AgentOtherMember = 'W',
|
126
|
+
# IndividualPTIA = 'J', AgencyPTIA = 'U', AgentOtherMemberPTIA = 'M',
|
127
|
+
# IndividualPT = 'K', AgencyPT = 'Y', AgentOtherMemberPT = 'N'
|
128
|
+
:all_or_none, # bool
|
129
|
+
:min_quantity, # int
|
130
|
+
:percent_offset, # double: REL orders only
|
131
|
+
:trail_stop_price, # double: for TRAILLIMIT orders only
|
132
|
+
|
133
|
+
# Financial advisors only, all Strings
|
134
|
+
:fa_group, :fa_profile, :fa_method, :fa_percentage,
|
135
|
+
|
136
|
+
# Institutional orders only
|
137
|
+
:open_close, # String: O=Open, C=Close
|
138
|
+
:origin, # int: 0=Customer, 1=Firm
|
139
|
+
:short_sale_slot, # 1 - you hold the shares, 2 - they will be delivered from elsewhere. Only for Action="SSHORT
|
140
|
+
:designated_location, # String: set when slot==2 only
|
141
|
+
:exempt_code, # int
|
142
|
+
|
143
|
+
# SMART routing only
|
144
|
+
:discretionary_amount, # double
|
145
|
+
:etrade_only, # bool
|
146
|
+
:firm_quote_only, # bool
|
147
|
+
:nbbo_price_cap, # double
|
148
|
+
|
149
|
+
# BOX or VOL ORDERS ONLY
|
150
|
+
:auction_strategy, # 1=AUCTION_MATCH, 2=AUCTION_IMPROVEMENT, 3=AUCTION_TRANSPARENT
|
151
|
+
:starting_price, # double, BOX ORDERS ONLY
|
152
|
+
:stock_ref_price, # double, BOX ORDERS ONLY
|
153
|
+
:delta, # double, BOX ORDERS ONLY
|
154
|
+
|
155
|
+
# Pegged to stock or VOL orders
|
156
|
+
:stock_range_lower, # double
|
157
|
+
:stock_range_upper, # double
|
158
|
+
|
159
|
+
# VOLATILITY ORDERS ONLY
|
160
|
+
:volatility, # double
|
161
|
+
:volatility_type, # int: 1=daily, 2=annual
|
162
|
+
:continuous_update, # int
|
163
|
+
:reference_price_type, # int: 1=Average, 2 = BidOrAsk
|
164
|
+
:delta_neutral_order_type, # String
|
165
|
+
:delta_neutral_aux_price, # double
|
166
|
+
|
167
|
+
# COMBO ORDERS ONLY
|
168
|
+
:basis_points, # double: EFP orders only
|
169
|
+
:basis_points_type, # double: EFP orders only
|
170
|
+
|
171
|
+
# SCALE ORDERS ONLY
|
172
|
+
:scale_init_level_size, # int
|
173
|
+
:scale_subs_level_size, # int
|
174
|
+
:scale_price_increment, # double
|
175
|
+
|
176
|
+
# Clearing info
|
177
|
+
:account, # String: IB account
|
178
|
+
:settling_firm, # String
|
179
|
+
:clearing_account, # String: True beneficiary of the order
|
180
|
+
:clearing_intent, # "" (Default), "IB", "Away", "PTA" (PostTrade)
|
181
|
+
|
182
|
+
# ALGO ORDERS ONLY
|
183
|
+
:algo_strategy, # String
|
184
|
+
:algo_params, # public Vector<TagValue> m_algoParams; ?!
|
185
|
+
|
186
|
+
# WTF?!
|
187
|
+
:what_if, #public boolean m_whatIf; // What-if
|
188
|
+
:not_held #public boolean m_notHeld; // Not Held
|
189
|
+
|
190
|
+
def init
|
191
|
+
super
|
192
|
+
|
193
|
+
@open_close = "0"
|
194
|
+
@origin = Origin_Customer
|
195
|
+
@transmit = true
|
196
|
+
@primary_exchange = ''
|
197
|
+
@designated_location = ''
|
198
|
+
@min_quantity = Max_Value # TODO: Initialize with nil instead of Max_Value, or change
|
199
|
+
# Order sending code in IB::Messages::Outgoing::PlaceOrder
|
200
|
+
@percent_offset = Max_Value # -"-
|
201
|
+
@nbba_price_cap = Max_Value # -"-
|
202
|
+
@starting_price = Max_Value # -"-
|
203
|
+
@stock_ref_price = Max_Value # -"-
|
204
|
+
@delta = Max_Value
|
205
|
+
@delta_neutral_order_type = ''
|
206
|
+
@delta_neutral_aux_price = Max_Value # -"-
|
207
|
+
@reference_price_type = Max_Value # -"-
|
208
|
+
end # init
|
209
|
+
|
210
|
+
end # class Order
|
211
|
+
|
212
|
+
|
213
|
+
class Contract < AbstractDatum
|
214
|
+
|
215
|
+
# Valid security types (sec_type attribute)
|
216
|
+
SECURITY_TYPES =
|
217
|
+
{
|
218
|
+
:stock => "STK",
|
219
|
+
:option => "OPT",
|
220
|
+
:future => "FUT",
|
221
|
+
:index => "IND",
|
222
|
+
:futures_option => "FOP",
|
223
|
+
:forex => "CASH",
|
224
|
+
:bag => "BAG"
|
225
|
+
}
|
226
|
+
|
227
|
+
# note that the :description field is entirely local to ib-ruby, and not part of TWS.
|
228
|
+
# You can use it to store whatever arbitrary data you want.
|
229
|
+
attr_accessor :symbol, :strike, :multiplier, :exchange, :currency,
|
230
|
+
:local_symbol, :combo_legs, :description
|
231
|
+
|
232
|
+
# Bond values
|
233
|
+
attr_accessor(:cusip, :ratings, :desc_append, :bond_type, :coupon_type, :callable,
|
234
|
+
:puttable, :coupon, :convertible, :maturity, :issue_date)
|
235
|
+
|
236
|
+
attr_reader :sec_type, :expiry, :right, :primary_exchange
|
237
|
+
|
238
|
+
|
239
|
+
# some protective filters
|
240
|
+
def primary_exchange=(x)
|
241
|
+
x.upcase! if x.is_a?(String)
|
242
|
+
|
243
|
+
# per http://chuckcaplan.com/twsapi/index.php/Class%20Contract
|
244
|
+
raise(ArgumentError.new("Don't set primary_exchange to smart")) if x == "SMART"
|
245
|
+
|
246
|
+
@primary_exchange = x
|
247
|
+
end
|
248
|
+
|
249
|
+
def right=(x)
|
250
|
+
x.upcase! if x.is_a?(String)
|
251
|
+
x = nil if !x.nil? && x.empty?
|
252
|
+
raise(ArgumentError.new("Invalid right \"#{x}\" (must be one of PUT, CALL, P, C)")) unless x.nil? || ["PUT", "CALL", "P", "C", "0"].include?(x)
|
253
|
+
@right = x
|
254
|
+
end
|
255
|
+
|
256
|
+
def expiry=(x)
|
257
|
+
x = x.to_s
|
258
|
+
if (x.nil? || !(x =~ /\d{6,8}/)) and !x.empty? then
|
259
|
+
raise ArgumentError.new("Invalid expiry \"#{x}\" (must be in format YYYYMM or YYYYMMDD)")
|
260
|
+
end
|
261
|
+
@expiry = x
|
262
|
+
end
|
263
|
+
|
264
|
+
def sec_type=(x)
|
265
|
+
x = nil if !x.nil? && x.empty?
|
266
|
+
raise(ArgumentError.new("Invalid security type \"#{x}\" (see SECURITY_TYPES constant in Contract class for valid types)")) unless x.nil? || SECURITY_TYPES.values.include?(x)
|
267
|
+
@sec_type = x
|
268
|
+
end
|
269
|
+
|
270
|
+
def reset
|
271
|
+
@combo_legs = Array.new
|
272
|
+
@strike = 0
|
273
|
+
end
|
274
|
+
|
275
|
+
# This returns an Array of data from the given contract, in standard format.
|
276
|
+
# Different messages serialize contracts differently. Go figure.
|
277
|
+
# Note that it does not include the combo legs.
|
278
|
+
def serialize(type = :long)
|
279
|
+
[self.symbol,
|
280
|
+
self.sec_type,
|
281
|
+
self.expiry,
|
282
|
+
self.strike,
|
283
|
+
self.right,
|
284
|
+
self.multiplier,
|
285
|
+
self.exchange] +
|
286
|
+
(type == :long ? [self.primary_exchange] : []) +
|
287
|
+
[self.currency,
|
288
|
+
self.local_symbol]
|
289
|
+
end
|
290
|
+
|
291
|
+
# @Legacy
|
292
|
+
def serialize_long(version)
|
293
|
+
serialize(:long)
|
294
|
+
end
|
295
|
+
|
296
|
+
# @Legacy
|
297
|
+
def serialize_short(version)
|
298
|
+
serialize(:short)
|
299
|
+
end
|
300
|
+
|
301
|
+
# This produces a string uniquely identifying this contract, in the format used
|
302
|
+
# for command line arguments in the IB-Ruby examples. The format is:
|
303
|
+
#
|
304
|
+
# symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
305
|
+
#
|
306
|
+
# Fields not needed for a particular security should be left blank
|
307
|
+
# (e.g. strike and right are only relevant for options.)
|
308
|
+
#
|
309
|
+
# For example, to query the British pound futures contract trading on Globex
|
310
|
+
# expiring in September, 2008, the string is:
|
311
|
+
#
|
312
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
313
|
+
def serialize_ib_ruby(version)
|
314
|
+
serialize.join(":")
|
315
|
+
end
|
316
|
+
|
317
|
+
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
318
|
+
def self.from_ib_ruby(string)
|
319
|
+
c = Contract.new
|
320
|
+
c.symbol, c.sec_type, c.expiry, c.strike, c.right, c.multiplier,
|
321
|
+
c.exchange, c.primary_exchange, c.currency, c.local_symbol = string.split(":")
|
322
|
+
c
|
323
|
+
end
|
324
|
+
|
325
|
+
def serialize_under_comp(*args)
|
326
|
+
raise "Unimplemented"
|
327
|
+
# EClientSocket.java, line 471:
|
328
|
+
#if (m_serverVersion >= MIN_SERVER_VER_UNDER_COMP) {
|
329
|
+
# if (contract.m_underComp != null) {
|
330
|
+
# UnderComp underComp = contract.m_underComp;
|
331
|
+
# send( true);
|
332
|
+
# send( underComp.m_conId);
|
333
|
+
# send( underComp.m_delta);
|
334
|
+
# send( underComp.m_price);
|
335
|
+
# }
|
336
|
+
end
|
337
|
+
|
338
|
+
def serialize_algo(*args)
|
339
|
+
raise "Unimplemented"
|
340
|
+
#if (m_serverVersion >= MIN_SERVER_VER_ALGO_ORDERS) {
|
341
|
+
# send( order.m_algoStrategy);
|
342
|
+
# if( !IsEmpty(order.m_algoStrategy)) {
|
343
|
+
# java.util.Vector algoParams = order.m_algoParams;
|
344
|
+
# int algoParamsCount = algoParams == null ? 0 : algoParams.size();
|
345
|
+
# send( algoParamsCount);
|
346
|
+
# if( algoParamsCount > 0) {
|
347
|
+
# for( int i = 0; i < algoParamsCount; ++i) {
|
348
|
+
# TagValue tagValue = (TagValue)algoParams.get(i);
|
349
|
+
# send( tagValue.m_tag);
|
350
|
+
# send( tagValue.m_value);
|
351
|
+
# }
|
352
|
+
# }
|
353
|
+
# }
|
354
|
+
#}
|
355
|
+
end
|
356
|
+
|
357
|
+
# Some messages send open_close too, some don't. WTF.
|
358
|
+
def serialize_combo_legs(include_open_close = false)
|
359
|
+
if self.combo_legs.nil?
|
360
|
+
[0]
|
361
|
+
else
|
362
|
+
[self.combo_legs.size].concat(self.combo_legs.serialize(include_open_close))
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def init
|
367
|
+
super
|
368
|
+
|
369
|
+
@combo_legs = Array.new
|
370
|
+
@strike = 0
|
371
|
+
@sec_type = ''
|
372
|
+
end
|
373
|
+
|
374
|
+
def to_human
|
375
|
+
"<IB-Contract: " + [symbol, expiry, sec_type, strike, right, exchange, currency].join("-") + "}>"
|
376
|
+
end
|
377
|
+
|
378
|
+
def to_short
|
379
|
+
"#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
|
380
|
+
end
|
381
|
+
|
382
|
+
def to_s
|
383
|
+
to_human
|
384
|
+
end
|
385
|
+
|
386
|
+
end # class Contract
|
387
|
+
|
388
|
+
class ContractDetails < AbstractDatum
|
389
|
+
attr_accessor :summary, :market_name, :trading_class, :con_id, :min_tick,
|
390
|
+
:multiplier, :price_magnifier, :order_types, :valid_exchanges
|
391
|
+
|
392
|
+
def init
|
393
|
+
super
|
394
|
+
|
395
|
+
@summary = Contract.new
|
396
|
+
@con_id = 0
|
397
|
+
@min_tick = 0
|
398
|
+
end
|
399
|
+
end # class ContractDetails
|
400
|
+
|
401
|
+
class Execution < AbstractDatum
|
402
|
+
attr_accessor :order_id, :client_id, :exec_id, :time, :account_number, :exchange,
|
403
|
+
:side, :shares, :price, :perm_id, :liquidation
|
404
|
+
|
405
|
+
def init
|
406
|
+
super
|
407
|
+
|
408
|
+
@order_id = 0
|
409
|
+
@client_id = 0
|
410
|
+
@shares = 0
|
411
|
+
@price = 0
|
412
|
+
@perm_id = 0
|
413
|
+
@liquidation =0
|
414
|
+
end
|
415
|
+
end # Execution
|
416
|
+
|
417
|
+
# From EClientSocket.java: Note that the valid format for m_time is "yyyymmdd-hh:mm:ss"
|
418
|
+
class ExecutionFilter < AbstractDatum
|
419
|
+
attr_accessor :client_id, :acct_code, :time, :symbol, :sec_type, :exchange, :side
|
420
|
+
|
421
|
+
def init
|
422
|
+
super
|
423
|
+
|
424
|
+
@client_id = 0
|
425
|
+
end
|
426
|
+
|
427
|
+
end # ExecutionFilter
|
428
|
+
|
429
|
+
|
430
|
+
class ComboLeg < AbstractDatum
|
431
|
+
attr_accessor :con_id, :ratio, :action, :exchange, :open_close
|
432
|
+
|
433
|
+
def init
|
434
|
+
super
|
435
|
+
|
436
|
+
@con_id = 0
|
437
|
+
@ratio = 0
|
438
|
+
@open_close = 0
|
439
|
+
end
|
440
|
+
|
441
|
+
# Some messages include open_close, some don't. wtf.
|
442
|
+
def serialize(include_open_close = false)
|
443
|
+
self.map { |leg|
|
444
|
+
[leg.con_id,
|
445
|
+
leg.ratio,
|
446
|
+
leg.action,
|
447
|
+
leg.exchange,
|
448
|
+
(include_open_close ? leg.open_close : [])]
|
449
|
+
}.flatten
|
450
|
+
end
|
451
|
+
end # ComboLeg
|
452
|
+
|
453
|
+
|
454
|
+
class ScannerSubscription < AbstractDatum
|
455
|
+
|
456
|
+
attr_accessor :number_of_rows, :instrument, :location_code, :scan_code, :above_price,
|
457
|
+
:below_price, :above_volume, :average_option_volume_above,
|
458
|
+
:market_cap_above, :market_cap_below, :moody_rating_above,
|
459
|
+
:moody_rating_below, :sp_rating_above, :sp_rating_below,
|
460
|
+
:maturity_date_above, :maturity_date_below, :coupon_rate_above,
|
461
|
+
:coupon_rate_below, :exclude_convertible, :scanner_setting_pairs,
|
462
|
+
:stock_type_filter
|
463
|
+
|
464
|
+
def init
|
465
|
+
super
|
466
|
+
|
467
|
+
@coupon_rate_above = @coupon_rate_below = @market_cap_below = @market_cap_above =
|
468
|
+
@average_option_volume_above = @above_volume = @below_price = @above_price = nil
|
469
|
+
@number_of_rows = -1 # none specified, per ScannerSubscription.java
|
470
|
+
end
|
471
|
+
end # ScannerSubscription
|
472
|
+
|
473
|
+
|
474
|
+
# Just like a Hash, but throws an exception if you try to access a key that doesn't exist.
|
475
|
+
class StringentHash < Hash
|
476
|
+
def initialize(hash)
|
477
|
+
super() { |hash, key| raise Exception.new("key #{key.inspect} not found!") }
|
478
|
+
self.merge!(hash) unless hash.nil?
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
end # module Datatypes
|
483
|
+
Models = Datatypes
|
484
|
+
|
485
|
+
end # module
|