ib-api 972.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +105 -0
- data/Guardfile +24 -0
- data/LICENSE +674 -0
- data/README.md +65 -0
- data/Rakefile +11 -0
- data/VERSION +1 -0
- data/api.gemspec +43 -0
- data/bin/console +95 -0
- data/bin/console.yml +3 -0
- data/bin/setup +8 -0
- data/changelog.md +7 -0
- data/example/README.md +76 -0
- data/example/account_info +54 -0
- data/example/account_positions +30 -0
- data/example/account_summary +88 -0
- data/example/cancel_orders +74 -0
- data/example/fa_accounts +25 -0
- data/example/fundamental_data +40 -0
- data/example/historic_data_cli +186 -0
- data/example/list_orders +45 -0
- data/example/portfolio_csv +81 -0
- data/example/scanner_data +62 -0
- data/example/template +19 -0
- data/example/tick_data +28 -0
- data/lib/extensions/class-extensions.rb +87 -0
- data/lib/ib-api.rb +7 -0
- data/lib/ib/base.rb +103 -0
- data/lib/ib/base_properties.rb +160 -0
- data/lib/ib/connection.rb +450 -0
- data/lib/ib/constants.rb +393 -0
- data/lib/ib/errors.rb +44 -0
- data/lib/ib/logger.rb +26 -0
- data/lib/ib/messages.rb +99 -0
- data/lib/ib/messages/abstract_message.rb +101 -0
- data/lib/ib/messages/incoming.rb +251 -0
- data/lib/ib/messages/incoming/abstract_message.rb +116 -0
- data/lib/ib/messages/incoming/account_value.rb +78 -0
- data/lib/ib/messages/incoming/alert.rb +34 -0
- data/lib/ib/messages/incoming/contract_data.rb +102 -0
- data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
- data/lib/ib/messages/incoming/execution_data.rb +50 -0
- data/lib/ib/messages/incoming/historical_data.rb +84 -0
- data/lib/ib/messages/incoming/market_depths.rb +44 -0
- data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
- data/lib/ib/messages/incoming/open_order.rb +277 -0
- data/lib/ib/messages/incoming/order_status.rb +85 -0
- data/lib/ib/messages/incoming/portfolio_value.rb +78 -0
- data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
- data/lib/ib/messages/incoming/scanner_data.rb +54 -0
- data/lib/ib/messages/incoming/ticks.rb +268 -0
- data/lib/ib/messages/outgoing.rb +437 -0
- data/lib/ib/messages/outgoing/abstract_message.rb +88 -0
- data/lib/ib/messages/outgoing/account_requests.rb +112 -0
- data/lib/ib/messages/outgoing/bar_requests.rb +250 -0
- data/lib/ib/messages/outgoing/place_order.rb +209 -0
- data/lib/ib/messages/outgoing/request_marketdata.rb +99 -0
- data/lib/ib/messages/outgoing/request_tick_data.rb +21 -0
- data/lib/ib/model.rb +4 -0
- data/lib/ib/models.rb +14 -0
- data/lib/ib/server_versions.rb +114 -0
- data/lib/ib/socket.rb +185 -0
- data/lib/ib/support.rb +160 -0
- data/lib/ib/version.rb +6 -0
- data/lib/models/ib/account.rb +85 -0
- data/lib/models/ib/account_value.rb +33 -0
- data/lib/models/ib/bag.rb +55 -0
- data/lib/models/ib/bar.rb +31 -0
- data/lib/models/ib/combo_leg.rb +105 -0
- data/lib/models/ib/condition.rb +245 -0
- data/lib/models/ib/contract.rb +415 -0
- data/lib/models/ib/contract_detail.rb +108 -0
- data/lib/models/ib/execution.rb +67 -0
- data/lib/models/ib/forex.rb +13 -0
- data/lib/models/ib/future.rb +15 -0
- data/lib/models/ib/index.rb +15 -0
- data/lib/models/ib/option.rb +78 -0
- data/lib/models/ib/option_detail.rb +55 -0
- data/lib/models/ib/order.rb +519 -0
- data/lib/models/ib/order_state.rb +152 -0
- data/lib/models/ib/portfolio_value.rb +64 -0
- data/lib/models/ib/stock.rb +16 -0
- data/lib/models/ib/underlying.rb +34 -0
- data/lib/models/ib/vertical.rb +96 -0
- data/lib/requires.rb +12 -0
- metadata +203 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'models/ib/contract'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
|
5
|
+
# "BAG" is not really a contract, but a combination (combo) of securities.
|
6
|
+
# AKA basket or bag of securities. Individual securities in combo are represented
|
7
|
+
# by ComboLeg objects.
|
8
|
+
class Bag < Contract
|
9
|
+
# General Notes:
|
10
|
+
# 1. :exchange for the leg definition must match that of the combination order.
|
11
|
+
# The exception is for a STK legs, which must specify the SMART exchange.
|
12
|
+
# 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like "USD")
|
13
|
+
|
14
|
+
validates_format_of :sec_type, :with => /\Abag\z/, :message => "should be a bag"
|
15
|
+
validates_format_of :right, :with => /\Anone\z/, :message => "should be none"
|
16
|
+
validates_format_of :expiry, :with => /\A\z/, :message => "should be blank"
|
17
|
+
|
18
|
+
def default_attributes
|
19
|
+
super.merge :sec_type => :bag #,:legs => Array.new,
|
20
|
+
end
|
21
|
+
|
22
|
+
# def description
|
23
|
+
# self[:description] || to_human
|
24
|
+
# end
|
25
|
+
|
26
|
+
def to_human
|
27
|
+
"<Bag: #{[symbol, exchange, currency].join(' ')} legs: #{legs_description} >"
|
28
|
+
end
|
29
|
+
|
30
|
+
def con_id= arg
|
31
|
+
# dont' update con_id
|
32
|
+
end
|
33
|
+
|
34
|
+
### Leg-related methods
|
35
|
+
|
36
|
+
# TODO: Rewrite with legs and legs_description being strictly in sync...
|
37
|
+
# TODO: Find a way to serialize legs without references...
|
38
|
+
# IB-equivalent leg description.
|
39
|
+
def legs_description
|
40
|
+
self[:legs_description] || combo_legs.map { |the_leg| "#{the_leg.con_id}|#{the_leg.weight}" }.join(',')
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check if two Contracts have same legs (maybe in different order)
|
44
|
+
def same_legs? other
|
45
|
+
combo_legs == other.combo_legs ||
|
46
|
+
legs_description.split(',').sort == other.legs_description.split(',').sort
|
47
|
+
end
|
48
|
+
|
49
|
+
# Contract comparison
|
50
|
+
def == other
|
51
|
+
super && same_legs?(other)
|
52
|
+
end
|
53
|
+
|
54
|
+
end # class Bag
|
55
|
+
end # IB
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module IB
|
2
|
+
# This is a single data point delivered by HistoricData or RealTimeBar messages.
|
3
|
+
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
4
|
+
class Bar < IB::Model
|
5
|
+
include BaseProperties
|
6
|
+
|
7
|
+
has_one :contract # The bar represents timeseries info for this Contract
|
8
|
+
|
9
|
+
prop :open, # The bar opening price.
|
10
|
+
:high, # The high price during the time covered by the bar.
|
11
|
+
:low, # The low price during the time covered by the bar.
|
12
|
+
:close, # The bar closing price.
|
13
|
+
:volume, # Volume
|
14
|
+
:wap, # Weighted average price during the time covered by the bar.
|
15
|
+
:trades, # int: When TRADES data history is returned, represents number
|
16
|
+
# of trades that occurred during the time period the bar covers
|
17
|
+
:time #DateTime
|
18
|
+
# The date-time stamp of the start of the bar. The format is
|
19
|
+
# determined by the reqHistoricalData() formatDate parameter.
|
20
|
+
# :has_gaps => :bool # Whether or not there are gaps in the data. ## omitted since ServerVersion 124
|
21
|
+
|
22
|
+
validates_numericality_of :open, :high, :low, :close, :volume
|
23
|
+
|
24
|
+
def to_human
|
25
|
+
"<Bar: #{time} wap #{wap} OHLC #{open} #{high} #{low} #{close} " +
|
26
|
+
(trades ? "trades #{trades}" : "") + " vol #{volume}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
alias to_s to_human
|
30
|
+
end # class Bar
|
31
|
+
end # module IB
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module IB
|
2
|
+
|
3
|
+
# ComboLeg is essentially a join Model between Combo (BAG) Contract and
|
4
|
+
# individual Contracts (securities) that this BAG contains.
|
5
|
+
class ComboLeg < IB::Model
|
6
|
+
include BaseProperties
|
7
|
+
|
8
|
+
# BAG Combo Contract that contains this Leg
|
9
|
+
# belongs_to :combo, :class_name => 'Contract'
|
10
|
+
# Contract that constitutes this Leg
|
11
|
+
# belongs_to :leg_contract, :class_name => 'Contract', :foreign_key => :leg_contract_id
|
12
|
+
|
13
|
+
# General Notes:
|
14
|
+
# 1. The exchange for the leg definition must match that of the combination order.
|
15
|
+
# The exception is for a STK leg definition, which must specify the SMART exchange.
|
16
|
+
|
17
|
+
prop :con_id, # int: The unique contract identifier specifying the security.
|
18
|
+
:ratio, # int: Select the relative number of contracts for the leg you
|
19
|
+
# are constructing. To help determine the ratio for a
|
20
|
+
# specific combination order, refer to the Interactive
|
21
|
+
# Analytics section of the User's Guide.
|
22
|
+
:exchange, # String: exchange to which the complete combo order will be routed.
|
23
|
+
#
|
24
|
+
# For institutional customers only! For stock legs when doing short sale
|
25
|
+
:short_sale_slot, # int: 0 - retail(default),
|
26
|
+
# 1 = clearing broker, 2 = third party
|
27
|
+
:designated_location, # String: Only for shortSaleSlot == 2.
|
28
|
+
# Otherwise leave blank or orders will be rejected.
|
29
|
+
:exempt_code, # int: (-1)
|
30
|
+
[:side, :action] => PROPS[:side], # String: Action/side: BUY/SELL/SSHORT/SSHORTX
|
31
|
+
:open_close => PROPS[:open_close]
|
32
|
+
# int: Whether the order is an open or close order. Values:
|
33
|
+
# SAME = 0 Same as the parent security. The only option for retail customers.
|
34
|
+
# OPEN = 1 Open. This value is only valid for institutional customers.
|
35
|
+
# CLOSE = 2 Close. This value is only valid for institutional customers.
|
36
|
+
# UNKNOWN = 3
|
37
|
+
:price # support for pet leg prices
|
38
|
+
|
39
|
+
# Extra validations
|
40
|
+
validates_numericality_of :ratio, :con_id
|
41
|
+
validates_format_of :designated_location, :with => /\A\z/,
|
42
|
+
:message => "should be blank or orders will be rejected"
|
43
|
+
|
44
|
+
def default_attributes
|
45
|
+
super.merge :con_id => 0,
|
46
|
+
:ratio => 1,
|
47
|
+
:side => :buy,
|
48
|
+
:open_close => :same, # The only option for retail customers.
|
49
|
+
:short_sale_slot => :default,
|
50
|
+
:designated_location => '',
|
51
|
+
:exchange => 'SMART', # Unless SMART, Order modification fails
|
52
|
+
:exempt_code => -1
|
53
|
+
end
|
54
|
+
|
55
|
+
# Leg's weight is a combination of action and ratio
|
56
|
+
def weight
|
57
|
+
side == :buy ? ratio : -ratio
|
58
|
+
end
|
59
|
+
|
60
|
+
def weight= value
|
61
|
+
value = value.to_i
|
62
|
+
if value > 0
|
63
|
+
self.side = :buy
|
64
|
+
self.ratio = value
|
65
|
+
else
|
66
|
+
self.side = :sell
|
67
|
+
self.ratio = -value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Some messages include open_close, some don't. wtf.
|
72
|
+
def serialize *fields
|
73
|
+
[con_id,
|
74
|
+
ratio,
|
75
|
+
side.to_sup,
|
76
|
+
exchange,
|
77
|
+
(fields.include?(:extended) ?
|
78
|
+
[self[:open_close],
|
79
|
+
self[:short_sale_slot],
|
80
|
+
designated_location,
|
81
|
+
exempt_code] :
|
82
|
+
[])
|
83
|
+
].flatten
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_human
|
87
|
+
"<ComboLeg: #{side} #{ratio} con_id #{con_id} at #{exchange}>"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Order comparison
|
91
|
+
def == other
|
92
|
+
super(other) ||
|
93
|
+
other.is_a?(self.class) &&
|
94
|
+
con_id == other.con_id &&
|
95
|
+
ratio == other.ratio &&
|
96
|
+
open_close == other.open_close &&
|
97
|
+
short_sale_slot == other.short_sale_slot &&
|
98
|
+
exempt_code == other.exempt_code &&
|
99
|
+
side == other.side &&
|
100
|
+
exchange == other.exchange &&
|
101
|
+
designated_location == other.designated_location
|
102
|
+
end
|
103
|
+
|
104
|
+
end # ComboLeg
|
105
|
+
end # module IB
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'ib/support'
|
2
|
+
module IB
|
3
|
+
class OrderCondition < IB::Model
|
4
|
+
include BaseProperties
|
5
|
+
|
6
|
+
|
7
|
+
prop :operator, # 1 -> " >= " , 0 -> " <= " see /lib/ib/constants # 338f
|
8
|
+
:conjunction_connection, # "o" -> or "a"
|
9
|
+
:contract
|
10
|
+
def self.verify_contract_if_necessary c
|
11
|
+
c.con_id.to_i.zero? ||( c.primary_exchange.blank? && c.exchange.blank?) ? c.verify! : c
|
12
|
+
end
|
13
|
+
def condition_type
|
14
|
+
error "condition_type method is abstract"
|
15
|
+
end
|
16
|
+
def default_attributes
|
17
|
+
super.merge( operator: ">=" , conjunction_connection: :and )
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize_contract_by_con_id
|
21
|
+
[ contract.con_id , contract.primary_exchange.presence || contract.exchange ]
|
22
|
+
end
|
23
|
+
|
24
|
+
def serialize
|
25
|
+
[ condition_type, self[:conjunction_connection] ]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
class PriceCondition < OrderCondition
|
32
|
+
using IBSupport # refine Array-method for decoding of IB-Messages
|
33
|
+
prop :price,
|
34
|
+
:trigger_method # see /models/ib/order.rb# 51 ff and /lib/ib/constants # 210 ff
|
35
|
+
|
36
|
+
def default_attributes
|
37
|
+
super.merge( :trigger_method => :default )
|
38
|
+
end
|
39
|
+
|
40
|
+
def condition_type
|
41
|
+
1
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.make buffer
|
45
|
+
m= self.new conjunction_connection: buffer.read_string,
|
46
|
+
operator: buffer.read_int,
|
47
|
+
price: buffer.read_decimal
|
48
|
+
|
49
|
+
the_contract = IB::Contract.new con_id: buffer.read_int, exchange: buffer.read_string
|
50
|
+
m.contract = the_contract
|
51
|
+
m.trigger_method = buffer.read_int
|
52
|
+
m
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
def serialize
|
57
|
+
super << self[:operator] << price << serialize_contract_by_con_id << self[:trigger_method]
|
58
|
+
end
|
59
|
+
|
60
|
+
# dsl: PriceCondition.fabricate some_contract, ">=", 500
|
61
|
+
def self.fabricate contract, operator, price
|
62
|
+
error "Condition Operator has to be \">=\" or \"<=\" " unless ["<=", ">="].include? operator
|
63
|
+
self.new operator: operator,
|
64
|
+
price: price.to_i,
|
65
|
+
contract: verify_contract_if_necessary( contract )
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
class TimeCondition < OrderCondition
|
71
|
+
using IBSupport # refine Array-method for decoding of IB-Messages
|
72
|
+
prop :time
|
73
|
+
|
74
|
+
def condition_type
|
75
|
+
3
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.make buffer
|
79
|
+
self.new conjunction_connection: buffer.read_string,
|
80
|
+
operator: buffer.read_int,
|
81
|
+
time: buffer.read_parse_date
|
82
|
+
end
|
83
|
+
|
84
|
+
def serialize
|
85
|
+
t = self[:time]
|
86
|
+
if t.is_a?(String) && t =~ /^\d{8}\z/ # expiry-format yyymmmdd
|
87
|
+
self.time = DateTime.new t[0..3],t[4..5],t[-2..-1]
|
88
|
+
end
|
89
|
+
serialized_time = case self[:time] # explicity formatting of time-object
|
90
|
+
when String
|
91
|
+
self[:time]
|
92
|
+
when DateTime
|
93
|
+
self[:time].gmtime.strftime("%Y%m%d %H:%M:%S %Z")
|
94
|
+
when Date, Time
|
95
|
+
self[:time].strftime("%Y%m%d %H:%M:%S")
|
96
|
+
end
|
97
|
+
|
98
|
+
super << self[:operator] << serialized_time
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.fabricate operator, time
|
102
|
+
self.new operator: operator,
|
103
|
+
time: time
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class ExecutionCondition < OrderCondition
|
108
|
+
using IBSupport # refine Array-method for decoding of IB-Messages
|
109
|
+
|
110
|
+
def condition_type
|
111
|
+
5
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.make buffer
|
115
|
+
m =self.new conjunction_connection: buffer.read_string,
|
116
|
+
operator: buffer.read_int
|
117
|
+
|
118
|
+
the_contract = IB::Contract.new sec_type: buffer.read_string,
|
119
|
+
exchange: buffer.read_string,
|
120
|
+
symbol: buffer.read_string
|
121
|
+
m.contract = the_contract
|
122
|
+
m
|
123
|
+
end
|
124
|
+
|
125
|
+
def serialize
|
126
|
+
super << contract[:sec_type] <<(contract.primary_exchange.presence || contract.exchange) << contract.symbol
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.fabricate contract
|
130
|
+
self.new contract: verify_contract_if_necessary( contract )
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
class MarginCondition < OrderCondition
|
136
|
+
using IBSupport # refine Array-method for decoding of IB-Messages
|
137
|
+
|
138
|
+
prop :percent
|
139
|
+
|
140
|
+
def condition_type
|
141
|
+
4
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.make buffer
|
145
|
+
self.new conjunction_connection: buffer.read_string,
|
146
|
+
operator: buffer.read_int,
|
147
|
+
percent: buffer.read_int
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
def serialize
|
152
|
+
super << self[:operator] << percent
|
153
|
+
end
|
154
|
+
def self.fabricate operator, percent
|
155
|
+
error "Condition Operator has to be \">=\" or \"<=\" " unless ["<=", ">="].include? operator
|
156
|
+
self.new operator: operator,
|
157
|
+
percent: percent
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
class VolumeCondition < OrderCondition
|
163
|
+
using IBSupport # refine Array-method for decoding of IB-Messages
|
164
|
+
|
165
|
+
prop :volume
|
166
|
+
|
167
|
+
def condition_type
|
168
|
+
6
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.make buffer
|
172
|
+
m = self.new conjunction_connection: buffer.read_string,
|
173
|
+
operator: buffer.read_int,
|
174
|
+
volumne: buffer.read_int
|
175
|
+
|
176
|
+
the_contract = IB::Contract.new con_id: buffer.read_int, exchange: buffer.read_string
|
177
|
+
m.contract = the_contract
|
178
|
+
m
|
179
|
+
end
|
180
|
+
|
181
|
+
def serialize
|
182
|
+
|
183
|
+
super << self[:operator] << volume << serialize_contract_by.con_id
|
184
|
+
end
|
185
|
+
|
186
|
+
# dsl: VolumeCondition.fabricate some_contract, ">=", 50000
|
187
|
+
def self.fabricate contract, operator, volume
|
188
|
+
error "Condition Operator has to be \">=\" or \"<=\" " unless ["<=", ">="].include? operator
|
189
|
+
self.new operator: operator,
|
190
|
+
volume: volume,
|
191
|
+
contract: verify_contract_if_necessary( contract )
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
class PercentChangeCondition < OrderCondition
|
196
|
+
using IBSupport # refine Array-method for decoding of IB-Messages
|
197
|
+
prop :percent_change
|
198
|
+
|
199
|
+
def condition_type
|
200
|
+
7
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.make buffer
|
204
|
+
m = self.new conjunction_connection: buffer.read_string,
|
205
|
+
operator: buffer.read_int,
|
206
|
+
percent_change: buffer.read_decimal
|
207
|
+
|
208
|
+
the_contract = IB::Contract.new con_id: buffer.read_int, exchange: buffer.read_string
|
209
|
+
m.contract = the_contract
|
210
|
+
m
|
211
|
+
end
|
212
|
+
|
213
|
+
def serialize
|
214
|
+
super << self[:operator] << percent_change << serialize_contract_by_con_id
|
215
|
+
|
216
|
+
end
|
217
|
+
# dsl: PercentChangeCondition.fabricate some_contract, ">=", "5%"
|
218
|
+
def self.fabricate contract, operator, change
|
219
|
+
error "Condition Operator has to be \">=\" or \"<=\" " unless ["<=", ">="].include? operator
|
220
|
+
self.new operator: operator,
|
221
|
+
percent_change: change.to_i,
|
222
|
+
contract: verify_contract_if_necessary( contract )
|
223
|
+
end
|
224
|
+
end
|
225
|
+
class OrderCondition
|
226
|
+
using IBSupport # refine Array-method for decoding of IB-Messages
|
227
|
+
# subclasses representing specialized condition types.
|
228
|
+
|
229
|
+
Subclasses = Hash.new(OrderCondition)
|
230
|
+
Subclasses[1] = IB::PriceCondition
|
231
|
+
Subclasses[3] = IB::TimeCondition
|
232
|
+
Subclasses[5] = IB::ExecutionCondition
|
233
|
+
Subclasses[4] = IB::MarginCondition
|
234
|
+
Subclasses[6] = IB::VolumeCondition
|
235
|
+
Subclasses[7] = IB::PercentChangeCondition
|
236
|
+
|
237
|
+
|
238
|
+
# This builds an appropriate subclass based on its type
|
239
|
+
#
|
240
|
+
def self.make_from buffer
|
241
|
+
condition_type = buffer.read_int
|
242
|
+
OrderCondition::Subclasses[condition_type].make( buffer )
|
243
|
+
end
|
244
|
+
end # class
|
245
|
+
end # module
|
@@ -0,0 +1,415 @@
|
|
1
|
+
require 'models/ib/contract_detail'
|
2
|
+
require 'models/ib/underlying'
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
module IB
|
7
|
+
class Contract < IB::Model
|
8
|
+
include BaseProperties
|
9
|
+
|
10
|
+
# Fields are Strings unless noted otherwise
|
11
|
+
prop :con_id, # int: The unique contract identifier.
|
12
|
+
:currency, # Only needed if there is an ambiguity, e.g. when SMART exchange
|
13
|
+
# and IBM is being requested (IBM can trade in GBP or USD).
|
14
|
+
|
15
|
+
:legs_description, # received in OpenOrder for all combos
|
16
|
+
|
17
|
+
:sec_type, # Security type. Valid values are: SECURITY_TYPES
|
18
|
+
|
19
|
+
:sec_id => :sup, # Unique identifier of the given secIdType.
|
20
|
+
|
21
|
+
:sec_id_type => :sup, # Security identifier, when querying contract details or
|
22
|
+
# when placing orders. Supported identifiers are:
|
23
|
+
# - ISIN (Example: Apple: US0378331005)
|
24
|
+
# - CUSIP (Example: Apple: 037833100)
|
25
|
+
# - SEDOL (6-AN + check digit. Example: BAE: 0263494)
|
26
|
+
# - RIC (exchange-independent RIC Root and exchange-
|
27
|
+
# identifying suffix. Ex: AAPL.O for Apple on NASDAQ.)
|
28
|
+
|
29
|
+
:symbol => :s, # This is the symbol of the underlying asset.
|
30
|
+
|
31
|
+
:local_symbol => :s, # Local exchange symbol of the underlying asset
|
32
|
+
:trading_class => :s,
|
33
|
+
# Future/option contract multiplier (only needed when multiple possibilities exist)
|
34
|
+
:multiplier => {:set => :i},
|
35
|
+
|
36
|
+
:strike => :f, # double: The strike price.
|
37
|
+
:expiry => :s, # The expiration date. Use the format YYYYMM or YYYYMMDD
|
38
|
+
:last_trading_day => :s, # the tws returns the last trading day in Format YYYYMMMDD hh:mm
|
39
|
+
# which may differ from the expiry
|
40
|
+
:exchange => :sup, # The order destination, such as Smart.
|
41
|
+
:primary_exchange => :sup, # Non-SMART exchange where the contract trades.
|
42
|
+
:include_expired => :bool, # When true, contract details requests and historical
|
43
|
+
# data queries can be performed pertaining to expired contracts.
|
44
|
+
# Note: Historical data queries on expired contracts are
|
45
|
+
# limited to the last year of the contracts life, and are
|
46
|
+
# only supported for expired futures contracts.
|
47
|
+
# This field can NOT be set to true for orders.
|
48
|
+
|
49
|
+
|
50
|
+
# Specifies a Put or Call. Valid input values are: P, PUT, C, CALL
|
51
|
+
:right => {
|
52
|
+
:set => proc { |val|
|
53
|
+
self[:right] =
|
54
|
+
case val.to_s.upcase
|
55
|
+
when 'NONE', '', '0', '?'
|
56
|
+
''
|
57
|
+
when 'PUT', 'P'
|
58
|
+
'P'
|
59
|
+
when 'CALL', 'C'
|
60
|
+
'C'
|
61
|
+
else
|
62
|
+
val
|
63
|
+
end
|
64
|
+
},
|
65
|
+
:validate => {:format => {:with => /\Aput$|^call$|^none\z/,
|
66
|
+
:message => "should be put, call or none"}}
|
67
|
+
}
|
68
|
+
|
69
|
+
attr_accessor :description # NB: local to ib, not part of TWS.
|
70
|
+
|
71
|
+
### Associations
|
72
|
+
has_many :misc # multi purpose association
|
73
|
+
has_many :orders # Placed for this Contract
|
74
|
+
has_many :portfolio_values
|
75
|
+
|
76
|
+
has_many :bars # Possibly representing trading history for this Contract
|
77
|
+
|
78
|
+
has_one :contract_detail # Volatile info about this Contract
|
79
|
+
|
80
|
+
# For Contracts that are part of BAa ## leg is now a method of contract
|
81
|
+
# has_one :leg #, :class_name => 'ComboLeg', :foreign_key => :leg_contract_id
|
82
|
+
# has_one :combo, :class_name => 'Contract', :through => :leg
|
83
|
+
|
84
|
+
# for Combo/BAG Contracts that contain ComboLegs
|
85
|
+
has_many :combo_legs#, :foreign_key => :combo_id
|
86
|
+
# has_many :leg_contracts, :class_name => 'Contract', :through => :combo_legs
|
87
|
+
# alias legs combo_legs
|
88
|
+
# alias legs= combo_legs=
|
89
|
+
|
90
|
+
# alias combo_legs_description legs_description
|
91
|
+
# alias combo_legs_description= legs_description=
|
92
|
+
|
93
|
+
# for Delta-Neutral Combo Contracts
|
94
|
+
has_one :underlying
|
95
|
+
alias under_comp underlying
|
96
|
+
alias under_comp= underlying=
|
97
|
+
|
98
|
+
|
99
|
+
### Extra validations
|
100
|
+
validates_inclusion_of :sec_type, :in => CODES[:sec_type].keys,
|
101
|
+
:message => "should be valid security type"
|
102
|
+
|
103
|
+
validates_format_of :expiry, :with => /\A\d{6}$|^\d{8}$|\A\z/,
|
104
|
+
:message => "should be YYYYMM or YYYYMMDD"
|
105
|
+
|
106
|
+
validates_format_of :primary_exchange, :without => /SMART/,
|
107
|
+
:message => "should not be SMART"
|
108
|
+
|
109
|
+
validates_format_of :sec_id_type, :with => /ISIN|SEDOL|CUSIP|RIC|\A\z/,
|
110
|
+
:message => "should be valid security identifier"
|
111
|
+
|
112
|
+
validates_numericality_of :multiplier, :strike, :allow_nil => true
|
113
|
+
|
114
|
+
def default_attributes # :nodoc:
|
115
|
+
super.merge :con_id => 0,
|
116
|
+
:strike => 0.0,
|
117
|
+
:right => :none, # Not an option
|
118
|
+
# :exchange => 'SMART',
|
119
|
+
:include_expired => false
|
120
|
+
end
|
121
|
+
# This returns an Array of data from the given contract and is used to represent
|
122
|
+
# contracts in outgoing messages.
|
123
|
+
#
|
124
|
+
# Different messages serialize contracts differently. Go figure.
|
125
|
+
#
|
126
|
+
# Note that it does NOT include the combo legs.
|
127
|
+
# serialize :option, :con_id, :include_expired, :sec_id
|
128
|
+
#
|
129
|
+
# 18/1/18: serialise always includes conid
|
130
|
+
|
131
|
+
def serialize *fields # :nodoc:
|
132
|
+
print_default = ->(field, default="") { field.blank? ? default : field }
|
133
|
+
print_not_zero = ->(field, default="") { field.to_i.zero? ? default : field }
|
134
|
+
[(con_id.present? && !con_id.is_a?(Symbol) && con_id.to_i > 0 ? con_id : ""),
|
135
|
+
print_default[symbol],
|
136
|
+
print_default[self[:sec_type]],
|
137
|
+
( fields.include?(:option) ?
|
138
|
+
[ print_default[expiry],
|
139
|
+
print_not_zero[strike],
|
140
|
+
print_default[self[:right]],
|
141
|
+
print_default[multiplier]] : nil ),
|
142
|
+
print_default[exchange],
|
143
|
+
( fields.include?(:primary_exchange) ? print_default[primary_exchange] : nil ) ,
|
144
|
+
print_default[currency],
|
145
|
+
print_default[local_symbol],
|
146
|
+
( fields.include?(:trading_class) ? print_default[trading_class] : nil ),
|
147
|
+
( fields.include?(:include_expired) ? print_default[include_expired,0] : nil ),
|
148
|
+
( fields.include?(:sec_id_type) ? [print_default[sec_id_type], print_default[sec_id]] : nil )
|
149
|
+
].flatten.compact
|
150
|
+
end
|
151
|
+
|
152
|
+
# serialize contract
|
153
|
+
# con_id. sec_type, expiry, strike, right, multiplier exchange, primary_exchange, currency, local_symbol, include_expired
|
154
|
+
# other fields on demand
|
155
|
+
def serialize_long *fields # :nodoc:
|
156
|
+
serialize :option, :include_expired, :primary_exchange, :trading_class, *fields
|
157
|
+
end
|
158
|
+
|
159
|
+
# serialize contract
|
160
|
+
# con_id. sec_type, expiry, strike, right, multiplier, exchange, primary_exchange, currency, local_symbol
|
161
|
+
# other fields on demand
|
162
|
+
# acutal used by place_order, request_marketdata, request_market_depth, exercise_options
|
163
|
+
def serialize_short *fields # :nodoc:
|
164
|
+
serialize :option, :trading_class, :primary_exchange, *fields
|
165
|
+
end
|
166
|
+
|
167
|
+
# same as :serialize_short, omitting primary_exchange
|
168
|
+
# used by RequestMarketDepth
|
169
|
+
def serialize_supershort *fields # :nodoc:
|
170
|
+
serialize :option, :trading_class, *fields
|
171
|
+
end
|
172
|
+
|
173
|
+
# Serialize under_comp parameters: EClientSocket.java, line 471
|
174
|
+
def serialize_under_comp *args # :nodoc:
|
175
|
+
under_comp ? under_comp.serialize : [false]
|
176
|
+
end
|
177
|
+
|
178
|
+
# Defined in Contract, not BAG subclass to keep code DRY
|
179
|
+
def serialize_legs *fields # :nodoc:
|
180
|
+
case
|
181
|
+
when !bag?
|
182
|
+
[]
|
183
|
+
when combo_legs.empty?
|
184
|
+
[0]
|
185
|
+
else
|
186
|
+
[combo_legs.size, combo_legs.map { |the_leg| the_leg.serialize *fields }].flatten
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
# This produces a string uniquely identifying this contract, in the format used
|
193
|
+
# for command line arguments in the IB-Ruby examples. The format is:
|
194
|
+
#
|
195
|
+
# symbol:sec_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
196
|
+
#
|
197
|
+
# Fields not needed for a particular security should be left blank
|
198
|
+
# (e.g. strike and right are only relevant for options.)
|
199
|
+
#
|
200
|
+
# For example, to query the British pound futures contract trading on Globex
|
201
|
+
# expiring in September, 2008, the string is:
|
202
|
+
#
|
203
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
204
|
+
def serialize_ib_ruby
|
205
|
+
serialize_long.join(":")
|
206
|
+
end
|
207
|
+
|
208
|
+
# extracts essential attributes of the contract,
|
209
|
+
# and returns a new contract.
|
210
|
+
#
|
211
|
+
# the link to contract-details is __not__ maintained.
|
212
|
+
def essential
|
213
|
+
|
214
|
+
self_attributes = [ :right, :sec_type]
|
215
|
+
the_attributes = [ :symbol , :con_id, :exchange,
|
216
|
+
:currency, :expiry, :strike, :local_symbol, :last_trading_day,
|
217
|
+
:multiplier, :primary_exchange, :trading_class ]
|
218
|
+
the_hash= the_attributes.map{|x| y= attributes[x]; [x,y] if y.present? }.compact.to_h
|
219
|
+
the_hash[:description] = @description if @description.present?
|
220
|
+
self.class.new the_hash.merge( self_attributes.map{|x| y = self.send(x); [x,y] unless y == :none}.compact.to_h )
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# creates a new Contract substituting attributes by the provied key-value pairs.
|
225
|
+
#
|
226
|
+
# con_id is resetted
|
227
|
+
def merge **new_attributes
|
228
|
+
self.con_id = 0
|
229
|
+
self.class.new attributes.merge new_attributes
|
230
|
+
end
|
231
|
+
|
232
|
+
# Contract comparison
|
233
|
+
|
234
|
+
def == other # :nodoc:
|
235
|
+
return false if !other.is_a?(Contract)
|
236
|
+
return true if super(other)
|
237
|
+
return true if !con_id.to_i.zero? && con_id == other.con_id
|
238
|
+
|
239
|
+
return false unless other.is_a?(self.class)
|
240
|
+
|
241
|
+
# Different sec_id_type
|
242
|
+
return false if sec_id_type && other.sec_id_type && sec_id_type != other.sec_id_type
|
243
|
+
|
244
|
+
# Different sec_id
|
245
|
+
return false if sec_id && other.sec_id && sec_id != other.sec_id
|
246
|
+
|
247
|
+
# Different symbols
|
248
|
+
return false if symbol && other.symbol && symbol != other.symbol
|
249
|
+
|
250
|
+
# Different currency
|
251
|
+
return false if currency && other.currency && currency != other.currency
|
252
|
+
|
253
|
+
# Same con_id for all Bags, but unknown for new Contracts...
|
254
|
+
# 0 or nil con_id matches any
|
255
|
+
return false if con_id != 0 && other.con_id != 0 &&
|
256
|
+
con_id && other.con_id && con_id != other.con_id
|
257
|
+
|
258
|
+
# SMART or nil exchange matches any
|
259
|
+
return false if exchange != 'SMART' && other.exchange != 'SMART' &&
|
260
|
+
exchange && other.exchange && exchange != other.exchange
|
261
|
+
|
262
|
+
# Comparison for Bonds and Options
|
263
|
+
if bond? || option?
|
264
|
+
return false if right != other.right || strike != other.strike
|
265
|
+
return false if multiplier && other.multiplier &&
|
266
|
+
multiplier != other.multiplier
|
267
|
+
return false if expiry && expiry[0..5] != other.expiry[0..5]
|
268
|
+
return false unless expiry && (expiry[6..7] == other.expiry[6..7] ||
|
269
|
+
expiry[6..7].empty? || other.expiry[6..7].empty?)
|
270
|
+
end
|
271
|
+
|
272
|
+
# All else being equal...
|
273
|
+
sec_type == other.sec_type
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_s
|
277
|
+
"<Contract: " + instance_variables.map do |key|
|
278
|
+
value = send(key[1..-1])
|
279
|
+
" #{key}=#{value} (#{value.class}) " unless value.blank?
|
280
|
+
end.compact.join(',') + " >"
|
281
|
+
end
|
282
|
+
|
283
|
+
def to_human
|
284
|
+
"<Contract: " +
|
285
|
+
[symbol,
|
286
|
+
sec_type,
|
287
|
+
(expiry == '' ? nil : expiry),
|
288
|
+
(right == :none ? nil : right),
|
289
|
+
(strike == 0 ? nil : strike),
|
290
|
+
exchange,
|
291
|
+
currency
|
292
|
+
].compact.join(" ") + ">"
|
293
|
+
end
|
294
|
+
|
295
|
+
def to_short
|
296
|
+
if expiry.blank? && last_trading_day.blank?
|
297
|
+
"#{symbol}# {exchange}# {currency}"
|
298
|
+
elsif expiry.present?
|
299
|
+
"#{symbol}(#{strike}) #{right} #{expiry} /#{exchange}/#{currency}"
|
300
|
+
else
|
301
|
+
"#{symbol}(#{strike}) #{right} #{last_trading_day} /#{exchange}/#{currency}"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
# Testing for type of contract:
|
305
|
+
# depreciated : use is_a?(IB::Stock, IB::Bond, IB::Bag etc) instead
|
306
|
+
def bag? # :nodoc:
|
307
|
+
self[:sec_type] == 'BAG'
|
308
|
+
end
|
309
|
+
|
310
|
+
def bond? # :nodoc:
|
311
|
+
|
312
|
+
self[:sec_type] == 'BOND'
|
313
|
+
end
|
314
|
+
|
315
|
+
def stock? # :nodoc:
|
316
|
+
|
317
|
+
self[:sec_type] == 'STK'
|
318
|
+
end
|
319
|
+
|
320
|
+
def option? # :nodoc:
|
321
|
+
|
322
|
+
self[:sec_type] == 'OPT'
|
323
|
+
end
|
324
|
+
|
325
|
+
def index? # :nodoc:
|
326
|
+
|
327
|
+
self[:sec_type] == 'IND'
|
328
|
+
end
|
329
|
+
|
330
|
+
=begin
|
331
|
+
From the release notes of TWS 9.50
|
332
|
+
|
333
|
+
Within TWS and Mosaic, we use the last trading day and not the actual expiration date for futures, options and futures options contracts. To be more accurate, all fields and selectors throughout TWS that were labeled Expiry or Expiration have been changed to Last Trading Day. Note that the last trading day and the expiration date may be the same or different dates.
|
334
|
+
|
335
|
+
In many places, such as the OptionTrader, Probability Lab and other options/futures tools, this is a simple case of changing the name of a field to Last Trading Day. In other cases the change is wider-reaching. For example, basket files that include derivatives were previously saved using the Expiry header. When you try to import these legacy .csv files, you will now receive a message requiring that you change this column title to LastTradingDayorContractMonth before the import will be accepted. New basket files that include derivatives will use this correct header. Additionally, this new field serves two functions. If you use the format YYYYMMDD, we understand you are identifying the last trading day for a contract. If you use the format YYYYMM, we understand you are identifying the contract month.
|
336
|
+
|
337
|
+
In places where these terms are used to indicate a concept, we have left them as Expiry or Expiration. For example in the Option Chain settings where we allow you to "Load the nearest N expiries" we have left the word expiries. Additionally, the Contract Description window will show both the Last Trading Date and the Expiration Date. Also in cases where it's appropriate, we have replaced Expiry or Expiration with Contract Month.
|
338
|
+
|
339
|
+
=end
|
340
|
+
|
341
|
+
|
342
|
+
# IB-ruby uses expiry to query Contracts.
|
343
|
+
#
|
344
|
+
# The response from the TWS is stored in 'last_trading_day' (Contract) and 'real_expiration_data' (ContractDetails)
|
345
|
+
#
|
346
|
+
# However, after querying a contract, 'expiry' ist overwritten by 'last_trading_day'. The original 'expiry'
|
347
|
+
# is still available through 'attributes[:expiry]'
|
348
|
+
|
349
|
+
def expiry
|
350
|
+
if self.last_trading_day.present?
|
351
|
+
last_trading_day.gsub(/-/,'')
|
352
|
+
else
|
353
|
+
@attributes[:expiry]
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
|
358
|
+
# is read by Account#PlaceOrder to set requirements for contract-types, as NonGuaranteed for stock-spreads
|
359
|
+
def order_requirements
|
360
|
+
Hash.new
|
361
|
+
end
|
362
|
+
|
363
|
+
end # class Contract
|
364
|
+
|
365
|
+
|
366
|
+
### Now let's deal with Contract subclasses
|
367
|
+
|
368
|
+
require 'models/ib/option'
|
369
|
+
require 'models/ib/bag'
|
370
|
+
require 'models/ib/forex'
|
371
|
+
require 'models/ib/future'
|
372
|
+
require 'models/ib/stock'
|
373
|
+
require 'models/ib/index'
|
374
|
+
|
375
|
+
class Contract
|
376
|
+
# Contract subclasses representing specialized security types.
|
377
|
+
|
378
|
+
Subclasses = Hash.new(Contract)
|
379
|
+
Subclasses[:bag] = IB::Bag
|
380
|
+
Subclasses[:option] = IB::Option
|
381
|
+
Subclasses[:future] = IB::Future
|
382
|
+
Subclasses[:stock] = IB::Stock
|
383
|
+
Subclasses[:forex] = IB::Forex
|
384
|
+
Subclasses[:index] = IB::Index
|
385
|
+
|
386
|
+
|
387
|
+
# This builds an appropriate Contract subclass based on its type
|
388
|
+
#
|
389
|
+
# the method is also used to copy Contract.values to new instances
|
390
|
+
def self.build opts = {}
|
391
|
+
subclass =( VALUES[:sec_type][opts[:sec_type]] || opts['sec_type'] || opts[:sec_type]).to_sym
|
392
|
+
Contract::Subclasses[subclass].new opts
|
393
|
+
end
|
394
|
+
|
395
|
+
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
396
|
+
def self.from_ib_ruby
|
397
|
+
keys = [:con_id, :symbol, :sec_type, :expiry, :strike, :right, :multiplier,
|
398
|
+
:exchange, :primary_exchange, :currency, :local_symbol]
|
399
|
+
props = Hash[keys.zip(string.split(":"))]
|
400
|
+
props.delete_if { |k, v| v.nil? || v.empty? }
|
401
|
+
Contract.build props
|
402
|
+
end
|
403
|
+
end # class Contract
|
404
|
+
end # module IB
|
405
|
+
|
406
|
+
class String
|
407
|
+
def to_contract
|
408
|
+
keys = [:con_id, :symbol, :sec_type, :expiry, :strike, :right, :multiplier,
|
409
|
+
:exchange, :primary_exchange, :currency, :local_symbol]
|
410
|
+
props = Hash[keys.zip(split(":"))]
|
411
|
+
props.delete_if { |k, v| v.nil? || v.empty? }
|
412
|
+
IB::Contract.build props
|
413
|
+
|
414
|
+
end
|
415
|
+
end
|