ib-ruby 0.4.3 → 0.4.20
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +32 -0
- data/HISTORY +68 -0
- data/README.rdoc +9 -6
- data/VERSION +1 -1
- data/bin/account_info +29 -0
- data/bin/contract_details +37 -0
- data/bin/depth_of_market +43 -0
- data/bin/historic_data +62 -0
- data/bin/{RequestHistoricData → historic_data_cli} +46 -91
- data/bin/market_data +49 -0
- data/bin/option_data +45 -0
- data/bin/template +21 -0
- data/bin/time_and_sales +63 -0
- data/lib/ib-ruby/connection.rb +166 -0
- data/lib/ib-ruby/constants.rb +91 -0
- data/lib/ib-ruby/messages/incoming.rb +807 -0
- data/lib/ib-ruby/messages/outgoing.rb +573 -0
- data/lib/ib-ruby/messages.rb +8 -1445
- data/lib/ib-ruby/models/bar.rb +26 -0
- data/lib/ib-ruby/models/contract.rb +335 -0
- data/lib/ib-ruby/models/execution.rb +55 -0
- data/lib/ib-ruby/models/model.rb +20 -0
- data/lib/ib-ruby/models/order.rb +262 -0
- data/lib/ib-ruby/models.rb +11 -0
- data/lib/ib-ruby/socket.rb +50 -0
- data/lib/ib-ruby/symbols/forex.rb +32 -72
- data/lib/ib-ruby/symbols/futures.rb +47 -68
- data/lib/ib-ruby/symbols/options.rb +30 -0
- data/lib/ib-ruby/symbols/stocks.rb +23 -0
- data/lib/ib-ruby/symbols.rb +9 -0
- data/lib/ib-ruby.rb +7 -8
- data/lib/legacy/bin/account_info_old +36 -0
- data/lib/legacy/bin/historic_data_old +81 -0
- data/lib/legacy/bin/market_data_old +68 -0
- data/lib/legacy/datatypes.rb +485 -0
- data/lib/legacy/ib-ruby.rb +10 -0
- data/lib/legacy/ib.rb +226 -0
- data/lib/legacy/messages.rb +1458 -0
- data/lib/version.rb +2 -3
- data/spec/ib-ruby/models/contract_spec.rb +261 -0
- data/spec/ib-ruby/models/order_spec.rb +64 -0
- data/spec/ib-ruby_spec.rb +0 -131
- metadata +106 -76
- data/bin/AccountInfo +0 -67
- data/bin/HistoricToCSV +0 -111
- data/bin/RequestMarketData +0 -78
- data/bin/SimpleTimeAndSales +0 -98
- data/bin/ib-ruby +0 -8
- data/lib/ib-ruby/datatypes.rb +0 -400
- data/lib/ib-ruby/ib.rb +0 -242
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'ib-ruby/models/model'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
module Models
|
5
|
+
# This is a single data point delivered by HistoricData messages.
|
6
|
+
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
7
|
+
class Bar < Model
|
8
|
+
attr_accessor :date, # The date-time stamp of the start of the bar. The format is
|
9
|
+
# determined by the reqHistoricalData() formatDate parameter.
|
10
|
+
:open, # The bar opening price.
|
11
|
+
:high, # The high price during the time covered by the bar.
|
12
|
+
:low, # The low price during the time covered by the bar.
|
13
|
+
:close, # The bar closing price.
|
14
|
+
:volume, # The bar opening price.
|
15
|
+
:wap, # Weighted average price during the time covered by the bar.
|
16
|
+
:has_gaps, # Whether or not there are gaps in the data.
|
17
|
+
:trades # int: When TRADES data history is returned, represents number
|
18
|
+
# of trades that occurred during the time period the bar covers
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"<Bar #{@date}: wap: #{@wap}, OHLC: #{@open}, #{@high}, #{@low}, #{@close}, " +
|
22
|
+
(@trades ? "trades: #{@trades}," : "") + " vol: #{@volume}, gaps? #{@has_gaps}>"
|
23
|
+
end
|
24
|
+
end # Bar
|
25
|
+
end # module Models
|
26
|
+
end # module IB
|
@@ -0,0 +1,335 @@
|
|
1
|
+
require 'ib-ruby/models/model'
|
2
|
+
|
3
|
+
# TODO: Implement equals() according to the criteria in IB's Java client.
|
4
|
+
|
5
|
+
module IB
|
6
|
+
module Models
|
7
|
+
class Contract < Model
|
8
|
+
|
9
|
+
# Valid security types (sec_type attribute)
|
10
|
+
SECURITY_TYPES = {:stock => "STK",
|
11
|
+
:option => "OPT",
|
12
|
+
:future => "FUT",
|
13
|
+
:index => "IND",
|
14
|
+
:futures_option => "FOP",
|
15
|
+
:forex => "CASH",
|
16
|
+
:bag => "BAG"}
|
17
|
+
|
18
|
+
BAG_SEC_TYPE = "BAG"
|
19
|
+
|
20
|
+
# Fields are Strings unless noted otherwise
|
21
|
+
attr_accessor :con_id, # int: The unique contract identifier.
|
22
|
+
:symbol, # This is the symbol of the underlying asset.
|
23
|
+
:sec_type, # Security type. Valid values are: SECURITY_TYPES
|
24
|
+
:expiry, # The expiration date. Use the format YYYYMM.
|
25
|
+
:strike, # double: The strike price.
|
26
|
+
:right, # Specifies a Put or Call. Valid values are: P, PUT, C, CALL
|
27
|
+
:multiplier, # Specifies a future or option contract multiplier
|
28
|
+
# String? (only necessary when multiple possibilities exist)
|
29
|
+
|
30
|
+
:exchange, # The order destination, such as Smart.
|
31
|
+
:currency, # Ambiguities MAY require that currency field be specified,
|
32
|
+
# for example, when SMART is the exchange and IBM is being
|
33
|
+
# requested (IBM can trade in GBP or USD).
|
34
|
+
|
35
|
+
:local_symbol, # Local exchange symbol of the underlying asset
|
36
|
+
:primary_exchange, # pick a non-aggregate (ie not the SMART) exchange
|
37
|
+
# that the contract trades on. DO NOT SET TO SMART.
|
38
|
+
|
39
|
+
:include_expired, # When true, contract details requests and historical
|
40
|
+
# data queries can be performed pertaining to expired contracts.
|
41
|
+
# Note: Historical data queries on expired contracts are
|
42
|
+
# limited to the last year of the contracts life, and are
|
43
|
+
# only supported for expired futures contracts.
|
44
|
+
# This field can NOT be set to true for orders.
|
45
|
+
|
46
|
+
:sec_id_type, # Security identifier, when querying contract details or
|
47
|
+
# when placing orders. Supported identifiers are:
|
48
|
+
# - ISIN (Example: Apple: US0378331005)
|
49
|
+
# - CUSIP (Example: Apple: 037833100)
|
50
|
+
# - SEDOL (6-AN + check digit. Example: BAE: 0263494)
|
51
|
+
# - RIC (exchange-independent RIC Root and exchange-
|
52
|
+
# identifying suffix. Ex: AAPL.O for Apple on NASDAQ.)
|
53
|
+
:sec_id, # Unique identifier of the given secIdType.
|
54
|
+
|
55
|
+
# COMBOS
|
56
|
+
:combo_legs_description, # received in open order for all combos
|
57
|
+
:combo_legs # Dynamic memory structure used to store the leg
|
58
|
+
# definitions for this contract.
|
59
|
+
|
60
|
+
# ContractDetails fields are bundled into Contract proper, as it should be
|
61
|
+
# All fields Strings, unless specified otherwise:
|
62
|
+
attr_accessor :summary, # NB: ContractDetails reference - to self!
|
63
|
+
:market_name, # The market name for this contract.
|
64
|
+
:trading_class, # The trading class name for this contract.
|
65
|
+
:min_tick, # double: The minimum price tick.
|
66
|
+
:price_magnifier, # int: Allows execution and strike prices to be
|
67
|
+
# reported consistently with market data, historical data and the
|
68
|
+
# order price: Z on LIFFE is reported in index points, not GBP.
|
69
|
+
|
70
|
+
:order_types, # The list of valid order types for this contract.
|
71
|
+
:valid_exchanges, # The list of exchanges this contract is traded on.
|
72
|
+
:under_con_id, # int: The underlying contract ID.
|
73
|
+
:long_name, # Descriptive name of the asset.
|
74
|
+
:contract_month, # Typically the contract month of the underlying for
|
75
|
+
# a futures contract.
|
76
|
+
|
77
|
+
# The industry classification of the underlying/product:
|
78
|
+
:industry, # Wide industry. For example, Financial.
|
79
|
+
:category, # Industry category. For example, InvestmentSvc.
|
80
|
+
:subcategory, # Subcategory. For example, Brokerage.
|
81
|
+
:time_zone, # The ID of the time zone for the trading hours of the
|
82
|
+
# product. For example, EST.
|
83
|
+
:trading_hours, # The trading hours of the product. For example:
|
84
|
+
# 20090507:0700-1830,1830-2330;20090508:CLOSED.
|
85
|
+
:liquid_hours, # The liquid trading hours of the product. For example,
|
86
|
+
# 20090507:0930-1600;20090508:CLOSED.
|
87
|
+
|
88
|
+
# Bond values:
|
89
|
+
:cusip, # The nine-character bond CUSIP or the 12-character SEDOL.
|
90
|
+
:ratings, # Credit rating of the issuer. Higher credit rating generally
|
91
|
+
# indicates a less risky investment. Bond ratings are from
|
92
|
+
# Moody's and S&P respectively.
|
93
|
+
:desc_append, # Additional descriptive information about the bond.
|
94
|
+
:bond_type, # The type of bond, such as "CORP."
|
95
|
+
:coupon_type, # The type of bond coupon.
|
96
|
+
:callable, # bool: Can be called by the issuer under certain conditions.
|
97
|
+
:puttable, # bool: Can be sold back to the issuer under certain conditions
|
98
|
+
:coupon, # double: The interest rate used to calculate the amount you
|
99
|
+
# will receive in interest payments over the year. default 0
|
100
|
+
:convertible, # bool: Can be converted to stock under certain conditions.
|
101
|
+
:maturity, # The date on which the issuer must repay bond face value
|
102
|
+
:issue_date, # The date the bond was issued.
|
103
|
+
:next_option_date, # only if bond has embedded options.
|
104
|
+
:next_option_type, # only if bond has embedded options.
|
105
|
+
:next_option_partial, # bool: # only if bond has embedded options.
|
106
|
+
:notes # Additional notes, if populated for the bond in IB's database
|
107
|
+
|
108
|
+
# Used for Delta-Neutral Combo contracts only!
|
109
|
+
# UnderComp fields are bundled into Contract proper, as it should be.
|
110
|
+
# Already defined
|
111
|
+
attr_accessor :under_comp, # if not nil, attributes below are sent to server
|
112
|
+
#:under_con_id is is already defined in ContractDetails section
|
113
|
+
:under_delta, # double: The underlying stock or future delta.
|
114
|
+
:under_price # double: The price of the underlying.
|
115
|
+
|
116
|
+
# NB :description field is entirely local to ib-ruby, and not part of TWS.
|
117
|
+
# You can use it to store whatever arbitrary data you want.
|
118
|
+
attr_accessor :description
|
119
|
+
|
120
|
+
def initialize opts = {}
|
121
|
+
# Assign defaults to properties first!
|
122
|
+
@con_id = 0
|
123
|
+
@strike = 0
|
124
|
+
@sec_type = ''
|
125
|
+
@include_expired = false
|
126
|
+
@combo_legs = Array.new
|
127
|
+
|
128
|
+
# These properties are from ContractDetails
|
129
|
+
@summary = self
|
130
|
+
@under_con_id = 0
|
131
|
+
@min_tick = 0
|
132
|
+
@callable = false
|
133
|
+
@puttable = false
|
134
|
+
@coupon = 0
|
135
|
+
@convertible = false
|
136
|
+
@next_option_partial = false
|
137
|
+
|
138
|
+
super opts
|
139
|
+
end
|
140
|
+
|
141
|
+
# some protective filters
|
142
|
+
def primary_exchange=(x)
|
143
|
+
x.upcase! if x.is_a?(String)
|
144
|
+
|
145
|
+
# per http://chuckcaplan.com/twsapi/index.php/Class%20Contract
|
146
|
+
raise(ArgumentError.new("Don't set primary_exchange to smart")) if x == "SMART"
|
147
|
+
|
148
|
+
@primary_exchange = x
|
149
|
+
end
|
150
|
+
|
151
|
+
def right=(x)
|
152
|
+
x.upcase! if x.is_a?(String)
|
153
|
+
x = nil if !x.nil? && x.empty?
|
154
|
+
raise(ArgumentError.new("Invalid right \"#{x}\" (must be one of PUT, CALL, P, C)")) unless x.nil? || ["PUT", "CALL", "P", "C", "0"].include?(x)
|
155
|
+
@right = x
|
156
|
+
end
|
157
|
+
|
158
|
+
def expiry=(x)
|
159
|
+
x = x.to_s
|
160
|
+
if (x.nil? || !(x =~ /\d{6,8}/)) and !x.empty? then
|
161
|
+
raise ArgumentError.new("Invalid expiry \"#{x}\" (must be in format YYYYMM or YYYYMMDD)")
|
162
|
+
end
|
163
|
+
@expiry = x
|
164
|
+
end
|
165
|
+
|
166
|
+
def sec_type=(x)
|
167
|
+
x = nil if !x.nil? && x.empty?
|
168
|
+
raise(ArgumentError.new("Invalid security type \"#{x}\" (see SECURITY_TYPES constant in Contract class for valid types)")) unless x.nil? || SECURITY_TYPES.values.include?(x)
|
169
|
+
@sec_type = x
|
170
|
+
end
|
171
|
+
|
172
|
+
def reset
|
173
|
+
@combo_legs = Array.new
|
174
|
+
@strike = 0
|
175
|
+
end
|
176
|
+
|
177
|
+
# This returns an Array of data from the given contract, in standard format.
|
178
|
+
# Different messages serialize contracts differently. Go figure.
|
179
|
+
# Note that it does not include the combo legs.
|
180
|
+
def serialize(type = :long)
|
181
|
+
[symbol,
|
182
|
+
sec_type,
|
183
|
+
expiry,
|
184
|
+
strike,
|
185
|
+
right,
|
186
|
+
multiplier,
|
187
|
+
exchange] +
|
188
|
+
(type == :long ? [primary_exchange] : []) +
|
189
|
+
[currency,
|
190
|
+
local_symbol]
|
191
|
+
end
|
192
|
+
|
193
|
+
# @Legacy
|
194
|
+
def serialize_long(version)
|
195
|
+
serialize(:long)
|
196
|
+
end
|
197
|
+
|
198
|
+
# @Legacy
|
199
|
+
def serialize_short(version)
|
200
|
+
serialize(:short)
|
201
|
+
end
|
202
|
+
|
203
|
+
# This produces a string uniquely identifying this contract, in the format used
|
204
|
+
# for command line arguments in the IB-Ruby examples. The format is:
|
205
|
+
#
|
206
|
+
# symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
207
|
+
#
|
208
|
+
# Fields not needed for a particular security should be left blank
|
209
|
+
# (e.g. strike and right are only relevant for options.)
|
210
|
+
#
|
211
|
+
# For example, to query the British pound futures contract trading on Globex
|
212
|
+
# expiring in September, 2008, the string is:
|
213
|
+
#
|
214
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
215
|
+
def serialize_ib_ruby(version)
|
216
|
+
serialize.join(":")
|
217
|
+
end
|
218
|
+
|
219
|
+
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
220
|
+
def self.from_ib_ruby(string)
|
221
|
+
c = Contract.new
|
222
|
+
c.symbol, c.sec_type, c.expiry, c.strike, c.right, c.multiplier,
|
223
|
+
c.exchange, c.primary_exchange, c.currency, c.local_symbol = string.split(":")
|
224
|
+
c
|
225
|
+
end
|
226
|
+
|
227
|
+
# Serialize under_comp parameters
|
228
|
+
def serialize_under_comp(*args)
|
229
|
+
# EClientSocket.java, line 471:
|
230
|
+
if under_comp
|
231
|
+
[true,
|
232
|
+
under_con_id,
|
233
|
+
under_delta,
|
234
|
+
under_price]
|
235
|
+
else
|
236
|
+
[false]
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def serialize_algo(*args)
|
241
|
+
raise "Unimplemented"
|
242
|
+
#if (m_serverVersion >= MIN_SERVER_VER_ALGO_ORDERS) {
|
243
|
+
# send( order.m_algoStrategy);
|
244
|
+
# if( !IsEmpty(order.m_algoStrategy)) {
|
245
|
+
# java.util.Vector algoParams = order.m_algoParams;
|
246
|
+
# int algoParamsCount = algoParams == null ? 0 : algoParams.size();
|
247
|
+
# send( algoParamsCount);
|
248
|
+
# if( algoParamsCount > 0) {
|
249
|
+
# for( int i = 0; i < algoParamsCount; ++i) {
|
250
|
+
# TagValue tagValue = (TagValue)algoParams.get(i);
|
251
|
+
# send( tagValue.m_tag);
|
252
|
+
# send( tagValue.m_value);
|
253
|
+
# }
|
254
|
+
# }
|
255
|
+
# }
|
256
|
+
#}
|
257
|
+
end
|
258
|
+
|
259
|
+
# Some messages send open_close too, some don't. WTF.
|
260
|
+
def serialize_combo_legs(type = :short)
|
261
|
+
# No idea what "BAG" means. Copied from the Java code.
|
262
|
+
return [] unless sec_type.upcase == "BAG"
|
263
|
+
return [0] if combo_legs.empty? || combo_legs.nil?
|
264
|
+
[combo_legs.size,
|
265
|
+
combo_legs.map { |leg| leg.serialize(type) }]
|
266
|
+
end
|
267
|
+
|
268
|
+
def to_human
|
269
|
+
"<Contract: " + [symbol, expiry, sec_type, strike, right, exchange, currency].join("-") + ">"
|
270
|
+
end
|
271
|
+
|
272
|
+
def to_short
|
273
|
+
"#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_s
|
277
|
+
to_human
|
278
|
+
end
|
279
|
+
|
280
|
+
# ComboLeg is an internal class of Contract, as it should be
|
281
|
+
class ComboLeg < Model
|
282
|
+
# // open/close leg value is same as combo
|
283
|
+
# Specifies whether the order is an open or close order. Valid values are:
|
284
|
+
SAME = 0 # Same as the parent security. The only option for retail customers.
|
285
|
+
OPEN = 1 # Open. This value is only valid for institutional customers.
|
286
|
+
CLOSE = 2 # Close. This value is only valid for institutional customers.
|
287
|
+
UNKNOWN = 3
|
288
|
+
|
289
|
+
|
290
|
+
attr_accessor :con_id, # int: The unique contract identifier specifying the security.
|
291
|
+
:ratio, # int: Select the relative number of contracts for the leg you
|
292
|
+
# are constructing. To help determine the ratio for a
|
293
|
+
# specific combination order, refer to the Interactive
|
294
|
+
# Analytics section of the User's Guide.
|
295
|
+
|
296
|
+
:action, # String: BUY/SELL/SSHORT/SSHORTX
|
297
|
+
# The side (buy or sell) for the leg you are constructing.
|
298
|
+
:exchange, # String: exchange to which the complete combination
|
299
|
+
# order will be routed.
|
300
|
+
:open_close, # int: Specifies whether the order is an open or close order.
|
301
|
+
# Valid values: ComboLeg::SAME/OPEN/CLOSE/UNKNOWN
|
302
|
+
|
303
|
+
# For institutional customers only! For stock legs when doing short sale
|
304
|
+
:short_sale_slot, # int: 0 - retail, 1 = clearing broker, 2 = third party
|
305
|
+
:designated_location, # String: Only for shortSaleSlot == 2.
|
306
|
+
# Otherwise leave blank or orders will be rejected.
|
307
|
+
:exempt_code # int: ?
|
308
|
+
|
309
|
+
def initialize opts = {}
|
310
|
+
@con_id = 0
|
311
|
+
@ratio = 0
|
312
|
+
@open_close = 0
|
313
|
+
@short_sale_slot = 0
|
314
|
+
@designated_location = ''
|
315
|
+
@exempt_code = -1
|
316
|
+
|
317
|
+
super opts
|
318
|
+
end
|
319
|
+
|
320
|
+
# Some messages include open_close, some don't. wtf.
|
321
|
+
def serialize(type = :short)
|
322
|
+
[con_id,
|
323
|
+
ratio,
|
324
|
+
action,
|
325
|
+
exchange] +
|
326
|
+
type == :short ? [] : [open_close,
|
327
|
+
short_sale_slot,
|
328
|
+
designated_location,
|
329
|
+
exempt_code]
|
330
|
+
end
|
331
|
+
end # ComboLeg
|
332
|
+
|
333
|
+
end # class Contract
|
334
|
+
end # module Models
|
335
|
+
end # module IB
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'ib-ruby/models/model'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
module Models
|
5
|
+
# This is IB Order execution report.
|
6
|
+
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
7
|
+
class Execution < Model
|
8
|
+
attr_accessor :order_id, # int: order id. TWS orders have a fixed order id of 0.
|
9
|
+
:client_id, # int: id of the client that placed the order.
|
10
|
+
# TWS orders have a fixed client id of 0.
|
11
|
+
:exec_id, # String: Unique order execution id.
|
12
|
+
:time, # String: The order execution time.
|
13
|
+
:account_number, #String: The customer account number.
|
14
|
+
:exchange, # String: Exchange that executed the order.
|
15
|
+
:side, # String: Was the transaction a buy or a sale: BOT|SLD
|
16
|
+
:shares, # int: The number of shares filled.
|
17
|
+
:price, # double: The order execution price.
|
18
|
+
:perm_id, # int: TWS id used to identify orders, remains
|
19
|
+
# the same over TWS sessions.
|
20
|
+
:liquidation, # int: Identifies the position as one to be liquidated
|
21
|
+
# last should the need arise.
|
22
|
+
:cumulative_quantity, # int: Cumulative quantity. Used in regular
|
23
|
+
# trades, combo trades and legs of the combo
|
24
|
+
:average_price # double: Average price. Used in regular trades, combo
|
25
|
+
# trades and legs of the combo.
|
26
|
+
|
27
|
+
def side= value
|
28
|
+
@side = case value
|
29
|
+
when 'BOT'
|
30
|
+
:bought
|
31
|
+
when 'SLD'
|
32
|
+
:sold
|
33
|
+
else
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize opts = {}
|
39
|
+
@order_id = 0
|
40
|
+
@client_id = 0
|
41
|
+
@shares = 0
|
42
|
+
@price = 0
|
43
|
+
@perm_id = 0
|
44
|
+
@liquidation = 0
|
45
|
+
|
46
|
+
super opts
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"<Execution #{@time}: #{@side} #{@shares} shares @ #{@price} on #{@exchange}, " +
|
51
|
+
"IDs: #{@order_id} order, #{@exec_id} exec, #{@perm_id} perm>"
|
52
|
+
end
|
53
|
+
end # Execution
|
54
|
+
end # module Models
|
55
|
+
end # module IB
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module IB
|
2
|
+
module Models
|
3
|
+
|
4
|
+
# Base IB data Model class, in future it will be developed into ActiveModel
|
5
|
+
class Model
|
6
|
+
attr_reader :created_at
|
7
|
+
|
8
|
+
# If a opts hash is given, keys are taken as attribute names, values as data.
|
9
|
+
# The model instance fields are then set automatically from the opts Hash.
|
10
|
+
#
|
11
|
+
def initialize(opts={})
|
12
|
+
raise ArgumentError.new("Argument must be a Hash") unless opts.is_a?(Hash)
|
13
|
+
@created_at = Time.now
|
14
|
+
opts.keys.each do |key|
|
15
|
+
self.send((key.to_s + "=").to_sym, opts[key])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end # Model
|
19
|
+
end # module Models
|
20
|
+
end # module IB
|