ib-ruby 0.8.1 → 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/HISTORY +5 -0
- data/README.md +47 -53
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/app/assets/javascripts/ib/application.js +15 -0
- data/app/assets/javascripts/ib/underlyings.js +2 -0
- data/app/assets/stylesheets/ib/application.css +13 -0
- data/app/assets/stylesheets/ib/underlyings.css +4 -0
- data/app/assets/stylesheets/scaffold.css +56 -0
- data/app/controllers/ib/application_controller.rb +5 -0
- data/app/controllers/ib/underlyings_controller.rb +87 -0
- data/app/helpers/ib/application_helper.rb +4 -0
- data/app/helpers/ib/underlyings_helper.rb +4 -0
- data/app/models/ib/underlying.rb +5 -0
- data/app/views/ib/underlyings/_form.html.erb +33 -0
- data/app/views/ib/underlyings/edit.html.erb +6 -0
- data/app/views/ib/underlyings/index.html.erb +29 -0
- data/app/views/ib/underlyings/new.html.erb +5 -0
- data/app/views/ib/underlyings/show.html.erb +25 -0
- data/app/views/layouts/ib/application.html.erb +14 -0
- data/config/routes.rb +6 -0
- data/db/config.yml +19 -0
- data/db/migrate/{101_add_executions.rb → 101_add_ib_executions.rb} +2 -2
- data/db/migrate/{111_add_bars.rb → 111_add_ib_bars.rb} +2 -2
- data/db/migrate/{121_add_order_states.rb → 121_add_ib_order_states.rb} +2 -2
- data/db/migrate/{131_add_orders.rb → 131_add_ib_orders.rb} +2 -2
- data/db/migrate/{141_add_combo_legs.rb → 141_add_ib_combo_legs.rb} +2 -2
- data/db/migrate/{151_add_underlyings.rb → 151_add_ib_underlyings.rb} +2 -2
- data/db/migrate/{161_add_contract_details.rb → 161_add_ib_contract_details.rb} +2 -2
- data/db/migrate/{171_add_contracts.rb → 171_add_ib_contracts.rb} +2 -2
- data/db/schema.rb +245 -0
- data/lib/ib/base.rb +97 -0
- data/lib/ib/base_properties.rb +140 -0
- data/lib/{ib-ruby → ib}/connection.rb +2 -2
- data/lib/{ib-ruby → ib}/constants.rb +0 -0
- data/lib/{ib-ruby → ib}/db.rb +9 -5
- data/lib/ib/engine.rb +35 -0
- data/lib/{ib-ruby → ib}/errors.rb +0 -0
- data/lib/{ib-ruby → ib}/extensions.rb +2 -2
- data/lib/{ib-ruby → ib}/logger.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/abstract_message.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/abstract_message.rb +1 -1
- data/lib/{ib-ruby → ib}/messages/incoming/alert.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/contract_data.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/delta_neutral_validation.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/execution_data.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/historical_data.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/market_depths.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/next_valid_id.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/open_order.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/order_status.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/portfolio_value.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/real_time_bar.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/scanner_data.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming/ticks.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/incoming.rb +14 -14
- data/lib/{ib-ruby → ib}/messages/outgoing/abstract_message.rb +1 -1
- data/lib/{ib-ruby → ib}/messages/outgoing/bar_requests.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/outgoing/place_order.rb +0 -0
- data/lib/{ib-ruby → ib}/messages/outgoing.rb +5 -5
- data/lib/ib/messages.rb +8 -0
- data/lib/ib/model.rb +8 -0
- data/lib/ib/models.rb +10 -0
- data/lib/ib/requires.rb +9 -0
- data/lib/{ib-ruby → ib}/socket.rb +0 -0
- data/lib/{ib-ruby → ib}/symbols/forex.rb +1 -1
- data/lib/{ib-ruby → ib}/symbols/futures.rb +2 -2
- data/lib/{ib-ruby → ib}/symbols/options.rb +1 -1
- data/lib/{ib-ruby → ib}/symbols/stocks.rb +1 -1
- data/lib/ib/symbols.rb +9 -0
- data/lib/{ib-ruby → ib}/version.rb +0 -0
- data/lib/ib-ruby.rb +2 -24
- data/lib/ib.rb +23 -0
- data/lib/models/ib/bag.rb +51 -0
- data/lib/models/ib/bar.rb +41 -0
- data/lib/models/ib/combo_leg.rb +102 -0
- data/lib/models/ib/contract.rb +287 -0
- data/lib/models/ib/contract_detail.rb +68 -0
- data/lib/models/ib/execution.rb +62 -0
- data/lib/models/ib/option.rb +60 -0
- data/lib/models/ib/order.rb +389 -0
- data/lib/models/ib/order_state.rb +126 -0
- data/lib/models/ib/underlying.rb +35 -0
- data/spec/README.md +34 -2
- data/spec/TODO +5 -1
- data/spec/comb.rb +13 -0
- data/spec/db.rb +1 -1
- data/spec/db_helper.rb +3 -3
- data/spec/dummy.rb +13 -0
- data/spec/gw.rb +4 -0
- data/spec/{ib-ruby → ib}/connection_spec.rb +0 -0
- data/spec/{ib-ruby → ib}/messages/incoming/alert_spec.rb +0 -0
- data/spec/{ib-ruby → ib}/messages/incoming/open_order_spec.rb +0 -0
- data/spec/{ib-ruby → ib}/messages/incoming/order_status_spec.rb +16 -17
- data/spec/{ib-ruby → ib}/messages/outgoing/account_data_spec.rb +0 -0
- data/spec/{ib-ruby → ib}/messages/outgoing/market_data_type_spec.rb +0 -0
- data/spec/integration/historic_data_spec.rb +3 -3
- data/spec/integration/orders/trades_spec.rb +1 -1
- data/spec/{ib-ruby/models → models/ib}/bag_spec.rb +2 -7
- data/spec/{ib-ruby/models → models/ib}/bar_spec.rb +1 -6
- data/spec/{ib-ruby/models → models/ib}/combo_leg_spec.rb +2 -12
- data/spec/{ib-ruby/models → models/ib}/contract_detail_spec.rb +3 -8
- data/spec/{ib-ruby/models → models/ib}/contract_spec.rb +4 -12
- data/spec/{ib-ruby/models → models/ib}/execution_spec.rb +2 -7
- data/spec/{ib-ruby/models → models/ib}/option_spec.rb +1 -6
- data/spec/{ib-ruby/models → models/ib}/order_spec.rb +5 -10
- data/spec/{ib-ruby/models → models/ib}/order_state_spec.rb +2 -7
- data/spec/{ib-ruby/models → models/ib}/underlying_spec.rb +3 -7
- data/spec/my.rb +5 -0
- data/spec/spec_helper.rb +62 -36
- metadata +417 -544
- data/lib/ib-ruby/messages.rb +0 -8
- data/lib/ib-ruby/models/bag.rb +0 -54
- data/lib/ib-ruby/models/bar.rb +0 -43
- data/lib/ib-ruby/models/combo_leg.rb +0 -104
- data/lib/ib-ruby/models/contract.rb +0 -287
- data/lib/ib-ruby/models/contract_detail.rb +0 -70
- data/lib/ib-ruby/models/execution.rb +0 -64
- data/lib/ib-ruby/models/model.rb +0 -105
- data/lib/ib-ruby/models/model_properties.rb +0 -146
- data/lib/ib-ruby/models/option.rb +0 -62
- data/lib/ib-ruby/models/order.rb +0 -389
- data/lib/ib-ruby/models/order_state.rb +0 -128
- data/lib/ib-ruby/models/underlying.rb +0 -36
- data/lib/ib-ruby/models.rb +0 -15
- data/lib/ib-ruby/symbols.rb +0 -9
- data/spec/test.rb +0 -61
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'ib
|
1
|
+
require 'ib/messages/outgoing/abstract_message'
|
2
2
|
|
3
3
|
# TODO: Don't instantiate messages, use their classes as just namespace for .encode/decode
|
4
4
|
|
@@ -108,7 +108,7 @@ module IB
|
|
108
108
|
:side)
|
109
109
|
|
110
110
|
# data = { :id => ticker_id (int),
|
111
|
-
# :contract => Contract,
|
111
|
+
# :contract => IB::Contract,
|
112
112
|
# :exercise_action => int, 1 = exercise, 2 = lapse
|
113
113
|
# :exercise_quantity => int, The number of contracts to be exercised
|
114
114
|
# :account => string,
|
@@ -130,7 +130,7 @@ module IB
|
|
130
130
|
|
131
131
|
# @data={:id => int: ticker_id - Must be a unique value. When the market data
|
132
132
|
# returns, it will be identified by this tag,
|
133
|
-
# :contract =>
|
133
|
+
# :contract => IB::Contract, requested contract.
|
134
134
|
# :tick_list => String: comma delimited list of requested tick groups:
|
135
135
|
# Group ID - Description - Requested Tick Types
|
136
136
|
# 100 - Option Volume (currently for stocks) - 29, 30
|
@@ -268,8 +268,8 @@ module IB
|
|
268
268
|
:scanner_setting_pairs,
|
269
269
|
:stock_type_filter)
|
270
270
|
|
271
|
-
require 'ib
|
272
|
-
require 'ib
|
271
|
+
require 'ib/messages/outgoing/place_order'
|
272
|
+
require 'ib/messages/outgoing/bar_requests'
|
273
273
|
|
274
274
|
end # module Outgoing
|
275
275
|
end # module Messages
|
data/lib/ib/messages.rb
ADDED
data/lib/ib/model.rb
ADDED
data/lib/ib/models.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'ib/model'
|
2
|
+
|
3
|
+
require 'models/ib/contract_detail'
|
4
|
+
require 'models/ib/underlying'
|
5
|
+
require 'models/ib/contract'
|
6
|
+
require 'models/ib/order_state'
|
7
|
+
require 'models/ib/order'
|
8
|
+
require 'models/ib/combo_leg'
|
9
|
+
require 'models/ib/execution'
|
10
|
+
require 'models/ib/bar'
|
data/lib/ib/requires.rb
ADDED
File without changes
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Note that the :description field is particular to ib
|
1
|
+
# Note that the :description field is particular to ib, and is NOT part of the
|
2
2
|
# standard TWS API. It is never transmitted to IB. It's purely used clientside, and
|
3
3
|
# you can store any arbitrary string that you may find useful there.
|
4
4
|
module IB
|
@@ -4,7 +4,7 @@
|
|
4
4
|
# takes over as the volume leader.
|
5
5
|
#
|
6
6
|
#
|
7
|
-
# Note that the :description field is particular to ib
|
7
|
+
# Note that the :description field is particular to ib, and is NOT part of the standard TWS API.
|
8
8
|
# It is never transmitted to IB. It's purely used clientside, and you can store any arbitrary string that
|
9
9
|
# you may find useful there.
|
10
10
|
#
|
@@ -39,7 +39,7 @@ module IB
|
|
39
39
|
"#{ next_quarter_year(time) }#{ sprintf("%02d", next_quarter_month(time)) }"
|
40
40
|
end
|
41
41
|
|
42
|
-
# Convenience method; generates
|
42
|
+
# Convenience method; generates an IB::Contract instance for a futures
|
43
43
|
# contract with the given parameters.
|
44
44
|
#
|
45
45
|
# If expiry is nil, it will use the end month of the current
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Stock contracts definitions
|
2
2
|
#
|
3
|
-
# Note that the :description field is particular to ib
|
3
|
+
# Note that the :description field is particular to ib, and is NOT part of the
|
4
4
|
# standard TWS API. It is never transmitted to IB. It's purely used clientside, and
|
5
5
|
# you can store any arbitrary string that you may find useful there.
|
6
6
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# Stock contracts definitions
|
2
2
|
#
|
3
|
-
# Note that the :description field is particular to ib
|
3
|
+
# Note that the :description field is particular to ib, and is NOT part of the
|
4
4
|
# standard TWS API. It is never transmitted to IB. It's purely used clientside, and
|
5
5
|
# you can store any arbitrary string that you may find useful there.
|
6
6
|
|
data/lib/ib/symbols.rb
ADDED
File without changes
|
data/lib/ib-ruby.rb
CHANGED
@@ -1,24 +1,2 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
# By default there is no DB backend, unless specifically requested
|
4
|
-
# require 'ib-ruby/db' # to make all IB models database-backed
|
5
|
-
DB ||= false
|
6
|
-
|
7
|
-
require 'ib-ruby/version'
|
8
|
-
require 'ib-ruby/extensions'
|
9
|
-
require 'ib-ruby/errors'
|
10
|
-
require 'ib-ruby/constants'
|
11
|
-
require 'ib-ruby/connection'
|
12
|
-
|
13
|
-
require 'ib-ruby/models'
|
14
|
-
Datatypes = Models # Flatten namespace (IB::Contract instead of IB::Models::Contract)
|
15
|
-
include Models # Legacy alias
|
16
|
-
|
17
|
-
require 'ib-ruby/messages'
|
18
|
-
IncomingMessages = Messages::Incoming # Legacy alias
|
19
|
-
OutgoingMessages = Messages::Outgoing # Legacy alias
|
20
|
-
|
21
|
-
require 'ib-ruby/symbols'
|
22
|
-
end
|
23
|
-
IbRuby = IB
|
24
|
-
|
1
|
+
require 'ib'
|
2
|
+
|
data/lib/ib.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module IB
|
2
|
+
def self.db_backed?
|
3
|
+
!!defined?(IB::DB)
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.rails?
|
7
|
+
!!defined?(Rails) && Rails.respond_to?('env')
|
8
|
+
end
|
9
|
+
|
10
|
+
end # module IB
|
11
|
+
|
12
|
+
IbRuby = IB
|
13
|
+
Ib = IB
|
14
|
+
|
15
|
+
# IB Models can be either lightweight (tableless) or database-backed.
|
16
|
+
# By default there is no DB backend, unless specifically requested
|
17
|
+
# require 'ib/db' # to make all IB models database-backed
|
18
|
+
|
19
|
+
if IB.rails?
|
20
|
+
require 'ib/engine'
|
21
|
+
else
|
22
|
+
require 'ib/requires'
|
23
|
+
end
|
@@ -0,0 +1,51 @@
|
|
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 => /^bag$/, :message => "should be a bag"
|
15
|
+
validates_format_of :right, :with => /^none$/, :message => "should be none"
|
16
|
+
validates_format_of :expiry, :with => /^$/, :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
|
+
### Leg-related methods
|
31
|
+
|
32
|
+
# TODO: Rewrite with legs and legs_description being strictly in sync...
|
33
|
+
# TODO: Find a way to serialize legs without references...
|
34
|
+
# IB-equivalent leg description.
|
35
|
+
def legs_description
|
36
|
+
self[:legs_description] || legs.map { |leg| "#{leg.con_id}|#{leg.weight}" }.join(',')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Check if two Contracts have same legs (maybe in different order)
|
40
|
+
def same_legs? other
|
41
|
+
legs == other.legs ||
|
42
|
+
legs_description.split(',').sort == other.legs_description.split(',').sort
|
43
|
+
end
|
44
|
+
|
45
|
+
# Contract comparison
|
46
|
+
def == other
|
47
|
+
super && same_legs?(other)
|
48
|
+
end
|
49
|
+
|
50
|
+
end # class Bag
|
51
|
+
end # IB
|
@@ -0,0 +1,41 @@
|
|
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
|
+
prop :open, # The bar opening price.
|
8
|
+
:high, # The high price during the time covered by the bar.
|
9
|
+
:low, # The low price during the time covered by the bar.
|
10
|
+
:close, # The bar closing price.
|
11
|
+
:volume, # Volume
|
12
|
+
:wap, # Weighted average price during the time covered by the bar.
|
13
|
+
:trades, # int: When TRADES data history is returned, represents number
|
14
|
+
# of trades that occurred during the time period the bar covers
|
15
|
+
:time, # TODO: convert into Time object?
|
16
|
+
# The date-time stamp of the start of the bar. The format is
|
17
|
+
# determined by the reqHistoricalData() formatDate parameter.
|
18
|
+
:has_gaps => :bool # Whether or not there are gaps in the data.
|
19
|
+
|
20
|
+
validates_numericality_of :open, :high, :low, :close, :volume
|
21
|
+
|
22
|
+
# Order comparison
|
23
|
+
def == other
|
24
|
+
time == other.time &&
|
25
|
+
open == other.open &&
|
26
|
+
high == other.high &&
|
27
|
+
low == other.low &&
|
28
|
+
close == other.close &&
|
29
|
+
wap == other.wap &&
|
30
|
+
trades == other.trades &&
|
31
|
+
volume == other.volume
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_human
|
35
|
+
"<Bar: #{time} wap #{wap} OHLC #{open} #{high} #{low} #{close} " +
|
36
|
+
(trades ? "trades #{trades}" : "") + " vol #{volume} gaps #{has_gaps}>"
|
37
|
+
end
|
38
|
+
|
39
|
+
alias to_s to_human
|
40
|
+
end # class Bar
|
41
|
+
end # module IB
|
@@ -0,0 +1,102 @@
|
|
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
|
+
|
23
|
+
:exchange, # String: exchange to which the complete combo order will be routed.
|
24
|
+
# For institutional customers only! For stock legs when doing short sale
|
25
|
+
:short_sale_slot, # int: 0 - retail(default), 1 = clearing broker, 2 = third party
|
26
|
+
:designated_location, # String: Only for shortSaleSlot == 2.
|
27
|
+
# Otherwise leave blank or orders will be rejected.
|
28
|
+
:exempt_code, # int: ?
|
29
|
+
[:side, :action] => PROPS[:side], # String: Action/side: BUY/SELL/SSHORT/SSHORTX
|
30
|
+
:open_close => PROPS[:open_close]
|
31
|
+
# int: Whether the order is an open or close order. Values:
|
32
|
+
# SAME = 0 Same as the parent security. The only option for retail customers.
|
33
|
+
# OPEN = 1 Open. This value is only valid for institutional customers.
|
34
|
+
# CLOSE = 2 Close. This value is only valid for institutional customers.
|
35
|
+
# UNKNOWN = 3
|
36
|
+
|
37
|
+
# Extra validations
|
38
|
+
validates_numericality_of :ratio, :con_id
|
39
|
+
validates_format_of :designated_location, :with => /^$/,
|
40
|
+
:message => "should be blank or orders will be rejected"
|
41
|
+
|
42
|
+
def default_attributes
|
43
|
+
super.merge :con_id => 0,
|
44
|
+
:ratio => 1,
|
45
|
+
:side => :buy,
|
46
|
+
:open_close => :same, # The only option for retail customers.
|
47
|
+
:short_sale_slot => :default,
|
48
|
+
:designated_location => '',
|
49
|
+
:exchange => 'SMART', # Unless SMART, Order modification fails
|
50
|
+
:exempt_code => -1
|
51
|
+
end
|
52
|
+
|
53
|
+
# Leg's weight is a combination of action and ratio
|
54
|
+
def weight
|
55
|
+
side == :buy ? ratio : -ratio
|
56
|
+
end
|
57
|
+
|
58
|
+
def weight= value
|
59
|
+
value = value.to_i
|
60
|
+
if value > 0
|
61
|
+
self.side = :buy
|
62
|
+
self.ratio = value
|
63
|
+
else
|
64
|
+
self.side = :sell
|
65
|
+
self.ratio = -value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Some messages include open_close, some don't. wtf.
|
70
|
+
def serialize *fields
|
71
|
+
[con_id,
|
72
|
+
ratio,
|
73
|
+
side.to_sup,
|
74
|
+
exchange,
|
75
|
+
(fields.include?(:extended) ?
|
76
|
+
[self[:open_close],
|
77
|
+
self[:short_sale_slot],
|
78
|
+
designated_location,
|
79
|
+
exempt_code] :
|
80
|
+
[])
|
81
|
+
].flatten
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_human
|
85
|
+
"<ComboLeg: #{side} #{ratio} con_id #{con_id} at #{exchange}>"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Order comparison
|
89
|
+
def == other
|
90
|
+
other && other.is_a?(ComboLeg) &&
|
91
|
+
con_id == other.con_id &&
|
92
|
+
ratio == other.ratio &&
|
93
|
+
open_close == other.open_close &&
|
94
|
+
short_sale_slot == other.short_sale_slot&&
|
95
|
+
exempt_code == other.exempt_code &&
|
96
|
+
side == other.side &&
|
97
|
+
exchange == other.exchange &&
|
98
|
+
designated_location == other.designated_location
|
99
|
+
end
|
100
|
+
|
101
|
+
end # ComboLeg
|
102
|
+
end # module IB
|
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'models/ib/contract_detail'
|
2
|
+
require 'models/ib/underlying'
|
3
|
+
|
4
|
+
module IB
|
5
|
+
class Contract < IB::Model
|
6
|
+
include BaseProperties
|
7
|
+
|
8
|
+
# Fields are Strings unless noted otherwise
|
9
|
+
prop :con_id, # int: The unique contract identifier.
|
10
|
+
:currency, # Only needed if there is an ambiguity, e.g. when SMART exchange
|
11
|
+
# and IBM is being requested (IBM can trade in GBP or USD).
|
12
|
+
|
13
|
+
:legs_description, # received in OpenOrder for all combos
|
14
|
+
|
15
|
+
:sec_type, # Security type. Valid values are: SECURITY_TYPES
|
16
|
+
|
17
|
+
:sec_id, # Unique identifier of the given secIdType.
|
18
|
+
|
19
|
+
:sec_id_type => :sup, # Security identifier, when querying contract details or
|
20
|
+
# when placing orders. Supported identifiers are:
|
21
|
+
# - ISIN (Example: Apple: US0378331005)
|
22
|
+
# - CUSIP (Example: Apple: 037833100)
|
23
|
+
# - SEDOL (6-AN + check digit. Example: BAE: 0263494)
|
24
|
+
# - RIC (exchange-independent RIC Root and exchange-
|
25
|
+
# identifying suffix. Ex: AAPL.O for Apple on NASDAQ.)
|
26
|
+
|
27
|
+
:symbol => :s, # This is the symbol of the underlying asset.
|
28
|
+
|
29
|
+
:local_symbol => :s, # Local exchange symbol of the underlying asset
|
30
|
+
|
31
|
+
# Future/option contract multiplier (only needed when multiple possibilities exist)
|
32
|
+
:multiplier => {:set => :i},
|
33
|
+
|
34
|
+
:strike => :f, # double: The strike price.
|
35
|
+
:expiry => :s, # The expiration date. Use the format YYYYMM or YYYYMMDD
|
36
|
+
:exchange => :sup, # The order destination, such as Smart.
|
37
|
+
:primary_exchange => :sup, # Non-SMART exchange where the contract trades.
|
38
|
+
:include_expired => :bool, # When true, contract details requests and historical
|
39
|
+
# data queries can be performed pertaining to expired contracts.
|
40
|
+
# Note: Historical data queries on expired contracts are
|
41
|
+
# limited to the last year of the contracts life, and are
|
42
|
+
# only supported for expired futures contracts.
|
43
|
+
# This field can NOT be set to true for orders.
|
44
|
+
|
45
|
+
|
46
|
+
# Specifies a Put or Call. Valid input values are: P, PUT, C, CALL
|
47
|
+
:right => {
|
48
|
+
:set => proc { |val|
|
49
|
+
self[:right] =
|
50
|
+
case val.to_s.upcase
|
51
|
+
when 'NONE', '', '0', '?'
|
52
|
+
''
|
53
|
+
when 'PUT', 'P'
|
54
|
+
'P'
|
55
|
+
when 'CALL', 'C'
|
56
|
+
'C'
|
57
|
+
else
|
58
|
+
val
|
59
|
+
end
|
60
|
+
},
|
61
|
+
:validate => {:format => {:with => /^put$|^call$|^none$/,
|
62
|
+
:message => "should be put, call or none"}}
|
63
|
+
}
|
64
|
+
|
65
|
+
attr_accessor :description # NB: local to ib, not part of TWS.
|
66
|
+
|
67
|
+
### Associations
|
68
|
+
|
69
|
+
has_many :orders # Placed for this Contract
|
70
|
+
|
71
|
+
has_one :contract_detail # Volatile info about this Contract
|
72
|
+
|
73
|
+
# For Contracts that are part of BAG
|
74
|
+
has_one :leg, :class_name => 'ComboLeg', :foreign_key => :leg_contract_id
|
75
|
+
has_one :combo, :class_name => 'Contract', :through => :leg
|
76
|
+
|
77
|
+
# for Combo/BAG Contracts that contain ComboLegs
|
78
|
+
has_many :combo_legs, :foreign_key => :combo_id
|
79
|
+
has_many :leg_contracts, :class_name => 'Contract', :through => :combo_legs
|
80
|
+
alias legs combo_legs
|
81
|
+
alias legs= combo_legs=
|
82
|
+
|
83
|
+
alias combo_legs_description legs_description
|
84
|
+
alias combo_legs_description= legs_description=
|
85
|
+
|
86
|
+
# for Delta-Neutral Combo Contracts
|
87
|
+
has_one :underlying
|
88
|
+
alias under_comp underlying
|
89
|
+
alias under_comp= underlying=
|
90
|
+
|
91
|
+
|
92
|
+
### Extra validations
|
93
|
+
validates_inclusion_of :sec_type, :in => CODES[:sec_type].keys,
|
94
|
+
:message => "should be valid security type"
|
95
|
+
|
96
|
+
validates_format_of :expiry, :with => /^\d{6}$|^\d{8}$|^$/,
|
97
|
+
:message => "should be YYYYMM or YYYYMMDD"
|
98
|
+
|
99
|
+
validates_format_of :primary_exchange, :without => /SMART/,
|
100
|
+
:message => "should not be SMART"
|
101
|
+
|
102
|
+
validates_format_of :sec_id_type, :with => /ISIN|SEDOL|CUSIP|RIC|^$/,
|
103
|
+
:message => "should be valid security identifier"
|
104
|
+
|
105
|
+
validates_numericality_of :multiplier, :strike, :allow_nil => true
|
106
|
+
|
107
|
+
def default_attributes
|
108
|
+
super.merge :con_id => 0,
|
109
|
+
:strike => 0.0,
|
110
|
+
:right => :none, # Not an option
|
111
|
+
:exchange => 'SMART',
|
112
|
+
:include_expired => false
|
113
|
+
end
|
114
|
+
|
115
|
+
# This returns an Array of data from the given contract.
|
116
|
+
# Different messages serialize contracts differently. Go figure.
|
117
|
+
# Note that it does NOT include the combo legs.
|
118
|
+
# serialize [:option, :con_id, :include_expired, :sec_id]
|
119
|
+
def serialize *fields
|
120
|
+
[(fields.include?(:con_id) ? [con_id] : []),
|
121
|
+
symbol,
|
122
|
+
self[:sec_type],
|
123
|
+
(fields.include?(:option) ?
|
124
|
+
[expiry,
|
125
|
+
strike,
|
126
|
+
self[:right],
|
127
|
+
multiplier] : []),
|
128
|
+
exchange,
|
129
|
+
(fields.include?(:primary_exchange) ? [primary_exchange] : []),
|
130
|
+
currency,
|
131
|
+
local_symbol,
|
132
|
+
(fields.include?(:sec_id) ? [sec_id_type, sec_id] : []),
|
133
|
+
(fields.include?(:include_expired) ? [include_expired] : []),
|
134
|
+
].flatten
|
135
|
+
end
|
136
|
+
|
137
|
+
def serialize_long *fields
|
138
|
+
serialize :option, :primary_exchange, *fields
|
139
|
+
end
|
140
|
+
|
141
|
+
def serialize_short *fields
|
142
|
+
serialize :option, *fields
|
143
|
+
end
|
144
|
+
|
145
|
+
# Serialize under_comp parameters: EClientSocket.java, line 471
|
146
|
+
def serialize_under_comp *args
|
147
|
+
under_comp ? under_comp.serialize : [false]
|
148
|
+
end
|
149
|
+
|
150
|
+
# Defined in Contract, not BAG subclass to keep code DRY
|
151
|
+
def serialize_legs *fields
|
152
|
+
case
|
153
|
+
when !bag?
|
154
|
+
[]
|
155
|
+
when legs.empty?
|
156
|
+
[0]
|
157
|
+
else
|
158
|
+
[legs.size, legs.map { |leg| leg.serialize *fields }].flatten
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# This produces a string uniquely identifying this contract, in the format used
|
163
|
+
# for command line arguments in the IB-Ruby examples. The format is:
|
164
|
+
#
|
165
|
+
# symbol:sec_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
166
|
+
#
|
167
|
+
# Fields not needed for a particular security should be left blank
|
168
|
+
# (e.g. strike and right are only relevant for options.)
|
169
|
+
#
|
170
|
+
# For example, to query the British pound futures contract trading on Globex
|
171
|
+
# expiring in September, 2008, the string is:
|
172
|
+
#
|
173
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
174
|
+
def serialize_ib_ruby
|
175
|
+
serialize_long.join(":")
|
176
|
+
end
|
177
|
+
|
178
|
+
# Contract comparison
|
179
|
+
def == other
|
180
|
+
return false unless other.is_a?(self.class)
|
181
|
+
|
182
|
+
# Different sec_id_type
|
183
|
+
return false if sec_id_type && other.sec_id_type && sec_id_type != other.sec_id_type
|
184
|
+
|
185
|
+
# Different sec_id
|
186
|
+
return false if sec_id && other.sec_id && sec_id != other.sec_id
|
187
|
+
|
188
|
+
# Different symbols
|
189
|
+
return false if symbol && other.symbol && symbol != other.symbol
|
190
|
+
|
191
|
+
# Different currency
|
192
|
+
return false if currency && other.currency && currency != other.currency
|
193
|
+
|
194
|
+
# Same con_id for all Bags, but unknown for new Contracts...
|
195
|
+
# 0 or nil con_id matches any
|
196
|
+
return false if con_id != 0 && other.con_id != 0 &&
|
197
|
+
con_id && other.con_id && con_id != other.con_id
|
198
|
+
|
199
|
+
# SMART or nil exchange matches any
|
200
|
+
return false if exchange != 'SMART' && other.exchange != 'SMART' &&
|
201
|
+
exchange && other.exchange && exchange != other.exchange
|
202
|
+
|
203
|
+
# Comparison for Bonds and Options
|
204
|
+
if bond? || option?
|
205
|
+
return false if right != other.right || strike != other.strike
|
206
|
+
return false if multiplier && other.multiplier &&
|
207
|
+
multiplier != other.multiplier
|
208
|
+
return false if expiry && expiry[0..5] != other.expiry[0..5]
|
209
|
+
return false unless expiry && (expiry[6..7] == other.expiry[6..7] ||
|
210
|
+
expiry[6..7].empty? || other.expiry[6..7].empty?)
|
211
|
+
end
|
212
|
+
|
213
|
+
# All else being equal...
|
214
|
+
sec_type == other.sec_type
|
215
|
+
end
|
216
|
+
|
217
|
+
def to_s
|
218
|
+
"<Contract: " + instance_variables.map do |key|
|
219
|
+
value = send(key[1..-1])
|
220
|
+
" #{key}=#{value}" unless value.nil? || value == '' || value == 0
|
221
|
+
end.compact.join(',') + " >"
|
222
|
+
end
|
223
|
+
|
224
|
+
def to_human
|
225
|
+
"<Contract: " +
|
226
|
+
[symbol,
|
227
|
+
sec_type,
|
228
|
+
(expiry == '' ? nil : expiry),
|
229
|
+
(right == :none ? nil : right),
|
230
|
+
(strike == 0 ? nil : strike),
|
231
|
+
exchange,
|
232
|
+
currency
|
233
|
+
].compact.join(" ") + ">"
|
234
|
+
end
|
235
|
+
|
236
|
+
def to_short
|
237
|
+
"#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
|
238
|
+
end
|
239
|
+
|
240
|
+
# Testing for type of contract:
|
241
|
+
|
242
|
+
def bag?
|
243
|
+
self[:sec_type] == 'BAG'
|
244
|
+
end
|
245
|
+
|
246
|
+
def bond?
|
247
|
+
self[:sec_type] == 'BOND'
|
248
|
+
end
|
249
|
+
|
250
|
+
def stock?
|
251
|
+
self[:sec_type] == 'STK'
|
252
|
+
end
|
253
|
+
|
254
|
+
def option?
|
255
|
+
self[:sec_type] == 'OPT'
|
256
|
+
end
|
257
|
+
|
258
|
+
end # class Contract
|
259
|
+
|
260
|
+
|
261
|
+
### Now let's deal with Contract subclasses
|
262
|
+
|
263
|
+
require 'models/ib/option'
|
264
|
+
require 'models/ib/bag'
|
265
|
+
|
266
|
+
class Contract
|
267
|
+
# Specialized Contract subclasses representing different security types
|
268
|
+
Subclasses = Hash.new(Contract)
|
269
|
+
Subclasses[:bag] = IB::Bag
|
270
|
+
Subclasses[:option] = IB::Option
|
271
|
+
|
272
|
+
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
273
|
+
def self.build opts = {}
|
274
|
+
subclass = VALUES[:sec_type][opts[:sec_type]] || opts[:sec_type].to_sym
|
275
|
+
Contract::Subclasses[subclass].new opts
|
276
|
+
end
|
277
|
+
|
278
|
+
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
279
|
+
def self.from_ib_ruby string
|
280
|
+
keys = [:symbol, :sec_type, :expiry, :strike, :right, :multiplier,
|
281
|
+
:exchange, :primary_exchange, :currency, :local_symbol]
|
282
|
+
props = Hash[keys.zip(string.split(":"))]
|
283
|
+
props.delete_if { |k, v| v.nil? || v.empty? }
|
284
|
+
Contract.build props
|
285
|
+
end
|
286
|
+
end # class Contract
|
287
|
+
end # module IB
|