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
@@ -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
|
-
|