ib-ruby 0.8.1 → 0.8.3

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.
Files changed (128) hide show
  1. data/.gitignore +0 -1
  2. data/HISTORY +5 -0
  3. data/README.md +47 -53
  4. data/Rakefile +2 -1
  5. data/VERSION +1 -1
  6. data/app/assets/javascripts/ib/application.js +15 -0
  7. data/app/assets/javascripts/ib/underlyings.js +2 -0
  8. data/app/assets/stylesheets/ib/application.css +13 -0
  9. data/app/assets/stylesheets/ib/underlyings.css +4 -0
  10. data/app/assets/stylesheets/scaffold.css +56 -0
  11. data/app/controllers/ib/application_controller.rb +5 -0
  12. data/app/controllers/ib/underlyings_controller.rb +87 -0
  13. data/app/helpers/ib/application_helper.rb +4 -0
  14. data/app/helpers/ib/underlyings_helper.rb +4 -0
  15. data/app/models/ib/underlying.rb +5 -0
  16. data/app/views/ib/underlyings/_form.html.erb +33 -0
  17. data/app/views/ib/underlyings/edit.html.erb +6 -0
  18. data/app/views/ib/underlyings/index.html.erb +29 -0
  19. data/app/views/ib/underlyings/new.html.erb +5 -0
  20. data/app/views/ib/underlyings/show.html.erb +25 -0
  21. data/app/views/layouts/ib/application.html.erb +14 -0
  22. data/config/routes.rb +6 -0
  23. data/db/config.yml +19 -0
  24. data/db/migrate/{101_add_executions.rb → 101_add_ib_executions.rb} +2 -2
  25. data/db/migrate/{111_add_bars.rb → 111_add_ib_bars.rb} +2 -2
  26. data/db/migrate/{121_add_order_states.rb → 121_add_ib_order_states.rb} +2 -2
  27. data/db/migrate/{131_add_orders.rb → 131_add_ib_orders.rb} +2 -2
  28. data/db/migrate/{141_add_combo_legs.rb → 141_add_ib_combo_legs.rb} +2 -2
  29. data/db/migrate/{151_add_underlyings.rb → 151_add_ib_underlyings.rb} +2 -2
  30. data/db/migrate/{161_add_contract_details.rb → 161_add_ib_contract_details.rb} +2 -2
  31. data/db/migrate/{171_add_contracts.rb → 171_add_ib_contracts.rb} +2 -2
  32. data/db/schema.rb +245 -0
  33. data/lib/ib/base.rb +97 -0
  34. data/lib/ib/base_properties.rb +140 -0
  35. data/lib/{ib-ruby → ib}/connection.rb +2 -2
  36. data/lib/{ib-ruby → ib}/constants.rb +0 -0
  37. data/lib/{ib-ruby → ib}/db.rb +9 -5
  38. data/lib/ib/engine.rb +35 -0
  39. data/lib/{ib-ruby → ib}/errors.rb +0 -0
  40. data/lib/{ib-ruby → ib}/extensions.rb +2 -2
  41. data/lib/{ib-ruby → ib}/logger.rb +0 -0
  42. data/lib/{ib-ruby → ib}/messages/abstract_message.rb +0 -0
  43. data/lib/{ib-ruby → ib}/messages/incoming/abstract_message.rb +1 -1
  44. data/lib/{ib-ruby → ib}/messages/incoming/alert.rb +0 -0
  45. data/lib/{ib-ruby → ib}/messages/incoming/contract_data.rb +0 -0
  46. data/lib/{ib-ruby → ib}/messages/incoming/delta_neutral_validation.rb +0 -0
  47. data/lib/{ib-ruby → ib}/messages/incoming/execution_data.rb +0 -0
  48. data/lib/{ib-ruby → ib}/messages/incoming/historical_data.rb +0 -0
  49. data/lib/{ib-ruby → ib}/messages/incoming/market_depths.rb +0 -0
  50. data/lib/{ib-ruby → ib}/messages/incoming/next_valid_id.rb +0 -0
  51. data/lib/{ib-ruby → ib}/messages/incoming/open_order.rb +0 -0
  52. data/lib/{ib-ruby → ib}/messages/incoming/order_status.rb +0 -0
  53. data/lib/{ib-ruby → ib}/messages/incoming/portfolio_value.rb +0 -0
  54. data/lib/{ib-ruby → ib}/messages/incoming/real_time_bar.rb +0 -0
  55. data/lib/{ib-ruby → ib}/messages/incoming/scanner_data.rb +0 -0
  56. data/lib/{ib-ruby → ib}/messages/incoming/ticks.rb +0 -0
  57. data/lib/{ib-ruby → ib}/messages/incoming.rb +14 -14
  58. data/lib/{ib-ruby → ib}/messages/outgoing/abstract_message.rb +1 -1
  59. data/lib/{ib-ruby → ib}/messages/outgoing/bar_requests.rb +0 -0
  60. data/lib/{ib-ruby → ib}/messages/outgoing/place_order.rb +0 -0
  61. data/lib/{ib-ruby → ib}/messages/outgoing.rb +5 -5
  62. data/lib/ib/messages.rb +8 -0
  63. data/lib/ib/model.rb +8 -0
  64. data/lib/ib/models.rb +10 -0
  65. data/lib/ib/requires.rb +9 -0
  66. data/lib/{ib-ruby → ib}/socket.rb +0 -0
  67. data/lib/{ib-ruby → ib}/symbols/forex.rb +1 -1
  68. data/lib/{ib-ruby → ib}/symbols/futures.rb +2 -2
  69. data/lib/{ib-ruby → ib}/symbols/options.rb +1 -1
  70. data/lib/{ib-ruby → ib}/symbols/stocks.rb +1 -1
  71. data/lib/ib/symbols.rb +9 -0
  72. data/lib/{ib-ruby → ib}/version.rb +0 -0
  73. data/lib/ib-ruby.rb +2 -24
  74. data/lib/ib.rb +23 -0
  75. data/lib/models/ib/bag.rb +51 -0
  76. data/lib/models/ib/bar.rb +41 -0
  77. data/lib/models/ib/combo_leg.rb +102 -0
  78. data/lib/models/ib/contract.rb +287 -0
  79. data/lib/models/ib/contract_detail.rb +68 -0
  80. data/lib/models/ib/execution.rb +62 -0
  81. data/lib/models/ib/option.rb +60 -0
  82. data/lib/models/ib/order.rb +389 -0
  83. data/lib/models/ib/order_state.rb +126 -0
  84. data/lib/models/ib/underlying.rb +35 -0
  85. data/spec/README.md +34 -2
  86. data/spec/TODO +5 -1
  87. data/spec/comb.rb +13 -0
  88. data/spec/db.rb +1 -1
  89. data/spec/db_helper.rb +3 -3
  90. data/spec/dummy.rb +13 -0
  91. data/spec/gw.rb +4 -0
  92. data/spec/{ib-ruby → ib}/connection_spec.rb +0 -0
  93. data/spec/{ib-ruby → ib}/messages/incoming/alert_spec.rb +0 -0
  94. data/spec/{ib-ruby → ib}/messages/incoming/open_order_spec.rb +0 -0
  95. data/spec/{ib-ruby → ib}/messages/incoming/order_status_spec.rb +16 -17
  96. data/spec/{ib-ruby → ib}/messages/outgoing/account_data_spec.rb +0 -0
  97. data/spec/{ib-ruby → ib}/messages/outgoing/market_data_type_spec.rb +0 -0
  98. data/spec/integration/historic_data_spec.rb +3 -3
  99. data/spec/integration/orders/trades_spec.rb +1 -1
  100. data/spec/{ib-ruby/models → models/ib}/bag_spec.rb +2 -7
  101. data/spec/{ib-ruby/models → models/ib}/bar_spec.rb +1 -6
  102. data/spec/{ib-ruby/models → models/ib}/combo_leg_spec.rb +2 -12
  103. data/spec/{ib-ruby/models → models/ib}/contract_detail_spec.rb +3 -8
  104. data/spec/{ib-ruby/models → models/ib}/contract_spec.rb +4 -12
  105. data/spec/{ib-ruby/models → models/ib}/execution_spec.rb +2 -7
  106. data/spec/{ib-ruby/models → models/ib}/option_spec.rb +1 -6
  107. data/spec/{ib-ruby/models → models/ib}/order_spec.rb +5 -10
  108. data/spec/{ib-ruby/models → models/ib}/order_state_spec.rb +2 -7
  109. data/spec/{ib-ruby/models → models/ib}/underlying_spec.rb +3 -7
  110. data/spec/my.rb +5 -0
  111. data/spec/spec_helper.rb +62 -36
  112. metadata +417 -544
  113. data/lib/ib-ruby/messages.rb +0 -8
  114. data/lib/ib-ruby/models/bag.rb +0 -54
  115. data/lib/ib-ruby/models/bar.rb +0 -43
  116. data/lib/ib-ruby/models/combo_leg.rb +0 -104
  117. data/lib/ib-ruby/models/contract.rb +0 -287
  118. data/lib/ib-ruby/models/contract_detail.rb +0 -70
  119. data/lib/ib-ruby/models/execution.rb +0 -64
  120. data/lib/ib-ruby/models/model.rb +0 -105
  121. data/lib/ib-ruby/models/model_properties.rb +0 -146
  122. data/lib/ib-ruby/models/option.rb +0 -62
  123. data/lib/ib-ruby/models/order.rb +0 -389
  124. data/lib/ib-ruby/models/order_state.rb +0 -128
  125. data/lib/ib-ruby/models/underlying.rb +0 -36
  126. data/lib/ib-ruby/models.rb +0 -15
  127. data/lib/ib-ruby/symbols.rb +0 -9
  128. data/spec/test.rb +0 -61
@@ -1,4 +1,4 @@
1
- require 'ib-ruby/messages/outgoing/abstract_message'
1
+ require 'ib/messages/outgoing/abstract_message'
2
2
 
3
3
  # TODO: Don't instantiate messages, use their classes as just namespace for .encode/decode
4
4
 
@@ -108,7 +108,7 @@ module IB
108
108
  :side)
109
109
 
110
110
  # data = { :id => ticker_id (int),
111
- # :contract => Contract,
111
+ # :contract => IB::Contract,
112
112
  # :exercise_action => int, 1 = exercise, 2 = lapse
113
113
  # :exercise_quantity => int, The number of contracts to be exercised
114
114
  # :account => string,
@@ -130,7 +130,7 @@ module IB
130
130
 
131
131
  # @data={:id => int: ticker_id - Must be a unique value. When the market data
132
132
  # returns, it will be identified by this tag,
133
- # :contract => Models::Contract, requested contract.
133
+ # :contract => IB::Contract, requested contract.
134
134
  # :tick_list => String: comma delimited list of requested tick groups:
135
135
  # Group ID - Description - Requested Tick Types
136
136
  # 100 - Option Volume (currently for stocks) - 29, 30
@@ -268,8 +268,8 @@ module IB
268
268
  :scanner_setting_pairs,
269
269
  :stock_type_filter)
270
270
 
271
- require 'ib-ruby/messages/outgoing/place_order'
272
- require 'ib-ruby/messages/outgoing/bar_requests'
271
+ require 'ib/messages/outgoing/place_order'
272
+ require 'ib/messages/outgoing/bar_requests'
273
273
 
274
274
  end # module Outgoing
275
275
  end # module Messages
@@ -0,0 +1,8 @@
1
+ module IB
2
+ module Messages
3
+ end
4
+ end
5
+
6
+ require 'ib/messages/outgoing'
7
+ require 'ib/messages/incoming'
8
+
data/lib/ib/model.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'ib/base_properties'
2
+ require 'ib/base'
3
+
4
+ module IB
5
+ # IB Models can be either lightweight (tableless) or database-backed.
6
+ # require 'ib/db' - to make all IB models database-backed
7
+ Model = IB.db_backed? ? ActiveRecord::Base : IB::Base
8
+ end
data/lib/ib/models.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'ib/model'
2
+
3
+ require 'models/ib/contract_detail'
4
+ require 'models/ib/underlying'
5
+ require 'models/ib/contract'
6
+ require 'models/ib/order_state'
7
+ require 'models/ib/order'
8
+ require 'models/ib/combo_leg'
9
+ require 'models/ib/execution'
10
+ require 'models/ib/bar'
@@ -0,0 +1,9 @@
1
+ require 'ib/version'
2
+ require 'ib/extensions'
3
+ require 'ib/errors'
4
+ require 'ib/constants'
5
+ require 'ib/connection'
6
+
7
+ require 'ib/models'
8
+ require 'ib/messages'
9
+ require 'ib/symbols'
File without changes
@@ -1,4 +1,4 @@
1
- # Note that the :description field is particular to ib-ruby, and is NOT part of the
1
+ # Note that the :description field is particular to ib, and is NOT part of the
2
2
  # standard TWS API. It is never transmitted to IB. It's purely used clientside, and
3
3
  # you can store any arbitrary string that you may find useful there.
4
4
  module IB
@@ -4,7 +4,7 @@
4
4
  # takes over as the volume leader.
5
5
  #
6
6
  #
7
- # Note that the :description field is particular to ib-ruby, and is NOT part of the standard TWS API.
7
+ # Note that the :description field is particular to ib, and is NOT part of the standard TWS API.
8
8
  # It is never transmitted to IB. It's purely used clientside, and you can store any arbitrary string that
9
9
  # you may find useful there.
10
10
  #
@@ -39,7 +39,7 @@ module IB
39
39
  "#{ next_quarter_year(time) }#{ sprintf("%02d", next_quarter_month(time)) }"
40
40
  end
41
41
 
42
- # Convenience method; generates a Models::Contract instance for a futures
42
+ # Convenience method; generates an IB::Contract instance for a futures
43
43
  # contract with the given parameters.
44
44
  #
45
45
  # If expiry is nil, it will use the end month of the current
@@ -1,6 +1,6 @@
1
1
  # Stock contracts definitions
2
2
  #
3
- # Note that the :description field is particular to ib-ruby, and is NOT part of the
3
+ # Note that the :description field is particular to ib, and is NOT part of the
4
4
  # standard TWS API. It is never transmitted to IB. It's purely used clientside, and
5
5
  # you can store any arbitrary string that you may find useful there.
6
6
 
@@ -1,6 +1,6 @@
1
1
  # Stock contracts definitions
2
2
  #
3
- # Note that the :description field is particular to ib-ruby, and is NOT part of the
3
+ # Note that the :description field is particular to ib, and is NOT part of the
4
4
  # standard TWS API. It is never transmitted to IB. It's purely used clientside, and
5
5
  # you can store any arbitrary string that you may find useful there.
6
6
 
data/lib/ib/symbols.rb ADDED
@@ -0,0 +1,9 @@
1
+ module IB
2
+ module Symbols
3
+ end
4
+ end
5
+
6
+ require 'ib/symbols/forex'
7
+ require 'ib/symbols/futures'
8
+ require 'ib/symbols/stocks'
9
+ require 'ib/symbols/options'
File without changes
data/lib/ib-ruby.rb CHANGED
@@ -1,24 +1,2 @@
1
- module IB
2
- # IB Models can be either database-backed, or not
3
- # By default there is no DB backend, unless specifically requested
4
- # require 'ib-ruby/db' # to make all IB models database-backed
5
- DB ||= false
6
-
7
- require 'ib-ruby/version'
8
- require 'ib-ruby/extensions'
9
- require 'ib-ruby/errors'
10
- require 'ib-ruby/constants'
11
- require 'ib-ruby/connection'
12
-
13
- require 'ib-ruby/models'
14
- Datatypes = Models # Flatten namespace (IB::Contract instead of IB::Models::Contract)
15
- include Models # Legacy alias
16
-
17
- require 'ib-ruby/messages'
18
- IncomingMessages = Messages::Incoming # Legacy alias
19
- OutgoingMessages = Messages::Outgoing # Legacy alias
20
-
21
- require 'ib-ruby/symbols'
22
- end
23
- IbRuby = IB
24
-
1
+ require 'ib'
2
+
data/lib/ib.rb ADDED
@@ -0,0 +1,23 @@
1
+ module IB
2
+ def self.db_backed?
3
+ !!defined?(IB::DB)
4
+ end
5
+
6
+ def self.rails?
7
+ !!defined?(Rails) && Rails.respond_to?('env')
8
+ end
9
+
10
+ end # module IB
11
+
12
+ IbRuby = IB
13
+ Ib = IB
14
+
15
+ # IB Models can be either lightweight (tableless) or database-backed.
16
+ # By default there is no DB backend, unless specifically requested
17
+ # require 'ib/db' # to make all IB models database-backed
18
+
19
+ if IB.rails?
20
+ require 'ib/engine'
21
+ else
22
+ require 'ib/requires'
23
+ end
@@ -0,0 +1,51 @@
1
+ require 'models/ib/contract'
2
+
3
+ module IB
4
+
5
+ # "BAG" is not really a contract, but a combination (combo) of securities.
6
+ # AKA basket or bag of securities. Individual securities in combo are represented
7
+ # by ComboLeg objects.
8
+ class Bag < Contract
9
+ # General Notes:
10
+ # 1. :exchange for the leg definition must match that of the combination order.
11
+ # The exception is for a STK legs, which must specify the SMART exchange.
12
+ # 2. :symbol => "USD" For combo Contract, this is an arbitrary value (like "USD")
13
+
14
+ validates_format_of :sec_type, :with => /^bag$/, :message => "should be a bag"
15
+ validates_format_of :right, :with => /^none$/, :message => "should be none"
16
+ validates_format_of :expiry, :with => /^$/, :message => "should be blank"
17
+
18
+ def default_attributes
19
+ super.merge :sec_type => :bag #,:legs => Array.new,
20
+ end
21
+
22
+ def description
23
+ self[:description] || to_human
24
+ end
25
+
26
+ def to_human
27
+ "<Bag: #{[symbol, exchange, currency].join(' ')} legs: #{legs_description} >"
28
+ end
29
+
30
+ ### Leg-related methods
31
+
32
+ # TODO: Rewrite with legs and legs_description being strictly in sync...
33
+ # TODO: Find a way to serialize legs without references...
34
+ # IB-equivalent leg description.
35
+ def legs_description
36
+ self[:legs_description] || legs.map { |leg| "#{leg.con_id}|#{leg.weight}" }.join(',')
37
+ end
38
+
39
+ # Check if two Contracts have same legs (maybe in different order)
40
+ def same_legs? other
41
+ legs == other.legs ||
42
+ legs_description.split(',').sort == other.legs_description.split(',').sort
43
+ end
44
+
45
+ # Contract comparison
46
+ def == other
47
+ super && same_legs?(other)
48
+ end
49
+
50
+ end # class Bag
51
+ end # IB
@@ -0,0 +1,41 @@
1
+ module IB
2
+ # This is a single data point delivered by HistoricData or RealTimeBar messages.
3
+ # Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
4
+ class Bar < IB::Model
5
+ include BaseProperties
6
+
7
+ prop :open, # The bar opening price.
8
+ :high, # The high price during the time covered by the bar.
9
+ :low, # The low price during the time covered by the bar.
10
+ :close, # The bar closing price.
11
+ :volume, # Volume
12
+ :wap, # Weighted average price during the time covered by the bar.
13
+ :trades, # int: When TRADES data history is returned, represents number
14
+ # of trades that occurred during the time period the bar covers
15
+ :time, # TODO: convert into Time object?
16
+ # The date-time stamp of the start of the bar. The format is
17
+ # determined by the reqHistoricalData() formatDate parameter.
18
+ :has_gaps => :bool # Whether or not there are gaps in the data.
19
+
20
+ validates_numericality_of :open, :high, :low, :close, :volume
21
+
22
+ # Order comparison
23
+ def == other
24
+ time == other.time &&
25
+ open == other.open &&
26
+ high == other.high &&
27
+ low == other.low &&
28
+ close == other.close &&
29
+ wap == other.wap &&
30
+ trades == other.trades &&
31
+ volume == other.volume
32
+ end
33
+
34
+ def to_human
35
+ "<Bar: #{time} wap #{wap} OHLC #{open} #{high} #{low} #{close} " +
36
+ (trades ? "trades #{trades}" : "") + " vol #{volume} gaps #{has_gaps}>"
37
+ end
38
+
39
+ alias to_s to_human
40
+ end # class Bar
41
+ end # module IB
@@ -0,0 +1,102 @@
1
+ module IB
2
+
3
+ # ComboLeg is essentially a join Model between Combo (BAG) Contract and
4
+ # individual Contracts (securities) that this BAG contains.
5
+ class ComboLeg < IB::Model
6
+ include BaseProperties
7
+
8
+ # BAG Combo Contract that contains this Leg
9
+ belongs_to :combo, :class_name => 'Contract'
10
+ # Contract that constitutes this Leg
11
+ belongs_to :leg_contract, :class_name => 'Contract', :foreign_key => :leg_contract_id
12
+
13
+ # General Notes:
14
+ # 1. The exchange for the leg definition must match that of the combination order.
15
+ # The exception is for a STK leg definition, which must specify the SMART exchange.
16
+
17
+ prop :con_id, # int: The unique contract identifier specifying the security.
18
+ :ratio, # int: Select the relative number of contracts for the leg you
19
+ # are constructing. To help determine the ratio for a
20
+ # specific combination order, refer to the Interactive
21
+ # Analytics section of the User's Guide.
22
+
23
+ :exchange, # String: exchange to which the complete combo order will be routed.
24
+ # For institutional customers only! For stock legs when doing short sale
25
+ :short_sale_slot, # int: 0 - retail(default), 1 = clearing broker, 2 = third party
26
+ :designated_location, # String: Only for shortSaleSlot == 2.
27
+ # Otherwise leave blank or orders will be rejected.
28
+ :exempt_code, # int: ?
29
+ [:side, :action] => PROPS[:side], # String: Action/side: BUY/SELL/SSHORT/SSHORTX
30
+ :open_close => PROPS[:open_close]
31
+ # int: Whether the order is an open or close order. Values:
32
+ # SAME = 0 Same as the parent security. The only option for retail customers.
33
+ # OPEN = 1 Open. This value is only valid for institutional customers.
34
+ # CLOSE = 2 Close. This value is only valid for institutional customers.
35
+ # UNKNOWN = 3
36
+
37
+ # Extra validations
38
+ validates_numericality_of :ratio, :con_id
39
+ validates_format_of :designated_location, :with => /^$/,
40
+ :message => "should be blank or orders will be rejected"
41
+
42
+ def default_attributes
43
+ super.merge :con_id => 0,
44
+ :ratio => 1,
45
+ :side => :buy,
46
+ :open_close => :same, # The only option for retail customers.
47
+ :short_sale_slot => :default,
48
+ :designated_location => '',
49
+ :exchange => 'SMART', # Unless SMART, Order modification fails
50
+ :exempt_code => -1
51
+ end
52
+
53
+ # Leg's weight is a combination of action and ratio
54
+ def weight
55
+ side == :buy ? ratio : -ratio
56
+ end
57
+
58
+ def weight= value
59
+ value = value.to_i
60
+ if value > 0
61
+ self.side = :buy
62
+ self.ratio = value
63
+ else
64
+ self.side = :sell
65
+ self.ratio = -value
66
+ end
67
+ end
68
+
69
+ # Some messages include open_close, some don't. wtf.
70
+ def serialize *fields
71
+ [con_id,
72
+ ratio,
73
+ side.to_sup,
74
+ exchange,
75
+ (fields.include?(:extended) ?
76
+ [self[:open_close],
77
+ self[:short_sale_slot],
78
+ designated_location,
79
+ exempt_code] :
80
+ [])
81
+ ].flatten
82
+ end
83
+
84
+ def to_human
85
+ "<ComboLeg: #{side} #{ratio} con_id #{con_id} at #{exchange}>"
86
+ end
87
+
88
+ # Order comparison
89
+ def == other
90
+ other && other.is_a?(ComboLeg) &&
91
+ con_id == other.con_id &&
92
+ ratio == other.ratio &&
93
+ open_close == other.open_close &&
94
+ short_sale_slot == other.short_sale_slot&&
95
+ exempt_code == other.exempt_code &&
96
+ side == other.side &&
97
+ exchange == other.exchange &&
98
+ designated_location == other.designated_location
99
+ end
100
+
101
+ end # ComboLeg
102
+ end # module IB
@@ -0,0 +1,287 @@
1
+ require 'models/ib/contract_detail'
2
+ require 'models/ib/underlying'
3
+
4
+ module IB
5
+ class Contract < IB::Model
6
+ include BaseProperties
7
+
8
+ # Fields are Strings unless noted otherwise
9
+ prop :con_id, # int: The unique contract identifier.
10
+ :currency, # Only needed if there is an ambiguity, e.g. when SMART exchange
11
+ # and IBM is being requested (IBM can trade in GBP or USD).
12
+
13
+ :legs_description, # received in OpenOrder for all combos
14
+
15
+ :sec_type, # Security type. Valid values are: SECURITY_TYPES
16
+
17
+ :sec_id, # Unique identifier of the given secIdType.
18
+
19
+ :sec_id_type => :sup, # Security identifier, when querying contract details or
20
+ # when placing orders. Supported identifiers are:
21
+ # - ISIN (Example: Apple: US0378331005)
22
+ # - CUSIP (Example: Apple: 037833100)
23
+ # - SEDOL (6-AN + check digit. Example: BAE: 0263494)
24
+ # - RIC (exchange-independent RIC Root and exchange-
25
+ # identifying suffix. Ex: AAPL.O for Apple on NASDAQ.)
26
+
27
+ :symbol => :s, # This is the symbol of the underlying asset.
28
+
29
+ :local_symbol => :s, # Local exchange symbol of the underlying asset
30
+
31
+ # Future/option contract multiplier (only needed when multiple possibilities exist)
32
+ :multiplier => {:set => :i},
33
+
34
+ :strike => :f, # double: The strike price.
35
+ :expiry => :s, # The expiration date. Use the format YYYYMM or YYYYMMDD
36
+ :exchange => :sup, # The order destination, such as Smart.
37
+ :primary_exchange => :sup, # Non-SMART exchange where the contract trades.
38
+ :include_expired => :bool, # When true, contract details requests and historical
39
+ # data queries can be performed pertaining to expired contracts.
40
+ # Note: Historical data queries on expired contracts are
41
+ # limited to the last year of the contracts life, and are
42
+ # only supported for expired futures contracts.
43
+ # This field can NOT be set to true for orders.
44
+
45
+
46
+ # Specifies a Put or Call. Valid input values are: P, PUT, C, CALL
47
+ :right => {
48
+ :set => proc { |val|
49
+ self[:right] =
50
+ case val.to_s.upcase
51
+ when 'NONE', '', '0', '?'
52
+ ''
53
+ when 'PUT', 'P'
54
+ 'P'
55
+ when 'CALL', 'C'
56
+ 'C'
57
+ else
58
+ val
59
+ end
60
+ },
61
+ :validate => {:format => {:with => /^put$|^call$|^none$/,
62
+ :message => "should be put, call or none"}}
63
+ }
64
+
65
+ attr_accessor :description # NB: local to ib, not part of TWS.
66
+
67
+ ### Associations
68
+
69
+ has_many :orders # Placed for this Contract
70
+
71
+ has_one :contract_detail # Volatile info about this Contract
72
+
73
+ # For Contracts that are part of BAG
74
+ has_one :leg, :class_name => 'ComboLeg', :foreign_key => :leg_contract_id
75
+ has_one :combo, :class_name => 'Contract', :through => :leg
76
+
77
+ # for Combo/BAG Contracts that contain ComboLegs
78
+ has_many :combo_legs, :foreign_key => :combo_id
79
+ has_many :leg_contracts, :class_name => 'Contract', :through => :combo_legs
80
+ alias legs combo_legs
81
+ alias legs= combo_legs=
82
+
83
+ alias combo_legs_description legs_description
84
+ alias combo_legs_description= legs_description=
85
+
86
+ # for Delta-Neutral Combo Contracts
87
+ has_one :underlying
88
+ alias under_comp underlying
89
+ alias under_comp= underlying=
90
+
91
+
92
+ ### Extra validations
93
+ validates_inclusion_of :sec_type, :in => CODES[:sec_type].keys,
94
+ :message => "should be valid security type"
95
+
96
+ validates_format_of :expiry, :with => /^\d{6}$|^\d{8}$|^$/,
97
+ :message => "should be YYYYMM or YYYYMMDD"
98
+
99
+ validates_format_of :primary_exchange, :without => /SMART/,
100
+ :message => "should not be SMART"
101
+
102
+ validates_format_of :sec_id_type, :with => /ISIN|SEDOL|CUSIP|RIC|^$/,
103
+ :message => "should be valid security identifier"
104
+
105
+ validates_numericality_of :multiplier, :strike, :allow_nil => true
106
+
107
+ def default_attributes
108
+ super.merge :con_id => 0,
109
+ :strike => 0.0,
110
+ :right => :none, # Not an option
111
+ :exchange => 'SMART',
112
+ :include_expired => false
113
+ end
114
+
115
+ # This returns an Array of data from the given contract.
116
+ # Different messages serialize contracts differently. Go figure.
117
+ # Note that it does NOT include the combo legs.
118
+ # serialize [:option, :con_id, :include_expired, :sec_id]
119
+ def serialize *fields
120
+ [(fields.include?(:con_id) ? [con_id] : []),
121
+ symbol,
122
+ self[:sec_type],
123
+ (fields.include?(:option) ?
124
+ [expiry,
125
+ strike,
126
+ self[:right],
127
+ multiplier] : []),
128
+ exchange,
129
+ (fields.include?(:primary_exchange) ? [primary_exchange] : []),
130
+ currency,
131
+ local_symbol,
132
+ (fields.include?(:sec_id) ? [sec_id_type, sec_id] : []),
133
+ (fields.include?(:include_expired) ? [include_expired] : []),
134
+ ].flatten
135
+ end
136
+
137
+ def serialize_long *fields
138
+ serialize :option, :primary_exchange, *fields
139
+ end
140
+
141
+ def serialize_short *fields
142
+ serialize :option, *fields
143
+ end
144
+
145
+ # Serialize under_comp parameters: EClientSocket.java, line 471
146
+ def serialize_under_comp *args
147
+ under_comp ? under_comp.serialize : [false]
148
+ end
149
+
150
+ # Defined in Contract, not BAG subclass to keep code DRY
151
+ def serialize_legs *fields
152
+ case
153
+ when !bag?
154
+ []
155
+ when legs.empty?
156
+ [0]
157
+ else
158
+ [legs.size, legs.map { |leg| leg.serialize *fields }].flatten
159
+ end
160
+ end
161
+
162
+ # This produces a string uniquely identifying this contract, in the format used
163
+ # for command line arguments in the IB-Ruby examples. The format is:
164
+ #
165
+ # symbol:sec_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
166
+ #
167
+ # Fields not needed for a particular security should be left blank
168
+ # (e.g. strike and right are only relevant for options.)
169
+ #
170
+ # For example, to query the British pound futures contract trading on Globex
171
+ # expiring in September, 2008, the string is:
172
+ #
173
+ # GBP:FUT:200809:::62500:GLOBEX::USD:
174
+ def serialize_ib_ruby
175
+ serialize_long.join(":")
176
+ end
177
+
178
+ # Contract comparison
179
+ def == other
180
+ return false unless other.is_a?(self.class)
181
+
182
+ # Different sec_id_type
183
+ return false if sec_id_type && other.sec_id_type && sec_id_type != other.sec_id_type
184
+
185
+ # Different sec_id
186
+ return false if sec_id && other.sec_id && sec_id != other.sec_id
187
+
188
+ # Different symbols
189
+ return false if symbol && other.symbol && symbol != other.symbol
190
+
191
+ # Different currency
192
+ return false if currency && other.currency && currency != other.currency
193
+
194
+ # Same con_id for all Bags, but unknown for new Contracts...
195
+ # 0 or nil con_id matches any
196
+ return false if con_id != 0 && other.con_id != 0 &&
197
+ con_id && other.con_id && con_id != other.con_id
198
+
199
+ # SMART or nil exchange matches any
200
+ return false if exchange != 'SMART' && other.exchange != 'SMART' &&
201
+ exchange && other.exchange && exchange != other.exchange
202
+
203
+ # Comparison for Bonds and Options
204
+ if bond? || option?
205
+ return false if right != other.right || strike != other.strike
206
+ return false if multiplier && other.multiplier &&
207
+ multiplier != other.multiplier
208
+ return false if expiry && expiry[0..5] != other.expiry[0..5]
209
+ return false unless expiry && (expiry[6..7] == other.expiry[6..7] ||
210
+ expiry[6..7].empty? || other.expiry[6..7].empty?)
211
+ end
212
+
213
+ # All else being equal...
214
+ sec_type == other.sec_type
215
+ end
216
+
217
+ def to_s
218
+ "<Contract: " + instance_variables.map do |key|
219
+ value = send(key[1..-1])
220
+ " #{key}=#{value}" unless value.nil? || value == '' || value == 0
221
+ end.compact.join(',') + " >"
222
+ end
223
+
224
+ def to_human
225
+ "<Contract: " +
226
+ [symbol,
227
+ sec_type,
228
+ (expiry == '' ? nil : expiry),
229
+ (right == :none ? nil : right),
230
+ (strike == 0 ? nil : strike),
231
+ exchange,
232
+ currency
233
+ ].compact.join(" ") + ">"
234
+ end
235
+
236
+ def to_short
237
+ "#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
238
+ end
239
+
240
+ # Testing for type of contract:
241
+
242
+ def bag?
243
+ self[:sec_type] == 'BAG'
244
+ end
245
+
246
+ def bond?
247
+ self[:sec_type] == 'BOND'
248
+ end
249
+
250
+ def stock?
251
+ self[:sec_type] == 'STK'
252
+ end
253
+
254
+ def option?
255
+ self[:sec_type] == 'OPT'
256
+ end
257
+
258
+ end # class Contract
259
+
260
+
261
+ ### Now let's deal with Contract subclasses
262
+
263
+ require 'models/ib/option'
264
+ require 'models/ib/bag'
265
+
266
+ class Contract
267
+ # Specialized Contract subclasses representing different security types
268
+ Subclasses = Hash.new(Contract)
269
+ Subclasses[:bag] = IB::Bag
270
+ Subclasses[:option] = IB::Option
271
+
272
+ # This returns a Contract initialized from the serialize_ib_ruby format string.
273
+ def self.build opts = {}
274
+ subclass = VALUES[:sec_type][opts[:sec_type]] || opts[:sec_type].to_sym
275
+ Contract::Subclasses[subclass].new opts
276
+ end
277
+
278
+ # This returns a Contract initialized from the serialize_ib_ruby format string.
279
+ def self.from_ib_ruby string
280
+ keys = [:symbol, :sec_type, :expiry, :strike, :right, :multiplier,
281
+ :exchange, :primary_exchange, :currency, :local_symbol]
282
+ props = Hash[keys.zip(string.split(":"))]
283
+ props.delete_if { |k, v| v.nil? || v.empty? }
284
+ Contract.build props
285
+ end
286
+ end # class Contract
287
+ end # module IB