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
@@ -1,13 +1,12 @@
|
|
1
|
-
require 'ib-ruby/models/model'
|
2
|
-
|
3
1
|
module IB
|
4
2
|
module Models
|
5
3
|
module Contracts
|
6
|
-
class Contract < Model
|
4
|
+
class Contract < Model.for(:contract)
|
5
|
+
include ModelProperties
|
7
6
|
|
8
7
|
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
9
8
|
def self.build opts = {}
|
10
|
-
Contracts::TYPES[opts[:sec_type]].new opts
|
9
|
+
Contracts::TYPES[VALUES[:sec_type][opts[:sec_type]]].new opts
|
11
10
|
end
|
12
11
|
|
13
12
|
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
@@ -21,14 +20,11 @@ module IB
|
|
21
20
|
|
22
21
|
# Fields are Strings unless noted otherwise
|
23
22
|
prop :con_id, # int: The unique contract identifier.
|
24
|
-
:
|
25
|
-
:sec_type, # Security type. Valid values are: SECURITY_TYPES
|
23
|
+
:sec_type,
|
26
24
|
:strike, # double: The strike price.
|
27
|
-
:exchange, # The order destination, such as Smart.
|
28
25
|
:currency, # Only needed if there is an ambiguity, e.g. when SMART exchange
|
29
26
|
# and IBM is being requested (IBM can trade in GBP or USD).
|
30
27
|
|
31
|
-
:local_symbol, # Local exchange symbol of the underlying asset
|
32
28
|
:include_expired, # When true, contract details requests and historical
|
33
29
|
# data queries can be performed pertaining to expired contracts.
|
34
30
|
# Note: Historical data queries on expired contracts are
|
@@ -45,57 +41,39 @@ module IB
|
|
45
41
|
# identifying suffix. Ex: AAPL.O for Apple on NASDAQ.)
|
46
42
|
:sec_id, # Unique identifier of the given secIdType.
|
47
43
|
|
48
|
-
# COMBOS
|
49
44
|
:legs_description, # received in OpenOrder for all combos
|
50
45
|
|
51
|
-
:
|
46
|
+
:symbol => :s, # This is the symbol of the underlying asset.
|
47
|
+
|
48
|
+
:local_symbol => :s, # Local exchange symbol of the underlying asset
|
49
|
+
|
52
50
|
# Future/option contract multiplier (only needed when multiple possibilities exist)
|
51
|
+
:multiplier => :i,
|
53
52
|
|
54
|
-
:
|
55
|
-
|
56
|
-
|
57
|
-
val.upcase! if val.is_a?(String)
|
58
|
-
error "Don't set primary_exchange to smart", :args if val == 'SMART'
|
59
|
-
self[:primary_exchange] = val
|
60
|
-
},
|
53
|
+
:expiry => :s, # The expiration date. Use the format YYYYMM or YYYYMMDD
|
54
|
+
:exchange => :sup, # The order destination, such as Smart.
|
55
|
+
:primary_exchange => :sup, # Non-SMART exchange where the contract trades.
|
61
56
|
|
62
|
-
|
63
|
-
|
57
|
+
# Specifies a Put or Call. Valid input values are: P, PUT, C, CALL
|
58
|
+
:right =>
|
59
|
+
{:set => proc { |val|
|
64
60
|
self[:right] =
|
65
61
|
case val.to_s.upcase
|
66
|
-
when '', '0', '?'
|
67
|
-
|
62
|
+
when 'NONE', '', '0', '?'
|
63
|
+
''
|
68
64
|
when 'PUT', 'P'
|
69
|
-
'
|
65
|
+
'P'
|
70
66
|
when 'CALL', 'C'
|
71
|
-
'
|
72
|
-
else
|
73
|
-
error "Right must be one of PUT, CALL, P, C - not '#{val}'", :args
|
74
|
-
end
|
75
|
-
},
|
76
|
-
|
77
|
-
:expiry => # The expiration date. Use the format YYYYMM.
|
78
|
-
proc { |val|
|
79
|
-
self[:expiry] =
|
80
|
-
case val.to_s
|
81
|
-
when /\d{6,8}/
|
82
|
-
val.to_s
|
83
|
-
when nil, ''
|
84
|
-
nil
|
67
|
+
'C'
|
85
68
|
else
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
:sec_type => # Security type. Valid values are: SECURITY_TYPES
|
91
|
-
proc { |val|
|
92
|
-
val = nil if !val.nil? && val.empty?
|
93
|
-
unless val.nil? || SECURITY_TYPES.values.include?(val)
|
94
|
-
error "Invalid security type '#{val}' (must be one of #{SECURITY_TYPES.values}", :args
|
95
|
-
end
|
96
|
-
self[:sec_type] = val
|
69
|
+
val
|
70
|
+
end },
|
71
|
+
:validate => {:format => {:with => /^put$|^call$|^none$/,
|
72
|
+
:message => "should be put, call or none"}}
|
97
73
|
}
|
98
74
|
|
75
|
+
# Security type. Valid values are: SECURITY_TYPES
|
76
|
+
|
99
77
|
# ContractDetails fields are bundled into Contract proper, as it should be
|
100
78
|
# All fields Strings, unless specified otherwise:
|
101
79
|
prop :market_name, # The market name for this contract.
|
@@ -128,17 +106,17 @@ module IB
|
|
128
106
|
:desc_append, # Additional descriptive information about the bond.
|
129
107
|
:bond_type, # The type of bond, such as "CORP."
|
130
108
|
:coupon_type, # The type of bond coupon.
|
131
|
-
:callable, # bool: Can be called by the issuer under certain conditions.
|
132
|
-
:puttable, # bool: Can be sold back to the issuer under certain conditions
|
133
109
|
:coupon, # double: The interest rate used to calculate the amount you
|
134
110
|
# will receive in interest payments over the year. default 0
|
135
|
-
:convertible, # bool: Can be converted to stock under certain conditions.
|
136
111
|
:maturity, # The date on which the issuer must repay bond face value
|
137
112
|
:issue_date, # The date the bond was issued.
|
138
113
|
:next_option_date, # only if bond has embedded options.
|
139
114
|
:next_option_type, # only if bond has embedded options.
|
140
|
-
:
|
141
|
-
:
|
115
|
+
:notes, # Additional notes, if populated for the bond in IB's database
|
116
|
+
:callable => :bool, # Can be called by the issuer under certain conditions.
|
117
|
+
:puttable => :bool, # Can be sold back to the issuer under certain conditions
|
118
|
+
:convertible => :bool, # Can be converted to stock under certain conditions.
|
119
|
+
:next_option_partial => :bool # # only if bond has embedded options.
|
142
120
|
|
143
121
|
# Used for Delta-Neutral Combo contracts only!
|
144
122
|
# UnderComp fields are bundled into Contract proper, as it should be.
|
@@ -156,18 +134,27 @@ module IB
|
|
156
134
|
|
157
135
|
attr_accessor :description # NB: local to ib-ruby, not part of TWS.
|
158
136
|
|
137
|
+
# Extra validations
|
138
|
+
validates_inclusion_of :sec_type, :in => CODES[:sec_type].keys,
|
139
|
+
:message => "should be valid security type"
|
140
|
+
|
141
|
+
validates_format_of :expiry, :with => /^\d{6}$|^\d{8}$|^$/,
|
142
|
+
:message => "should be YYYYMM or YYYYMMDD"
|
143
|
+
|
144
|
+
validates_format_of :primary_exchange, :without => /SMART/,
|
145
|
+
:message => "should not be SMART"
|
146
|
+
|
159
147
|
DEFAULT_PROPS = {:con_id => 0,
|
160
|
-
:strike => 0,
|
148
|
+
:strike => 0.0,
|
149
|
+
:right => :none, # Not an option
|
161
150
|
:exchange => 'SMART',
|
162
|
-
:include_expired => false,
|
163
|
-
|
164
|
-
# These properties are from ContractDetails
|
165
151
|
:under_con_id => 0,
|
166
152
|
:min_tick => 0,
|
153
|
+
:coupon => 0,
|
167
154
|
:callable => false,
|
168
155
|
:puttable => false,
|
169
|
-
:coupon => 0,
|
170
156
|
:convertible => false,
|
157
|
+
:include_expired => false,
|
171
158
|
:next_option_partial => false, }
|
172
159
|
|
173
160
|
# NB: ContractDetails reference - to self!
|
@@ -181,8 +168,12 @@ module IB
|
|
181
168
|
def serialize *fields
|
182
169
|
[(fields.include?(:con_id) ? [con_id] : []),
|
183
170
|
symbol,
|
184
|
-
sec_type,
|
185
|
-
(fields.include?(:option) ?
|
171
|
+
self[:sec_type],
|
172
|
+
(fields.include?(:option) ?
|
173
|
+
[expiry,
|
174
|
+
strike,
|
175
|
+
self[:right],
|
176
|
+
multiplier] : []),
|
186
177
|
exchange,
|
187
178
|
(fields.include?(:primary_exchange) ? [primary_exchange] : []),
|
188
179
|
currency,
|
@@ -213,9 +204,16 @@ module IB
|
|
213
204
|
end
|
214
205
|
end
|
215
206
|
|
216
|
-
#
|
207
|
+
# Defined in Contract, not BAG subclass to keep code DRY
|
217
208
|
def serialize_legs *fields
|
218
|
-
|
209
|
+
case
|
210
|
+
when !bag?
|
211
|
+
[]
|
212
|
+
when legs.empty?
|
213
|
+
[0]
|
214
|
+
else
|
215
|
+
[legs.size, legs.map { |leg| leg.serialize *fields }].flatten
|
216
|
+
end
|
219
217
|
end
|
220
218
|
|
221
219
|
# This produces a string uniquely identifying this contract, in the format used
|
@@ -263,12 +261,12 @@ module IB
|
|
263
261
|
exchange && other.exchange && exchange != other.exchange
|
264
262
|
|
265
263
|
# Comparison for Bonds and Options
|
266
|
-
if
|
264
|
+
if bond? || option?
|
267
265
|
return false if right != other.right || strike != other.strike
|
268
266
|
return false if multiplier && other.multiplier && multiplier != other.multiplier
|
269
|
-
return false if expiry[0..5] != other.expiry[0..5]
|
270
|
-
return false unless expiry[6..7] == other.expiry[6..7] ||
|
271
|
-
expiry[6..7].empty? || other.expiry[6..7].empty?
|
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?)
|
272
270
|
end
|
273
271
|
|
274
272
|
# All else being equal...
|
@@ -283,13 +281,39 @@ module IB
|
|
283
281
|
end
|
284
282
|
|
285
283
|
def to_human
|
286
|
-
"<Contract: " +
|
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(" ") + ">"
|
287
293
|
end
|
288
294
|
|
289
295
|
def to_short
|
290
296
|
"#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
|
291
297
|
end
|
292
298
|
|
299
|
+
# Testing for type of contract:
|
300
|
+
|
301
|
+
def bag?
|
302
|
+
self[:sec_type] == 'BAG'
|
303
|
+
end
|
304
|
+
|
305
|
+
def bond?
|
306
|
+
self[:sec_type] == 'BOND'
|
307
|
+
end
|
308
|
+
|
309
|
+
def stock?
|
310
|
+
self[:sec_type] == 'STK'
|
311
|
+
end
|
312
|
+
|
313
|
+
def option?
|
314
|
+
self[:sec_type] == 'OPT'
|
315
|
+
end
|
316
|
+
|
293
317
|
end # class Contract
|
294
318
|
end # module Contracts
|
295
319
|
end # module Models
|
@@ -5,6 +5,14 @@ module IB
|
|
5
5
|
module Contracts
|
6
6
|
class Option < Contract
|
7
7
|
|
8
|
+
validates_numericality_of :strike, :greater_than => 0
|
9
|
+
validates_format_of :sec_type, :with => /^option$/,
|
10
|
+
:message => "should be an option"
|
11
|
+
validates_format_of :local_symbol, :with => /^\w+\s*\d{15}$|^$/,
|
12
|
+
:message => "invalid OSI code"
|
13
|
+
validates_format_of :right, :with => /^put$|^call$/,
|
14
|
+
:message => "should be put or call"
|
15
|
+
|
8
16
|
# For Options, this is contract's OSI (Option Symbology Initiative) name/code
|
9
17
|
alias osi local_symbol
|
10
18
|
|
@@ -16,8 +24,8 @@ module IB
|
|
16
24
|
# Make valid IB Contract definition from OSI (Option Symbology Initiative) code.
|
17
25
|
# NB: Simply making a new Contract with *local_symbol* (osi) property set to a
|
18
26
|
# valid OSI code works just as well, just do NOT set *expiry*, *right* or
|
19
|
-
# *strike* properties
|
20
|
-
# This class method provided as a backup
|
27
|
+
# *strike* properties in this case.
|
28
|
+
# This class method provided as a backup and shows how to analyse OSI codes.
|
21
29
|
def self.from_osi osi
|
22
30
|
|
23
31
|
# Parse contract's OSI (OCC Option Symbology Initiative) code
|
@@ -32,25 +40,26 @@ module IB
|
|
32
40
|
|
33
41
|
# Set correct expiry date - IB expiry date differs from OSI if expiry date
|
34
42
|
# falls on Saturday (see https://github.com/arvicco/option_mower/issues/4)
|
35
|
-
expiry_date = Time.
|
36
|
-
expiry_date = Time.
|
43
|
+
expiry_date = Time.utc(year, month, day)
|
44
|
+
expiry_date = Time.utc(year, month, day-1) if expiry_date.wday == 6
|
37
45
|
|
38
46
|
new :symbol => symbol,
|
39
47
|
:exchange => "SMART",
|
40
|
-
:expiry => expiry_date.to_ib,
|
48
|
+
:expiry => expiry_date.to_ib[2..7], # YYMMDD
|
41
49
|
:right => right,
|
42
50
|
:strike => strike
|
43
51
|
end
|
44
52
|
|
45
53
|
def initialize opts = {}
|
46
54
|
super opts
|
47
|
-
self
|
55
|
+
self.sec_type = 'OPT'
|
48
56
|
self[:description] ||= osi ? osi : "#{symbol} #{strike} #{right} #{expiry}"
|
49
57
|
end
|
50
58
|
|
51
59
|
def to_human
|
52
|
-
"<Option: " + [symbol, expiry, right, strike, exchange, currency].join("
|
60
|
+
"<Option: " + [symbol, expiry, right, strike, exchange, currency].join(" ") + ">"
|
53
61
|
end
|
62
|
+
|
54
63
|
end # class Option
|
55
64
|
end # class Contract
|
56
65
|
end # module Models
|
@@ -1,29 +1,30 @@
|
|
1
|
-
require 'ib-ruby/models/model'
|
2
|
-
|
3
1
|
module IB
|
4
2
|
module Models
|
5
3
|
# This is IB Order execution report.
|
6
4
|
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
7
|
-
class Execution < Model
|
5
|
+
class Execution < Model.for(:execution)
|
6
|
+
include ModelProperties
|
7
|
+
|
8
8
|
prop :order_id, # int: order id. TWS orders have a fixed order id of 0.
|
9
|
-
:client_id, # int: id
|
10
|
-
#
|
11
|
-
:
|
12
|
-
:
|
13
|
-
#
|
14
|
-
:time, # String: The order execution time.
|
9
|
+
:client_id, # int: client id. TWS orders have a fixed client id of 0.
|
10
|
+
:perm_id, # int: TWS id used to identify orders over TWS sessions
|
11
|
+
:exec_id, # String: Unique order execution id over TWS sessions.
|
12
|
+
:time, # # TODO: convert into Time object?
|
13
|
+
# String: The order execution time.
|
15
14
|
:exchange, # String: Exchange that executed the order.
|
15
|
+
:order_ref, # int: Same order_ref as in corresponding Order
|
16
|
+
[:account_name, :account_number], # String: The customer account number.
|
16
17
|
:price, # double: The order execution price.
|
17
18
|
:average_price, # double: Average price. Used in regular trades, combo
|
18
19
|
# trades and legs of the combo.
|
19
20
|
:shares, # int: The number of shares filled.
|
20
21
|
:cumulative_quantity, # int: Cumulative quantity. Used in regular
|
21
22
|
# trades, combo trades and legs of the combo
|
22
|
-
:liquidation, # int: This position is liquidated last should the need arise.
|
23
|
-
:
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
:liquidation => :bool, # int: This position is liquidated last should the need arise.
|
24
|
+
[:side, :action] => PROPS[:side] # String: Was the transaction a buy or a sale: BOT|SLD
|
25
|
+
|
26
|
+
# Extra validations
|
27
|
+
validates_numericality_of :shares, :cumulative_quantity, :price, :average_price
|
27
28
|
|
28
29
|
DEFAULT_PROPS = {:order_id => 0,
|
29
30
|
:client_id => 0,
|
@@ -31,12 +32,27 @@ module IB
|
|
31
32
|
:price => 0,
|
32
33
|
:perm_id => 0,
|
33
34
|
:liquidation => 0, }
|
35
|
+
# Comparison
|
36
|
+
def == other
|
37
|
+
perm_id == other.perm_id &&
|
38
|
+
order_id == other.order_id && # ((p __LINE__)||true) &&
|
39
|
+
client_id == other.client_id &&
|
40
|
+
exec_id == other.exec_id &&
|
41
|
+
time == other.time &&
|
42
|
+
exchange == other.exchange &&
|
43
|
+
order_ref == other.order_ref &&
|
44
|
+
side == other.side
|
45
|
+
# TODO: || compare all attributes!
|
46
|
+
end
|
34
47
|
|
35
|
-
def
|
36
|
-
"<Execution #{time}
|
37
|
-
"cumulative
|
38
|
-
"
|
48
|
+
def to_human
|
49
|
+
"<Execution: #{time} #{side} #{shares} at #{price} on #{exchange}, " +
|
50
|
+
"cumulative #{cumulative_quantity} at #{average_price}, " +
|
51
|
+
"ids #{order_id}/#{perm_id}/#{exec_id}>"
|
39
52
|
end
|
53
|
+
|
54
|
+
alias to_s to_human
|
55
|
+
|
40
56
|
end # Execution
|
41
57
|
end # module Models
|
42
58
|
end # module IB
|
data/lib/ib-ruby/models/model.rb
CHANGED
@@ -1,21 +1,29 @@
|
|
1
|
-
require 'ib-ruby/models/model_properties'
|
2
|
-
|
3
1
|
module IB
|
4
2
|
module Models
|
5
3
|
|
6
4
|
# Base IB data Model class, in future it will be developed into ActiveModel
|
7
5
|
class Model
|
8
|
-
extend ModelProperties
|
9
|
-
|
10
|
-
attr_reader :created_at
|
11
6
|
|
12
|
-
|
7
|
+
# IB Models can be either database-backed, or not
|
8
|
+
# require 'ib-ruby/db' # to make IB models database-backed
|
9
|
+
def self.for subclass
|
10
|
+
if DB
|
11
|
+
case subclass
|
12
|
+
when :execution, :bar, :order_state
|
13
|
+
# Just a couple of AR models introduced for now...
|
14
|
+
ActiveRecord::Base
|
15
|
+
else
|
16
|
+
Model
|
17
|
+
end
|
18
|
+
else
|
19
|
+
Model
|
20
|
+
end
|
21
|
+
end
|
13
22
|
|
14
23
|
# If a opts hash is given, keys are taken as attribute names, values as data.
|
15
24
|
# The model instance fields are then set automatically from the opts Hash.
|
16
25
|
def initialize(opts={})
|
17
26
|
error "Argument must be a Hash", :args unless opts.is_a?(Hash)
|
18
|
-
@created_at = Time.now
|
19
27
|
|
20
28
|
props = self.class::DEFAULT_PROPS.merge(opts)
|
21
29
|
props.keys.each { |key| self.send("#{key}=", props[key]) }
|
@@ -1,63 +1,120 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
1
4
|
module IB
|
2
5
|
module Models
|
3
6
|
|
4
|
-
# Module
|
7
|
+
# Module adds prop Macro and
|
5
8
|
module ModelProperties
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
DEFAULT_PROPS = {}
|
6
12
|
|
7
|
-
|
8
|
-
prop_hash = properties.last.is_a?(Hash) ? properties.pop : {}
|
13
|
+
### Instance methods
|
9
14
|
|
10
|
-
|
11
|
-
|
15
|
+
attr_accessor :created_at
|
16
|
+
|
17
|
+
def initialize opts={}
|
18
|
+
@created_at = Time.now
|
19
|
+
super self.class::DEFAULT_PROPS.merge(opts)
|
12
20
|
end
|
13
21
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
22
|
+
included do
|
23
|
+
|
24
|
+
### Class macros
|
25
|
+
|
26
|
+
def self.prop *properties
|
27
|
+
prop_hash = properties.last.is_a?(Hash) ? properties.pop : {}
|
28
|
+
|
29
|
+
properties.each { |names| define_property names, '' }
|
30
|
+
prop_hash.each { |names, type| define_property names, type }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.define_property names, body
|
34
|
+
aliases = [names].flatten
|
35
|
+
name = aliases.shift
|
36
|
+
instance_eval do
|
18
37
|
|
19
|
-
|
38
|
+
define_property_methods name, body
|
20
39
|
|
21
|
-
|
22
|
-
|
23
|
-
|
40
|
+
aliases.each do |ali|
|
41
|
+
alias_method "#{ali}", name
|
42
|
+
alias_method "#{ali}=", "#{name}="
|
43
|
+
end
|
24
44
|
end
|
25
45
|
end
|
26
|
-
end
|
27
46
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
def self.define_property_methods name, body={}
|
48
|
+
#p name, body
|
49
|
+
case body
|
50
|
+
when '' # default getter and setter
|
51
|
+
define_property_methods name
|
52
|
+
|
53
|
+
when Array # [setter, getter, validators]
|
54
|
+
define_property_methods name,
|
55
|
+
:get => body[0],
|
56
|
+
:set => body[1],
|
57
|
+
:validate => body[2]
|
58
|
+
|
59
|
+
when Hash # recursion base case
|
60
|
+
|
61
|
+
getter = case # Define getter
|
62
|
+
when body[:get].respond_to?(:call)
|
63
|
+
body[:get]
|
64
|
+
when body[:get]
|
65
|
+
proc { self[name].send "to_#{body[:get]}" }
|
66
|
+
when VALUES[name] # property is encoded
|
67
|
+
proc { VALUES[name][self[name]] }
|
68
|
+
else
|
69
|
+
proc { self[name] }
|
70
|
+
end
|
71
|
+
define_method name, &getter
|
72
|
+
|
73
|
+
setter = case # Define setter
|
74
|
+
when body[:set].respond_to?(:call)
|
75
|
+
body[:set]
|
76
|
+
when body[:set]
|
77
|
+
proc { |value| self[name] = value.send "to_#{body[:set]}" }
|
78
|
+
when CODES[name] # property is encoded
|
79
|
+
proc { |value| self[name] = CODES[name][value] || value }
|
80
|
+
else
|
81
|
+
proc { |value| self[name] = value }
|
82
|
+
end
|
83
|
+
define_method "#{name}=", &setter
|
84
|
+
|
85
|
+
# Define validator(s)
|
86
|
+
[body[:validate]].flatten.compact.each do |validator|
|
87
|
+
case validator
|
88
|
+
when Proc
|
89
|
+
validates_each name, &validator
|
90
|
+
when Hash
|
91
|
+
validates name, validator.dup
|
92
|
+
end
|
51
93
|
end
|
52
94
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
95
|
+
# TODO define self[:name] accessors for :virtual and :flag properties
|
96
|
+
|
97
|
+
else # setter given
|
98
|
+
define_property_methods name, :set => body
|
99
|
+
end
|
57
100
|
end
|
58
|
-
end
|
59
101
|
|
102
|
+
# Extending lighweight (not DB-backed) Model class to mimic AR::Base
|
103
|
+
unless ancestors.include? ActiveModel::Validations
|
104
|
+
include ActiveModel::Validations
|
105
|
+
|
106
|
+
def save
|
107
|
+
false
|
108
|
+
end
|
109
|
+
|
110
|
+
alias save! save
|
111
|
+
|
112
|
+
def self.find *args
|
113
|
+
[]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end # included
|
60
118
|
end # module ModelProperties
|
61
|
-
end
|
119
|
+
end # module Models
|
62
120
|
end
|
63
|
-
|