ib-ruby 0.7.4 → 0.7.6
Sign up to get free protection for your applications and to get access to all the features.
- 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 ||
|