ib-ruby 0.7.4 → 0.7.6
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 +3 -0
- data/HISTORY +8 -0
- data/README.md +2 -2
- data/Rakefile +15 -0
- data/TODO +7 -2
- data/VERSION +1 -1
- data/bin/account_info +1 -1
- data/bin/cancel_orders +1 -1
- data/bin/contract_details +1 -1
- data/bin/depth_of_market +1 -1
- data/bin/fa_accounts +1 -1
- data/bin/fundamental_data +42 -0
- data/bin/historic_data +1 -1
- data/bin/historic_data_cli +1 -1
- data/bin/list_orders +1 -2
- data/bin/market_data +1 -1
- data/bin/option_data +1 -1
- data/bin/place_combo_order +1 -1
- data/bin/place_order +1 -1
- data/bin/template +1 -4
- data/bin/tick_data +2 -2
- data/bin/time_and_sales +1 -1
- data/lib/ib-ruby.rb +4 -0
- data/lib/ib-ruby/connection.rb +50 -34
- data/lib/ib-ruby/constants.rb +232 -37
- data/lib/ib-ruby/db.rb +25 -0
- data/lib/ib-ruby/extensions.rb +51 -1
- data/lib/ib-ruby/messages/abstract_message.rb +0 -8
- data/lib/ib-ruby/messages/incoming.rb +18 -493
- data/lib/ib-ruby/messages/incoming/abstract_message.rb +100 -0
- data/lib/ib-ruby/messages/incoming/alert.rb +34 -0
- data/lib/ib-ruby/messages/incoming/contract_data.rb +82 -0
- data/lib/ib-ruby/messages/incoming/delta_neutral_validation.rb +20 -0
- data/lib/ib-ruby/messages/incoming/execution_data.rb +59 -0
- data/lib/ib-ruby/messages/incoming/historical_data.rb +55 -0
- data/lib/ib-ruby/messages/incoming/market_depths.rb +44 -0
- data/lib/ib-ruby/messages/incoming/open_order.rb +32 -16
- data/lib/ib-ruby/messages/incoming/order_status.rb +67 -0
- data/lib/ib-ruby/messages/incoming/portfolio_value.rb +39 -0
- data/lib/ib-ruby/messages/incoming/real_time_bar.rb +32 -0
- data/lib/ib-ruby/messages/incoming/scanner_data.rb +49 -0
- data/lib/ib-ruby/messages/outgoing.rb +25 -223
- data/lib/ib-ruby/messages/outgoing/abstract_message.rb +61 -0
- data/lib/ib-ruby/messages/outgoing/bar_requests.rb +149 -0
- data/lib/ib-ruby/messages/outgoing/place_order.rb +24 -0
- data/lib/ib-ruby/models.rb +4 -0
- data/lib/ib-ruby/models/bar.rb +31 -14
- data/lib/ib-ruby/models/combo_leg.rb +48 -23
- data/lib/ib-ruby/models/contracts.rb +2 -2
- data/lib/ib-ruby/models/contracts/bag.rb +11 -7
- data/lib/ib-ruby/models/contracts/contract.rb +90 -66
- data/lib/ib-ruby/models/contracts/option.rb +16 -7
- data/lib/ib-ruby/models/execution.rb +34 -18
- data/lib/ib-ruby/models/model.rb +15 -7
- data/lib/ib-ruby/models/model_properties.rb +101 -44
- data/lib/ib-ruby/models/order.rb +176 -187
- data/lib/ib-ruby/models/order_state.rb +99 -0
- data/lib/ib-ruby/symbols/forex.rb +10 -10
- data/lib/ib-ruby/symbols/futures.rb +6 -6
- data/lib/ib-ruby/symbols/stocks.rb +3 -3
- data/spec/account_helper.rb +4 -5
- data/spec/combo_helper.rb +4 -4
- data/spec/db.rb +18 -0
- data/spec/ib-ruby/messages/{incoming_spec.rb → incoming/alert_spec.rb} +1 -0
- data/spec/ib-ruby/messages/incoming/open_order_spec.rb +100 -0
- data/spec/ib-ruby/messages/incoming/order_status_spec.rb +74 -0
- data/spec/ib-ruby/messages/{outgoing_spec.rb → outgoing/account_data_spec.rb} +0 -0
- data/spec/ib-ruby/messages/outgoing/market_data_type_spec.rb +44 -0
- data/spec/ib-ruby/models/bag_spec.rb +97 -0
- data/spec/ib-ruby/models/bar_spec.rb +45 -0
- data/spec/ib-ruby/models/combo_leg_spec.rb +56 -40
- data/spec/ib-ruby/models/contract_spec.rb +134 -170
- data/spec/ib-ruby/models/execution_spec.rb +35 -50
- data/spec/ib-ruby/models/option_spec.rb +127 -0
- data/spec/ib-ruby/models/order_spec.rb +89 -68
- data/spec/ib-ruby/models/order_state_spec.rb +55 -0
- data/spec/integration/contract_info_spec.rb +4 -6
- data/spec/integration/fundamental_data_spec.rb +41 -0
- data/spec/integration/historic_data_spec.rb +4 -4
- data/spec/integration/market_data_spec.rb +1 -3
- data/spec/integration/orders/attached_spec.rb +8 -10
- data/spec/integration/orders/combo_spec.rb +2 -2
- data/spec/integration/orders/execution_spec.rb +0 -1
- data/spec/integration/orders/placement_spec.rb +1 -3
- data/spec/integration/orders/valid_ids_spec.rb +1 -2
- data/spec/message_helper.rb +1 -1
- data/spec/model_helper.rb +211 -0
- data/spec/order_helper.rb +44 -37
- data/spec/spec_helper.rb +36 -23
- data/spec/v.rb +7 -0
- data/tasks/doc.rake +1 -1
- metadata +116 -12
- data/spec/integration/orders/open_order +0 -98
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'ib-ruby/messages/abstract_message'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
module Messages
|
5
|
+
module Outgoing
|
6
|
+
|
7
|
+
# Container for specific message classes, keyed by their message_ids
|
8
|
+
Classes = {}
|
9
|
+
|
10
|
+
class AbstractMessage < IB::Messages::AbstractMessage
|
11
|
+
|
12
|
+
def initialize data={}
|
13
|
+
@data = data
|
14
|
+
@created_at = Time.now
|
15
|
+
end
|
16
|
+
|
17
|
+
# This causes the message to send itself over the server socket in server[:socket].
|
18
|
+
# "server" is the @server instance variable from the IB object.
|
19
|
+
# You can also use this to e.g. get the server version number.
|
20
|
+
#
|
21
|
+
# Subclasses can either override this method for precise control over how
|
22
|
+
# stuff gets sent to the server, or else define a method encode() that returns
|
23
|
+
# an Array of elements that ought to be sent to the server by calling to_s on
|
24
|
+
# each one and postpending a '\0'.
|
25
|
+
#
|
26
|
+
def send_to server
|
27
|
+
self.encode(server).flatten.each do |datum|
|
28
|
+
#p datum
|
29
|
+
server[:socket].write_data datum
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# At minimum, Outgoing message contains message_id and version.
|
34
|
+
# Most messages also contain (ticker, request or order) :id.
|
35
|
+
# Then, content of @data Hash is encoded per instructions in data_map.
|
36
|
+
def encode server
|
37
|
+
[self.class.message_id,
|
38
|
+
self.class.version,
|
39
|
+
@data[:id] || @data[:ticker_id] || @data[:request_id]|| @data[:order_id] || [],
|
40
|
+
self.class.data_map.map do |(field, default_method, args)|
|
41
|
+
case
|
42
|
+
when default_method.nil?
|
43
|
+
@data[field]
|
44
|
+
|
45
|
+
when default_method.is_a?(Symbol) # method name with args
|
46
|
+
@data[field].send default_method, *args
|
47
|
+
|
48
|
+
when default_method.respond_to?(:call) # callable with args
|
49
|
+
default_method.call @data[field], *args
|
50
|
+
|
51
|
+
else # default
|
52
|
+
@data[field].nil? ? default_method : @data[field] # may be false still
|
53
|
+
end
|
54
|
+
end
|
55
|
+
].flatten
|
56
|
+
end
|
57
|
+
|
58
|
+
end # AbstractMessage
|
59
|
+
end # module Outgoing
|
60
|
+
end # module Messages
|
61
|
+
end # module IB
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module IB
|
2
|
+
module Messages
|
3
|
+
module Outgoing
|
4
|
+
|
5
|
+
# Messages that request bar data have special processing of @data
|
6
|
+
|
7
|
+
class BarRequestMessage < AbstractMessage
|
8
|
+
# Preprocessor for some data fields
|
9
|
+
def parse data
|
10
|
+
type = data[:data_type] || data[:what_to_show]
|
11
|
+
data_type = DATA_TYPES.invert[type] || type
|
12
|
+
unless DATA_TYPES.keys.include?(data_type)
|
13
|
+
error ":data_type must be one of #{DATA_TYPES.inspect}", :args
|
14
|
+
end
|
15
|
+
|
16
|
+
size = data[:bar_size] || data[:size]
|
17
|
+
bar_size = BAR_SIZES.invert[size] || size
|
18
|
+
unless BAR_SIZES.keys.include?(bar_size)
|
19
|
+
error ":bar_size must be one of #{BAR_SIZES.inspect}", :args
|
20
|
+
end
|
21
|
+
|
22
|
+
contract = data[:contract].is_a?(IB::Contract) ?
|
23
|
+
data[:contract] : IB::Contract.from_ib_ruby(data[:contract])
|
24
|
+
|
25
|
+
[data_type, bar_size, contract]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# data = { :id => ticker_id (int),
|
30
|
+
# :contract => Contract ,
|
31
|
+
# :bar_size => int/Symbol? Currently only 5 second bars are supported,
|
32
|
+
# if any other value is used, an exception will be thrown.,
|
33
|
+
# :data_type => Symbol: Determines the nature of data being extracted.
|
34
|
+
# :trades, :midpoint, :bid, :ask, :bid_ask,
|
35
|
+
# :historical_volatility, :option_implied_volatility,
|
36
|
+
# :option_volume, :option_open_interest
|
37
|
+
# - converts to "TRADES," "MIDPOINT," "BID," etc...
|
38
|
+
# :use_rth => int: 0 - all data available during the time span requested
|
39
|
+
# is returned, even data bars covering time intervals where the
|
40
|
+
# market in question was illiquid. 1 - only data within the
|
41
|
+
# "Regular Trading Hours" of the product in question is returned,
|
42
|
+
# even if the time span requested falls partially or completely
|
43
|
+
# outside of them.
|
44
|
+
RequestRealTimeBars = def_message 50, BarRequestMessage
|
45
|
+
|
46
|
+
class RequestRealTimeBars
|
47
|
+
def encode server
|
48
|
+
data_type, bar_size, contract = parse @data
|
49
|
+
|
50
|
+
[super,
|
51
|
+
contract.serialize_long,
|
52
|
+
bar_size,
|
53
|
+
data_type.to_s.upcase,
|
54
|
+
@data[:use_rth]].flatten
|
55
|
+
end
|
56
|
+
end # RequestRealTimeBars
|
57
|
+
|
58
|
+
# data = { :id => int: Ticker id, needs to be different than the reqMktData ticker
|
59
|
+
# id. If you use the same ticker ID you used for the symbol when
|
60
|
+
# you did ReqMktData, nothing comes back for the historical data call
|
61
|
+
# :contract => Contract: requested ticker description
|
62
|
+
# :end_date_time => String: "yyyymmdd HH:mm:ss", with optional time zone
|
63
|
+
# allowed after a space: "20050701 18:26:44 GMT"
|
64
|
+
# :duration => String, time span the request will cover, and is specified
|
65
|
+
# using the format: <integer> <unit>, eg: '1 D', valid units are:
|
66
|
+
# '1 S' (seconds, default if no unit is specified)
|
67
|
+
# '1 D' (days)
|
68
|
+
# '1 W' (weeks)
|
69
|
+
# '1 M' (months)
|
70
|
+
# '1 Y' (years, currently limited to one)
|
71
|
+
# :bar_size => String: Specifies the size of the bars that will be returned
|
72
|
+
# (within IB/TWS limits). Valid values include:
|
73
|
+
# '1 sec'
|
74
|
+
# '5 secs'
|
75
|
+
# '15 secs'
|
76
|
+
# '30 secs'
|
77
|
+
# '1 min'
|
78
|
+
# '2 mins'
|
79
|
+
# '3 mins'
|
80
|
+
# '5 mins'
|
81
|
+
# '15 mins'
|
82
|
+
# '30 min'
|
83
|
+
# '1 hour'
|
84
|
+
# '1 day'
|
85
|
+
# :what_to_show => Symbol: Determines the nature of data being extracted.
|
86
|
+
# Valid values:
|
87
|
+
# :trades, :midpoint, :bid, :ask, :bid_ask,
|
88
|
+
# :historical_volatility, :option_implied_volatility,
|
89
|
+
# :option_volume, :option_open_interest
|
90
|
+
# - converts to "TRADES," "MIDPOINT," "BID," etc...
|
91
|
+
# :use_rth => int: 0 - all data available during the time span requested
|
92
|
+
# is returned, even data bars covering time intervals where the
|
93
|
+
# market in question was illiquid. 1 - only data within the
|
94
|
+
# "Regular Trading Hours" of the product in question is returned,
|
95
|
+
# even if the time span requested falls partially or completely
|
96
|
+
# outside of them.
|
97
|
+
# :format_date => int: 1 - text format, like "20050307 11:32:16".
|
98
|
+
# 2 - offset in seconds from the beginning of 1970,
|
99
|
+
# which is the same format as the UNIX epoch time.
|
100
|
+
# }
|
101
|
+
#
|
102
|
+
# Note that as of 4/07 there is no historical data available for forex spot.
|
103
|
+
#
|
104
|
+
# data[:contract] may either be a Contract object or a String. A String should be
|
105
|
+
# in serialize_ib_ruby format; that is, it should be a colon-delimited string in
|
106
|
+
# the format (e.g. for Globex British pound futures contract expiring in Sep-2008):
|
107
|
+
#
|
108
|
+
# symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
109
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
110
|
+
#
|
111
|
+
# Fields not needed for a particular security should be left blank (e.g. strike
|
112
|
+
# and right are only relevant for options.)
|
113
|
+
#
|
114
|
+
# A Contract object will be automatically serialized into the required format.
|
115
|
+
#
|
116
|
+
# See also http://chuckcaplan.com/twsapi/index.php/void%20reqIntradayData%28%29
|
117
|
+
# for general information about how TWS handles historic data requests, whence
|
118
|
+
# the following has been adapted:
|
119
|
+
#
|
120
|
+
# The server providing historical prices appears to not always be
|
121
|
+
# available outside of market hours. If you call it outside of its
|
122
|
+
# supported time period, or if there is otherwise a problem with
|
123
|
+
# it, you will receive error #162 "Historical Market Data Service
|
124
|
+
# query failed.:HMDS query returned no data."
|
125
|
+
#
|
126
|
+
# For backfill on futures data, you may need to leave the Primary
|
127
|
+
# Exchange field of the Contract structure blank; see
|
128
|
+
# http://www.interactivebrokers.com/discus/messages/2/28477.html?1114646754
|
129
|
+
RequestHistoricalData = def_message [20, 4], BarRequestMessage
|
130
|
+
|
131
|
+
class RequestHistoricalData
|
132
|
+
def encode server
|
133
|
+
data_type, bar_size, contract = parse @data
|
134
|
+
|
135
|
+
[super,
|
136
|
+
contract.serialize_long(:include_expired),
|
137
|
+
@data[:end_date_time],
|
138
|
+
bar_size,
|
139
|
+
@data[:duration],
|
140
|
+
@data[:use_rth],
|
141
|
+
data_type.to_s.upcase,
|
142
|
+
@data[:format_date],
|
143
|
+
contract.serialize_legs].flatten
|
144
|
+
end
|
145
|
+
end # RequestHistoricalData
|
146
|
+
|
147
|
+
end # module Outgoing
|
148
|
+
end # module Messages
|
149
|
+
end # module IB
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module IB
|
2
|
+
module Messages
|
3
|
+
module Outgoing
|
4
|
+
|
5
|
+
# Data format is { :id => int: order_id,
|
6
|
+
# :contract => Contract,
|
7
|
+
# :order => Order }
|
8
|
+
PlaceOrder = def_message [3, 31] # v.38 is NOT properly supported by API yet
|
9
|
+
|
10
|
+
class PlaceOrder
|
11
|
+
|
12
|
+
def encode server
|
13
|
+
# Old server version supports no enhancements
|
14
|
+
@version = 31 if server[:server_version] <= 60
|
15
|
+
|
16
|
+
[super,
|
17
|
+
@data[:order].serialize_with(server, @data[:contract])].flatten
|
18
|
+
end
|
19
|
+
end # PlaceOrder
|
20
|
+
|
21
|
+
|
22
|
+
end # module Outgoing
|
23
|
+
end # module Messages
|
24
|
+
end # module IB
|
data/lib/ib-ruby/models.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
module IB
|
2
2
|
module Models
|
3
|
+
|
4
|
+
require 'ib-ruby/models/model_properties'
|
5
|
+
require 'ib-ruby/models/model'
|
3
6
|
require 'ib-ruby/models/contracts'
|
4
7
|
# Flatten namespace (IB::Models::Option instead of IB::Models::Contracts::Option)
|
5
8
|
include Contracts
|
6
9
|
|
10
|
+
require 'ib-ruby/models/order_state'
|
7
11
|
require 'ib-ruby/models/order'
|
8
12
|
require 'ib-ruby/models/combo_leg'
|
9
13
|
require 'ib-ruby/models/execution'
|
data/lib/ib-ruby/models/bar.rb
CHANGED
@@ -1,26 +1,43 @@
|
|
1
|
-
require 'ib-ruby/models/model'
|
2
|
-
|
3
1
|
module IB
|
4
2
|
module Models
|
5
|
-
# This is a single data point delivered by HistoricData messages.
|
3
|
+
# This is a single data point delivered by HistoricData or RealTimeBar messages.
|
6
4
|
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
7
|
-
class Bar < Model
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
class Bar < Model.for(:bar)
|
6
|
+
include ModelProperties
|
7
|
+
|
8
|
+
prop :open, # The bar opening price.
|
11
9
|
:high, # The high price during the time covered by the bar.
|
12
10
|
:low, # The low price during the time covered by the bar.
|
13
11
|
:close, # The bar closing price.
|
14
|
-
:volume, #
|
12
|
+
:volume, # Volume
|
15
13
|
:wap, # Weighted average price during the time covered by the bar.
|
16
|
-
:
|
17
|
-
|
18
|
-
|
14
|
+
:trades, # int: When TRADES data history is returned, represents number
|
15
|
+
# of trades that occurred during the time period the bar covers
|
16
|
+
:time, # TODO: convert into Time object?
|
17
|
+
# The date-time stamp of the start of the bar. The format is
|
18
|
+
# determined by the reqHistoricalData() formatDate parameter.
|
19
|
+
:has_gaps => :bool # Whether or not there are gaps in the data.
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
validates_numericality_of :open, :high, :low, :close, :volume
|
22
|
+
|
23
|
+
# Order comparison
|
24
|
+
def == other
|
25
|
+
time == other.time &&
|
26
|
+
open == other.open &&
|
27
|
+
high == other.high &&
|
28
|
+
low == other.low &&
|
29
|
+
close == other.close &&
|
30
|
+
wap == other.wap &&
|
31
|
+
trades == other.trades &&
|
32
|
+
volume == other.volume
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_human
|
36
|
+
"<Bar: #{time} wap #{wap} OHLC #{open} #{high} #{low} #{close} " +
|
37
|
+
(trades ? "trades #{trades}" : "") + " vol #{volume} gaps #{has_gaps}>"
|
23
38
|
end
|
39
|
+
|
40
|
+
alias to_s to_human
|
24
41
|
end # class Bar
|
25
42
|
end # module Models
|
26
43
|
end # module IB
|
@@ -4,53 +4,57 @@ module IB
|
|
4
4
|
# ComboLeg objects represent individual securities in a "BAG" contract - which
|
5
5
|
# is not really a contract, but a combination (combo) of securities. AKA basket
|
6
6
|
# or bag of securities.
|
7
|
-
class ComboLeg < Model
|
7
|
+
class ComboLeg < Model.for(:combo_leg)
|
8
|
+
include ModelProperties
|
9
|
+
|
8
10
|
# General Notes:
|
9
11
|
# 1. The exchange for the leg definition must match that of the combination order.
|
10
12
|
# The exception is for a STK leg definition, which must specify the SMART exchange.
|
11
13
|
|
12
|
-
# // open/close leg value is same as combo
|
13
|
-
# Specifies whether the order is an open or close order. Valid values are:
|
14
|
-
SAME = 0 # Same as the parent security. The only option for retail customers.
|
15
|
-
OPEN = 1 # Open. This value is only valid for institutional customers.
|
16
|
-
CLOSE = 2 # Close. This value is only valid for institutional customers.
|
17
|
-
UNKNOWN = 3
|
18
|
-
|
19
14
|
prop :con_id, # int: The unique contract identifier specifying the security.
|
20
15
|
:ratio, # int: Select the relative number of contracts for the leg you
|
21
16
|
# are constructing. To help determine the ratio for a
|
22
17
|
# specific combination order, refer to the Interactive
|
23
18
|
# Analytics section of the User's Guide.
|
24
19
|
|
25
|
-
:action, # String: BUY/SELL/SSHORT/SSHORTX The side (buy or sell) for the leg.
|
26
20
|
:exchange, # String: exchange to which the complete combo order will be routed.
|
27
|
-
:open_close, # int: Specifies whether the order is an open or close order.
|
28
|
-
# Valid values: ComboLeg::SAME/OPEN/CLOSE/UNKNOWN
|
29
|
-
|
30
21
|
# For institutional customers only! For stock legs when doing short sale
|
31
|
-
:short_sale_slot, # int: 0 - retail, 1 = clearing broker, 2 = third party
|
22
|
+
:short_sale_slot, # int: 0 - retail(default), 1 = clearing broker, 2 = third party
|
32
23
|
:designated_location, # String: Only for shortSaleSlot == 2.
|
33
24
|
# Otherwise leave blank or orders will be rejected.
|
34
|
-
:exempt_code # int: ?
|
25
|
+
:exempt_code, # int: ?
|
26
|
+
[:side, :action] => PROPS[:side], # String: Action/side: BUY/SELL/SSHORT/SSHORTX
|
27
|
+
:open_close => PROPS[:open_close]
|
28
|
+
# int: Whether the order is an open or close order. Values:
|
29
|
+
# SAME = 0 Same as the parent security. The only option for retail customers.
|
30
|
+
# OPEN = 1 Open. This value is only valid for institutional customers.
|
31
|
+
# CLOSE = 2 Close. This value is only valid for institutional customers.
|
32
|
+
# UNKNOWN = 3
|
33
|
+
|
34
|
+
# Extra validations
|
35
|
+
validates_numericality_of :ratio, :con_id
|
36
|
+
validates_format_of :designated_location, :with => /^$/,
|
37
|
+
:message => "should be blank or orders will be rejected"
|
35
38
|
|
36
39
|
DEFAULT_PROPS = {:con_id => 0,
|
37
|
-
:open_close =>
|
38
|
-
:short_sale_slot =>
|
40
|
+
:open_close => :same, # The only option for retail customers.
|
41
|
+
:short_sale_slot => :default,
|
39
42
|
:designated_location => '',
|
43
|
+
:exchange => 'SMART', # Unless SMART, Order modification fails
|
40
44
|
:exempt_code => -1, }
|
41
45
|
|
42
46
|
# Leg's weight is a combination of action and ratio
|
43
47
|
def weight
|
44
|
-
|
48
|
+
side == :buy ? ratio : -ratio
|
45
49
|
end
|
46
50
|
|
47
51
|
def weight= value
|
48
52
|
value = value.to_i
|
49
53
|
if value > 0
|
50
|
-
self.
|
54
|
+
self.side = :buy
|
51
55
|
self.ratio = value
|
52
56
|
else
|
53
|
-
self.
|
57
|
+
self.side = :sell
|
54
58
|
self.ratio = -value
|
55
59
|
end
|
56
60
|
end
|
@@ -59,13 +63,34 @@ module IB
|
|
59
63
|
def serialize *fields
|
60
64
|
[con_id,
|
61
65
|
ratio,
|
62
|
-
|
66
|
+
side.to_sup,
|
63
67
|
exchange,
|
64
|
-
(fields.include?(:extended) ?
|
65
|
-
|
68
|
+
(fields.include?(:extended) ?
|
69
|
+
[self[:open_close],
|
70
|
+
self[:short_sale_slot],
|
71
|
+
designated_location,
|
72
|
+
exempt_code] :
|
73
|
+
[])
|
66
74
|
].flatten
|
67
75
|
end
|
68
|
-
end # ComboLeg
|
69
76
|
|
77
|
+
def to_human
|
78
|
+
"<ComboLeg: #{side} #{ratio} con_id #{con_id} at #{exchange}>"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Order comparison
|
82
|
+
def == other
|
83
|
+
other && other.is_a?(ComboLeg) &&
|
84
|
+
con_id == other.con_id &&
|
85
|
+
ratio == other.ratio &&
|
86
|
+
open_close == other.open_close &&
|
87
|
+
short_sale_slot == other.short_sale_slot&&
|
88
|
+
exempt_code == other.exempt_code &&
|
89
|
+
side == other.side &&
|
90
|
+
exchange == other.exchange &&
|
91
|
+
designated_location == other.designated_location
|
92
|
+
end
|
93
|
+
|
94
|
+
end # ComboLeg
|
70
95
|
end # module Models
|
71
96
|
end # module IB
|
@@ -15,8 +15,8 @@ module IB
|
|
15
15
|
module Contracts
|
16
16
|
# Specialized Contract subclasses representing different security types
|
17
17
|
TYPES = Hash.new(Contract)
|
18
|
-
TYPES[
|
19
|
-
TYPES[
|
18
|
+
TYPES[:bag] = Bag
|
19
|
+
TYPES[:option] = Option
|
20
20
|
|
21
21
|
# Returns concrete subclass for this sec_type, or default Contract
|
22
22
|
def [] sec_type
|
@@ -11,11 +11,20 @@ module IB
|
|
11
11
|
# General Notes:
|
12
12
|
# 1. :exchange for the leg definition must match that of the combination order.
|
13
13
|
# The exception is for a STK legs, which must specify the SMART exchange.
|
14
|
-
# 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like
|
14
|
+
# 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like "USD")
|
15
|
+
|
16
|
+
validates_format_of :sec_type, :with => /^bag$/, :message => "should be a bag"
|
17
|
+
validates_format_of :right, :with => /^none$/, :message => "should be none"
|
18
|
+
validates_format_of :expiry, :with => /^$/, :message => "should be blank"
|
19
|
+
validate :legs_cannot_be_empty
|
20
|
+
|
21
|
+
def legs_cannot_be_empty
|
22
|
+
errors.add(:legs, "legs cannot be empty") if legs.empty?
|
23
|
+
end
|
15
24
|
|
16
25
|
def initialize opts = {}
|
17
26
|
@legs = Array.new
|
18
|
-
self
|
27
|
+
self.sec_type = :bag
|
19
28
|
super opts
|
20
29
|
end
|
21
30
|
|
@@ -36,11 +45,6 @@ module IB
|
|
36
45
|
self[:legs_description] || legs.map { |leg| "#{leg.con_id}|#{leg.weight}" }.join(',')
|
37
46
|
end
|
38
47
|
|
39
|
-
def serialize_legs *fields
|
40
|
-
return [0] if legs.empty?
|
41
|
-
[legs.size, legs.map { |leg| leg.serialize *fields }]
|
42
|
-
end
|
43
|
-
|
44
48
|
# Check if two Contracts have same legs (maybe in different order)
|
45
49
|
def same_legs? other
|
46
50
|
legs == other.legs ||
|