ib-api 10.33.1

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 (161) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +52 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CLAUDE.md +131 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +17 -0
  8. data/Gemfile.lock +120 -0
  9. data/Guardfile +24 -0
  10. data/LICENSE +674 -0
  11. data/LLM_GUIDE.md +388 -0
  12. data/README.md +114 -0
  13. data/Rakefile +11 -0
  14. data/VERSION +1 -0
  15. data/api.gemspec +50 -0
  16. data/bin/console +96 -0
  17. data/bin/console.yml +3 -0
  18. data/bin/setup +8 -0
  19. data/bin/simple +91 -0
  20. data/changelog.md +32 -0
  21. data/conditions/ib/execution_condition.rb +31 -0
  22. data/conditions/ib/margin_condition.rb +28 -0
  23. data/conditions/ib/order_condition.rb +29 -0
  24. data/conditions/ib/percent_change_condition.rb +34 -0
  25. data/conditions/ib/price_condition.rb +44 -0
  26. data/conditions/ib/time_condition.rb +42 -0
  27. data/conditions/ib/volume_condition.rb +36 -0
  28. data/lib/class_extensions.rb +167 -0
  29. data/lib/ib/base.rb +109 -0
  30. data/lib/ib/base_properties.rb +178 -0
  31. data/lib/ib/connection.rb +573 -0
  32. data/lib/ib/constants.rb +402 -0
  33. data/lib/ib/contract.rb +30 -0
  34. data/lib/ib/errors.rb +52 -0
  35. data/lib/ib/messages/abstract_message.rb +68 -0
  36. data/lib/ib/messages/incoming/abstract_message.rb +116 -0
  37. data/lib/ib/messages/incoming/abstract_tick.rb +25 -0
  38. data/lib/ib/messages/incoming/account_message.rb +26 -0
  39. data/lib/ib/messages/incoming/alert.rb +34 -0
  40. data/lib/ib/messages/incoming/contract_data.rb +105 -0
  41. data/lib/ib/messages/incoming/contract_message.rb +13 -0
  42. data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
  43. data/lib/ib/messages/incoming/execution_data.rb +50 -0
  44. data/lib/ib/messages/incoming/histogram_data.rb +30 -0
  45. data/lib/ib/messages/incoming/historical_data.rb +65 -0
  46. data/lib/ib/messages/incoming/historical_data_update.rb +50 -0
  47. data/lib/ib/messages/incoming/managed_accounts.rb +21 -0
  48. data/lib/ib/messages/incoming/market_depth.rb +34 -0
  49. data/lib/ib/messages/incoming/market_depth_l2.rb +15 -0
  50. data/lib/ib/messages/incoming/next_valid_id.rb +19 -0
  51. data/lib/ib/messages/incoming/open_order.rb +290 -0
  52. data/lib/ib/messages/incoming/order_status.rb +85 -0
  53. data/lib/ib/messages/incoming/portfolio_value.rb +47 -0
  54. data/lib/ib/messages/incoming/position_data.rb +21 -0
  55. data/lib/ib/messages/incoming/positions_multi.rb +15 -0
  56. data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
  57. data/lib/ib/messages/incoming/receive_fa.rb +30 -0
  58. data/lib/ib/messages/incoming/scanner_data.rb +54 -0
  59. data/lib/ib/messages/incoming/tick_by_tick.rb +77 -0
  60. data/lib/ib/messages/incoming/tick_efp.rb +18 -0
  61. data/lib/ib/messages/incoming/tick_generic.rb +12 -0
  62. data/lib/ib/messages/incoming/tick_option.rb +60 -0
  63. data/lib/ib/messages/incoming/tick_price.rb +60 -0
  64. data/lib/ib/messages/incoming/tick_size.rb +55 -0
  65. data/lib/ib/messages/incoming/tick_string.rb +13 -0
  66. data/lib/ib/messages/incoming.rb +292 -0
  67. data/lib/ib/messages/outgoing/abstract_message.rb +84 -0
  68. data/lib/ib/messages/outgoing/bar_request_message.rb +247 -0
  69. data/lib/ib/messages/outgoing/new-place-order.rb +193 -0
  70. data/lib/ib/messages/outgoing/old-place-order.rb +147 -0
  71. data/lib/ib/messages/outgoing/place_order.rb +149 -0
  72. data/lib/ib/messages/outgoing/request_account_summary.rb +79 -0
  73. data/lib/ib/messages/outgoing/request_historical_data.rb +182 -0
  74. data/lib/ib/messages/outgoing/request_market_data.rb +102 -0
  75. data/lib/ib/messages/outgoing/request_market_depth.rb +57 -0
  76. data/lib/ib/messages/outgoing/request_real_time_bars.rb +48 -0
  77. data/lib/ib/messages/outgoing/request_scanner_subscription.rb +73 -0
  78. data/lib/ib/messages/outgoing/request_tick_by_tick_data.rb +21 -0
  79. data/lib/ib/messages/outgoing.rb +410 -0
  80. data/lib/ib/messages.rb +139 -0
  81. data/lib/ib/order_condition.rb +26 -0
  82. data/lib/ib/plugins.rb +27 -0
  83. data/lib/ib/prepare_data.rb +61 -0
  84. data/lib/ib/raw_message_parser.rb +99 -0
  85. data/lib/ib/socket.rb +83 -0
  86. data/lib/ib/support.rb +236 -0
  87. data/lib/ib/version.rb +6 -0
  88. data/lib/ib-api.rb +44 -0
  89. data/lib/server_versions.rb +145 -0
  90. data/lib/support/array_function.rb +28 -0
  91. data/lib/support/logging.rb +45 -0
  92. data/models/ib/account.rb +72 -0
  93. data/models/ib/account_value.rb +33 -0
  94. data/models/ib/bag.rb +55 -0
  95. data/models/ib/bar.rb +31 -0
  96. data/models/ib/combo_leg.rb +127 -0
  97. data/models/ib/contract.rb +411 -0
  98. data/models/ib/contract_detail.rb +118 -0
  99. data/models/ib/execution.rb +67 -0
  100. data/models/ib/forex.rb +12 -0
  101. data/models/ib/future.rb +64 -0
  102. data/models/ib/index.rb +14 -0
  103. data/models/ib/option.rb +149 -0
  104. data/models/ib/option_detail.rb +84 -0
  105. data/models/ib/order.rb +720 -0
  106. data/models/ib/order_state.rb +155 -0
  107. data/models/ib/portfolio_value.rb +86 -0
  108. data/models/ib/spread.rb +176 -0
  109. data/models/ib/stock.rb +25 -0
  110. data/models/ib/underlying.rb +32 -0
  111. data/plugins/ib/advanced-account.rb +442 -0
  112. data/plugins/ib/alerts/base-alert.rb +125 -0
  113. data/plugins/ib/alerts/gateway-alerts.rb +15 -0
  114. data/plugins/ib/alerts/order-alerts.rb +73 -0
  115. data/plugins/ib/auto-adjust.rb +0 -0
  116. data/plugins/ib/connection-tools.rb +122 -0
  117. data/plugins/ib/eod.rb +326 -0
  118. data/plugins/ib/greeks.rb +102 -0
  119. data/plugins/ib/managed-accounts.rb +274 -0
  120. data/plugins/ib/market-price.rb +150 -0
  121. data/plugins/ib/option-chain.rb +167 -0
  122. data/plugins/ib/order-flow.rb +157 -0
  123. data/plugins/ib/order-prototypes/abstract.rb +67 -0
  124. data/plugins/ib/order-prototypes/adaptive.rb +40 -0
  125. data/plugins/ib/order-prototypes/all-in-one.rb +46 -0
  126. data/plugins/ib/order-prototypes/combo.rb +46 -0
  127. data/plugins/ib/order-prototypes/forex.rb +40 -0
  128. data/plugins/ib/order-prototypes/limit.rb +193 -0
  129. data/plugins/ib/order-prototypes/market.rb +116 -0
  130. data/plugins/ib/order-prototypes/pegged.rb +169 -0
  131. data/plugins/ib/order-prototypes/premarket.rb +31 -0
  132. data/plugins/ib/order-prototypes/stop.rb +202 -0
  133. data/plugins/ib/order-prototypes/volatility.rb +39 -0
  134. data/plugins/ib/order-prototypes.rb +118 -0
  135. data/plugins/ib/probability-of-expiring.rb +109 -0
  136. data/plugins/ib/process-orders.rb +155 -0
  137. data/plugins/ib/roll.rb +86 -0
  138. data/plugins/ib/spread-prototypes/butterfly.rb +77 -0
  139. data/plugins/ib/spread-prototypes/calendar.rb +97 -0
  140. data/plugins/ib/spread-prototypes/stock-spread.rb +56 -0
  141. data/plugins/ib/spread-prototypes/straddle.rb +70 -0
  142. data/plugins/ib/spread-prototypes/strangle.rb +93 -0
  143. data/plugins/ib/spread-prototypes/vertical.rb +83 -0
  144. data/plugins/ib/spread-prototypes.rb +70 -0
  145. data/plugins/ib/symbols/abstract.rb +136 -0
  146. data/plugins/ib/symbols/bonds.rb +28 -0
  147. data/plugins/ib/symbols/cfd.rb +19 -0
  148. data/plugins/ib/symbols/combo.rb +46 -0
  149. data/plugins/ib/symbols/commodity.rb +17 -0
  150. data/plugins/ib/symbols/forex.rb +41 -0
  151. data/plugins/ib/symbols/futures.rb +127 -0
  152. data/plugins/ib/symbols/index.rb +43 -0
  153. data/plugins/ib/symbols/options.rb +99 -0
  154. data/plugins/ib/symbols/stocks.rb +44 -0
  155. data/plugins/ib/symbols/version.rb +5 -0
  156. data/plugins/ib/symbols.rb +118 -0
  157. data/plugins/ib/verify.rb +226 -0
  158. data/symbols/w20.yml +210 -0
  159. data/t.txt +20 -0
  160. data/update.md +71 -0
  161. metadata +327 -0
@@ -0,0 +1,109 @@
1
+ module IB
2
+ module ProbabilityOfExpiring
3
+
4
+ # Use by calling
5
+ # a = Stock.new symbol: 'A'
6
+ #
7
+ require 'prime'
8
+ require 'distribution'
9
+
10
+
11
+
12
+ def probability_of_assignment **args
13
+ ( probability_of_expiring(**args) - 1 ).abs
14
+ end
15
+ def probability_of_expiring **args
16
+ @probability_of_expiring = calculate_probability_of_expiring(**args) if @probability_of_expiring.nil? || ! args.empty?
17
+ @probability_of_expiring
18
+ end
19
+
20
+ private
21
+ =begin
22
+ Here are the steps to calculate the probability of expiry cone for a stock in
23
+ the next six months using the Black-Scholes model:
24
+
25
+ * Determine the current stock price and the strike price for the option you
26
+ are interested in. Let's say the current stock price is $100 and the strike
27
+ price is $110. * Determine the time to expiry. In this case, we are
28
+ interested in the next six months, so the time to expiry is 0.5 years. *
29
+ Determine the implied volatility of the stock. Implied volatility is a measure
30
+ of the expected volatility of the stock over the life of the option, and can be
31
+ estimated from the option prices in the market.
32
+
33
+ * Use the Black-Scholes formula to calculate the probability of the stock
34
+ expiring within the range of prices that make up the expiry cone. The formula
35
+ is:
36
+
37
+ P = N(d2)
38
+
39
+ Where P is the probability of the stock expiring within the expiry cone, and
40
+ N is the cumulative distribution function of the standard normal
41
+ distribution. d2 is calculated as:
42
+
43
+ d2 = (ln(S/K) + (r - 0.5 * σ^2) * T) / (σ * sqrt(T))
44
+
45
+ Where S is the current stock price, K is the strike price, r is the risk-free
46
+ interest rate, σ is the implied volatility, and T is the time to expiry.
47
+
48
+ Look up the value of N(d2) in a standard normal distribution table, or use a
49
+ calculator or spreadsheet program that can calculate cumulative distribution
50
+ functions.
51
+
52
+ The result is the probability of the stock expiring within the expiry cone.
53
+ For example, if N(d2) is 0.35, then the probability of the stock expiring
54
+ within the expiry cone is 35%.
55
+
56
+ (ChatGPT)
57
+ =end
58
+ def calculate_probability_of_expiring price: nil,
59
+ interest: 0.03,
60
+ iv: nil,
61
+ strike: nil,
62
+ expiry: nil,
63
+ ref_date: Date.today
64
+
65
+ if iv.nil? && self.respond_to?( :greek )
66
+ IB::Connection.logger.info "Probability_of_expiring: using current IV and Underlying-Price for calculation"
67
+ request_greeks if greek.nil?
68
+ iv = greek.implied_volatility
69
+ price = greek.under_price if price.nil?
70
+ end
71
+ error "ProbabilityOfExpiringCone needs iv as input" if iv.nil? || iv.zero?
72
+
73
+ if price.nil?
74
+ price = if self.strike.to_i.zero?
75
+ market_price
76
+ else
77
+ underlying.market_price
78
+ end
79
+ end
80
+ error "ProbabilityOfExpiringCone needs price as input" if price.to_i.zero?
81
+
82
+
83
+ strike ||= self.strike
84
+ error "ProbabilityOfExpiringCone needs strike as input" if strike.to_i.zero?
85
+
86
+ if expiry.nil?
87
+ if !last_trading_day.present? || last_trading_day.empty?
88
+ error "ProbabilityOfExpiringCone needs expiry as input"
89
+ else
90
+ expiry = last_trading_day
91
+ end
92
+ end
93
+ time_to_expiry = ( Date.parse( expiry.to_s ) - ref_date ).to_i
94
+
95
+ # # Calculate d1 and d2
96
+ d1 = (Math.log(price/strike.to_f) + (interest + 0.5*iv**2)*time_to_expiry) / (iv * Math.sqrt(time_to_expiry))
97
+ d2 = d1 - iv * Math.sqrt(time_to_expiry)
98
+ #
99
+ # # Calculate the probability of expiry cone
100
+ Distribution::Normal.cdf(d2)
101
+
102
+ end
103
+ end
104
+
105
+ class Contract
106
+ include ProbabilityOfExpiring
107
+ end
108
+
109
+ end
@@ -0,0 +1,155 @@
1
+ module IB
2
+ =begin
3
+
4
+ Plugin for a comfortable processing of orders
5
+
6
+ Public API
7
+ ==========
8
+
9
+ Extends IB::Connection
10
+
11
+ * initialize_order_handling
12
+
13
+ subscribes to various tws-messages and keeps record of the order-state
14
+
15
+ * request_open_orders
16
+
17
+ (aliased as UpdateOrders) erases account.orders and requests open-orders from the TWS
18
+ and populates Account#Orders
19
+
20
+ =end
21
+ module ProcessOrders
22
+ protected
23
+ def initialize_order_handling
24
+
25
+ subscribe( :CommissionReport, :ExecutionData, :OrderStatus, :OpenOrder, :OpenOrderEnd, :NextValidId ) do |msg|
26
+ case msg
27
+
28
+ when IB::Messages::Incoming::CommissionReport
29
+ # Commission-Reports are not assigned to a order -
30
+ logger.info "CommissionReport -------#{msg.exec_id} :...:C: #{msg.commission} :...:P/L: #{msg.realized_pnl}-"
31
+ when IB::Messages::Incoming::OrderStatus
32
+
33
+ # The order-state only links via local_id and perm_id to orders.
34
+ # There is no reference to a contract or an account
35
+
36
+ success = update_order_dependent_object( msg.order_state) do |o|
37
+ o.order_states.save_insert msg.order_state, :status
38
+ end
39
+
40
+ logger.warn { "Order State not assigned-- #{msg.order_state.to_human} ----------" } if success.nil?
41
+
42
+ when IB::Messages::Incoming::OpenOrder
43
+ account_data(msg.order.account) do | this_account |
44
+ # first update the contracts
45
+ # make open order equal to IB::Spreads (include negativ con_id)
46
+ msg.contract[:con_id] = -msg.contract.combo_legs.map{|y| y.con_id}.sum if msg.contract.is_a? IB::Bag
47
+ msg.contract.orders.save_insert msg.order, :local_id
48
+ this_account.contracts.save_insert msg.contract, :con_id, false
49
+ # now save the order-record
50
+ msg.order.contract = msg.contract
51
+ this_account.orders.save_insert msg.order, :local_id
52
+ end
53
+
54
+ # update_ib_order msg ## aus support
55
+ when IB::Messages::Incoming::OpenOrderEnd
56
+ # exitcondition=true
57
+ logger.debug { "OpenOrderEnd" }
58
+
59
+ when IB::Messages::Incoming::ExecutionData
60
+ # Excution-Data are fired independly from order-states.
61
+ # The Objects are stored at the associated order
62
+ success = update_order_dependent_object( msg.execution) do |o|
63
+ o.executions << msg.execution
64
+ if msg.execution.cumulative_quantity.to_i == o.total_quantity.abs
65
+ logger.info{ "#{o.account} --> #{o.contract.symbol}: Execution completed" }
66
+ o.order_states << IB::OrderState.new( perm_id: o.perm_id,
67
+ local_id: o.local_id,
68
+ status: 'Filled' )
69
+ # update portfoliovalue
70
+ a = @accounts.detect{ | x | x.account == o.account } # we are in a mutex controlled environment
71
+ pv = a.portfolio_values.detect{ | y | y.contract.con_id == o.contract.con_id}
72
+ change = o.action == :sell ? -o.total_quantity : o.total_quantity
73
+ if pv.present?
74
+ pv.update_attribute :position, pv.position + change
75
+ else
76
+ a.portfolio_values << IB::PortfolioValue.new( position: change, contract: o.contract )
77
+ end
78
+ else
79
+ logger.debug{ "#{o.account} --> #{o.contract.symbol}: Execution not completed (#{msg.execution.cumulative_quantity.to_i}/#{o.total_quantity.abs})" }
80
+ end # branch
81
+ end # block
82
+
83
+ logger.warn { "Execution-Record not assigned-- #{msg.execution.to_human} ----------" } if success.nil?
84
+
85
+ end # case msg.code
86
+ end # do
87
+ end # def subscribe
88
+
89
+ # Resets the order-array for each account.
90
+ # Requests all open (eg. pending) orders from the tws
91
+ #
92
+ # Waits until the OpenOrderEnd-Message is received
93
+
94
+ public
95
+ def request_open_orders
96
+
97
+ q = Queue.new
98
+ subscription = subscribe( :OpenOrderEnd ) { q.push(true) } # signal success
99
+ account_data {| account | account.orders = [] }
100
+ send_message :RequestAllOpenOrders
101
+ ## the OpenOrderEnd-message usually appears after 0.1 sec.
102
+ ## we wait for 1 sec.
103
+ th = Thread.new{ sleep 1 ; q.close }
104
+
105
+ q.pop # wait for OpenOrderEnd or finishing of thread
106
+
107
+ unsubscribe subscription
108
+ if q.closed?
109
+ 5.times do
110
+ logger.fatal { "Is the API in read-only modus? No Open Order Message received! "}
111
+ sleep 0.2
112
+ end
113
+ else
114
+ Thread.kill(th)
115
+ q.close
116
+ account_data {| account | account.orders } # reset order array
117
+ end
118
+ end
119
+
120
+ alias update_orders request_open_orders
121
+
122
+ private
123
+ =begin
124
+ UpdateOrderDependingObject
125
+
126
+ Generic method which enables operations on the order-Object,
127
+ which is associated to OrderState-, Execution-, CommissionReport-
128
+ events fired by the tws.
129
+ The order is identified by local_id and perm_id
130
+
131
+ Everything is carried out in a mutex-synchonized environment
132
+ =end
133
+ def update_order_dependent_object order_dependent_object # :nodoc:
134
+ account_data do | a |
135
+ order = if order_dependent_object.local_id.present?
136
+ a.locate_order local_id: order_dependent_object.local_id
137
+ else
138
+ a.locate_order perm_id: order_dependent_object.perm_id
139
+ end
140
+ yield order if order.present?
141
+ end
142
+ end
143
+
144
+
145
+ end # module
146
+
147
+ class Connection
148
+ include ProcessOrders
149
+ end
150
+ Connection.current.activate_plugin 'managed-accounts'
151
+ Connection.current.initialize_managed_accounts!
152
+ Connection.current.initialize_order_handling!
153
+
154
+ end ## module IB
155
+
@@ -0,0 +1,86 @@
1
+ module IB
2
+ module RollFuture
3
+ # helper method to roll an existing future
4
+ #
5
+ # Argument is the expiry of the target-future or the distance
6
+ #
7
+ # > nq = IB::Symbols::Futures.nq.verify.first
8
+ # > t= nq.roll to: '3m'
9
+ # > puts t.as_table
10
+ # ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
11
+ # │ Roll NQ future from Sep 24 to Dec 24 / buy 1 <Future: NQ 20240920 USD> / sell 1 <Future: NQ 20241220 USD │
12
+ # ├────────┬────────┬─────────────┬──────────┬──────────┬────────────┬───────────────┬───────┬────────┬──────────┤
13
+ # │ │ symbol │ con_id │ exchange │ expiry │ multiplier │ trading-class │ right │ strike │ currency │
14
+ # ╞════════╪════════╪═════════════╪══════════╪══════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
15
+ # │ Spread │ NQ │ -1201481183 │ CME │ │ 20 │ │ │ │ USD │
16
+ # │ Future │ NQ │ 637533450 │ CME │ 20240920 │ 20 │ NQ │ │ │ USD │
17
+ # │ Future │ NQ │ 563947733 │ CME │ 20241220 │ 20 │ NQ │ │ │ USD │
18
+ # └────────┴────────┴─────────────┴──────────┴──────────┴────────────┴───────────────┴───────┴────────┴──────────┘
19
+ # > t= nq.roll expiry: 202412
20
+ # > puts t.to_human
21
+ # <Roll NQ future from Sep 24 to Dec 24 / buy 1 <Future: NQ 20240920 USD> / sell 1 <Future: NQ 20241220 USD>
22
+
23
+
24
+ def roll **args
25
+ print_expiry = ->(f){ Date.parse(f.last_trading_day).strftime('%b %y') }
26
+ error "specify expiry to roll a future" if args.empty?
27
+ args[:to] = args[:expiry] if args[:expiry].present? && args[:expiry].to_s =~ /[mwMW]$/
28
+ args[:expiry]= IB::Spread.transform_distance( expiry, args.delete(:to )) if args[:to].present?
29
+
30
+ new_future = merge( **args ).verify.first
31
+ error "Cannot roll future; target is no IB::Contract" unless new_future.is_a? IB::Future
32
+ target = IB::Spread.new exchange: exchange, symbol: symbol, currency: currency,
33
+ description: "<Roll #{symbol} future from #{print_expiry[self]} to #{print_expiry[new_future]}"
34
+ target.add_leg self, action: :sell
35
+ target.add_leg new_future, action: :buy
36
+ end
37
+ end
38
+
39
+
40
+ module RollOption
41
+ # helper method to roll an existing short-poption
42
+ #
43
+ # Arguments are strike and expiry of the target-option.
44
+ #
45
+ # Example: r= Symbols::Options.rut.merge(strike: 2000).next_expiry.roll( strike: 1900 )
46
+ # r.to_human
47
+ # => " rolling <Option: RUT 20240516 put 2000.0 SMART USD> to <Option: RUT 20240516 put 1900.0 SMART USD>"
48
+ # r.combo_legs.to_human
49
+ # => ["<ComboLeg: buy 1 con_id 684936898 at SMART>", "<ComboLeg: sell 1 con_id 684936524 at SMART>"]
50
+ #
51
+ # rolls the Option to another strike and/or expiry
52
+ #
53
+ # Same Expiry, roll down the strike
54
+ # `r= Symbols::Options.rut.merge(strike: 2000).next_expiry.roll( strike: 1900 ) `
55
+ #
56
+ # Same Expiry, roll to the next month
57
+ # `r= Symbols::Options.rut.merge(strike: 2000).next_expiry.roll( expiry: '+1m' ) `
58
+
59
+ def roll **args
60
+ error "specify strike and expiry to roll option" if args.empty?
61
+ args[:to] = args[:expiry] if args[:expiry].present? && args[:expiry].to_s =~ /[mwMW]$/
62
+ args[:expiry]= IB::Spread.transform_distance( expiry, args.delete(:to )) if args[:to].present?
63
+
64
+ new_option = merge( ** args ).then{ | y | y.next_expiry{ y.expiry } }
65
+
66
+ myself = con_id.to_i.zero? ? self.verify.first : self
67
+ error "Cannot roll option; target is no IB::Contract" unless new_option.is_a? IB::Option
68
+ error "Cannot roll option; Option cannot be verified" unless myself.is_a? IB::Option
69
+ target = IB::Spread.new exchange: exchange, symbol: symbol, currency: currency
70
+ target.add_leg myself, action: :buy
71
+ target.add_leg new_option, action: :sell
72
+ target.description= target.description.sub(/added <Option:/, 'rolling <Option:').then{|y| y.gsub /added <Option/, 'to <Option'}
73
+ target
74
+ end
75
+ end
76
+
77
+ Connection.current.activate_plugin 'verify'
78
+
79
+ class Future
80
+ include RollFuture
81
+ end
82
+
83
+ class Option
84
+ include RollOption
85
+ end
86
+ end
@@ -0,0 +1,77 @@
1
+ module IB
2
+
3
+ module Butterfly
4
+
5
+ extend SpreadPrototype
6
+ class << self
7
+
8
+ # Fabricate a Butterfly from Scratch
9
+ # -----------------------------------------
10
+ #
11
+ #
12
+ #
13
+ # Call with
14
+ # IB::Butterfly.fabricate IB::Option.new( symbol: :estx50, strike: 3000, expiry:'201901'),
15
+ # front: 2850, back: 3150
16
+ #
17
+ # or
18
+ # IB::Butterfly.build from: Symbols::Index.stoxx
19
+ # strike: 3000
20
+ # expiry: '201901', front: 2850, back: 3150
21
+ #
22
+ # where :strike defines the center of the Spread.
23
+ def fabricate master, front:, back:
24
+
25
+ error "fabrication is based on a master option. Please specify as first argument" unless master.is_a?(IB::Option)
26
+ strike = master.strike
27
+ master.right = :put unless master.right == :call
28
+ l= master.verify
29
+ if l.empty?
30
+ error "Invalid Parameters. No Contract found #{master.to_human}"
31
+ elsif l.size > 1
32
+ error "ambigous contract-specification: #{l.map(&:to_human).join(';')}"
33
+ available_trading_classes = l.map( &:trading_class ).uniq
34
+ if available_trading_classes.size >1
35
+ error "Refine Specification with trading_class: #{available_trading_classes.join('; ')} "
36
+ else
37
+ error "Respecify expiry, verification reveals #{l.size} contracts (only 1 is allowed)"
38
+ end
39
+ end
40
+
41
+ initialize_spread( master ) do | the_spread |
42
+ strikes = [front, master.strike, back]
43
+ strikes.zip([1, -2, 1]).each do |strike, ratio|
44
+ action = ratio >0 ? :buy : :sell
45
+ leg = IB::Option.new( master.attributes.merge( strike: strike )).verify.first.essential
46
+ the_spread.add_leg leg, action: action, ratio: ratio.abs
47
+ end
48
+ the_spread.description = the_description( the_spread )
49
+ the_spread.symbol = master.symbol
50
+ end
51
+ end
52
+
53
+ def build from: , front:, back:, **options
54
+ underlying_attributes = { expiry: IB::Future.next_expiry, right: :put }.merge( from.attributes.slice( :symbol, :currency, :exchange, :strike )).merge( options )
55
+ fabricate IB::Option.new( underlying_attributes), front: front, back: back
56
+ end
57
+
58
+ def the_description spread
59
+ x= [ spread.combo_legs.map(&:weight) , spread.legs.map( &:strike )].transpose
60
+ "<Butterfly #{spread.symbol} #{spread.legs.first.right}(#{x.map{|w,strike| "#{w} :#{strike} "}.join( '|+|' )} )[#{Date.parse(spread.legs.first.last_trading_day).strftime("%b %Y")}]>"
61
+ end
62
+
63
+ def defaults
64
+ super.merge expiry: IB::Future.next_expiry,
65
+ right: :put
66
+ end
67
+
68
+
69
+ def requirements
70
+ super.merge back: "the strike of the lower bougth option",
71
+ front: "the strike of the upper bougth option"
72
+
73
+ end
74
+
75
+ end # class
76
+ end # module
77
+ end # module ib
@@ -0,0 +1,97 @@
1
+ module IB
2
+
3
+ module Calendar
4
+
5
+ extend SpreadPrototype
6
+ class << self
7
+
8
+
9
+ # Fabricate a Calendar-Spread from a Master-Option
10
+ # -----------------------------------------
11
+ # If one Leg is known, the other is build through replacing the expiry
12
+ # The second leg is always SOLD !
13
+ #
14
+ # Call with
15
+ # IB::Calendar.fabricate an_option, the_other_expiry
16
+ def fabricate master, the_other_expiry
17
+
18
+ error "Argument must be a IB::Future or IB::Option" unless [:option, :future_option, :future ].include? master.sec_type
19
+ m = master.verify.first
20
+ error "Argument is a #{master.class}, but Verification failed" unless m.is_a? IB::Contract
21
+ the_other_expiry = the_other_expiry.values.first if the_other_expiry.is_a?(Hash)
22
+ back = IB::Spread.transform_distance m.expiry, the_other_expiry
23
+ the_other_contract = m.merge( expiry: back ).verify.first
24
+ error "Verification of second leg failed" unless the_other_contract.is_a? IB::Contract
25
+ target = IB::Spread.new exchange: m.exchange, symbol: m.symbol, currency: m.currency
26
+ target.add_leg m, action: :buy
27
+ target.add_leg the_other_contract, action: :sell
28
+
29
+ # calendar = m.roll expiry: back
30
+ error "Initialisation of Legs failed" if target.legs.size != 2
31
+ target.description = the_description( target )
32
+ target # return fabricated spread
33
+ end
34
+
35
+
36
+ # Build Vertical out of an Underlying
37
+ # -----------------------------------------
38
+ # Needed attributes: :strike, :expiry( front: expiry1, back: expiry2 ), right
39
+ #
40
+ # Optional: :trading_class, :multiplier
41
+ #
42
+ # Call with
43
+ # IB::Calendar.build from: IB::Contract, front: an_expiry, back: an_expiry,
44
+ # right: {put or call}, strike: a_strike
45
+ def build from:, front: nil, back: nil, right: :put, strike: nil, **fields
46
+ underlying = if from.is_a? IB::Option
47
+ right ||= from.right
48
+ front ||= from.expiry
49
+ strike ||= from.strike
50
+ details = from.verify.first.contract_detail
51
+ IB::Contract.new( con_id: details.under_con_id,
52
+ currency: from.currency).verify.first.essential
53
+ else
54
+ error "missing essential parameter: `strike`" unless strike.present?
55
+ from
56
+ end
57
+ error "`front:` and `back:` expiries are required" unless front.present? && back.present?
58
+ kind = { :front => front, :back => back }
59
+ initialize_spread( underlying ) do | the_spread |
60
+ leg_prototype = IB::Option.new underlying.invariant_attributes.except( :sec_type )
61
+ .slice( :currency, :symbol, :exchange )
62
+ .merge(defaults)
63
+ .merge( fields )
64
+ .merge( strike: strike )
65
+ kind[:back] = IB::Spread.transform_distance front, back
66
+ leg_prototype.sec_type = 'FOP' if underlying.is_a?(IB::Future)
67
+ leg1 = leg_prototype.merge( expiry: kind[:front] ).verify.first
68
+ leg2 = leg_prototype.merge( expiry: kind[:back] ).verify.first
69
+ unless leg2.is_a? IB::Option
70
+ leg2_trading_class = ''
71
+ leg2 = leg_prototype.merge( expiry: kind[:back] ).verify.first
72
+ end
73
+ the_spread.add_leg leg1 , action: :buy
74
+ the_spread.add_leg leg2 , action: :sell
75
+ error "Initialisation of Legs failed" if the_spread.legs.size != 2
76
+ the_spread.description = the_description( the_spread ) rescue nil
77
+ end
78
+ end
79
+
80
+ def defaults
81
+ super.merge right: :put
82
+ # expiry: IB::Future.next_expiry,
83
+ end
84
+
85
+
86
+ def the_description spread
87
+ x= [ spread.combo_legs.map(&:weight) , spread.legs.map( &:last_trading_day )].transpose
88
+ f_or_o = if spread.legs.first.is_a?(IB::Future)
89
+ "Future"
90
+ else
91
+ "#{spread.legs.first.right}(#{spread.legs.first.strike})"
92
+ end
93
+ "<Calendar #{spread.symbol} #{f_or_o} [#{x.map{|w,l_t_d| "#{w}:#{Date.parse(l_t_d).strftime("%b %Y")}"}.join( '|+|' )}]>"
94
+ end
95
+ end # class
96
+ end # module vertical
97
+ end # module ib
@@ -0,0 +1,56 @@
1
+ module IB
2
+
3
+ module StockSpread
4
+ extend SpreadPrototype
5
+ class << self
6
+
7
+ # Fabricate a StockSpread from Scratch
8
+ # -----------------------------------------
9
+ #
10
+ #
11
+ #
12
+ # Call with
13
+ # IB::StockSpread.fabricate 'GE','F', ratio:[1,-2]
14
+ #
15
+ # or
16
+ # IB::StockSpread.fabricate IB::Stock.new(symbol:'GE'), 'F', ratio:[1,-2]
17
+ #
18
+ #
19
+ #
20
+ def fabricate *underlying, ratio: [1,-1], **args
21
+ #
22
+ are_stocks = ->(l){ l.all?{|y| y.is_a? IB::Stock} }
23
+ legs = underlying.map do | the_stock |
24
+ if the_stock.is_a? IB::Stock
25
+ the_stock
26
+ else
27
+ IB::Stock.new symbol: the_stock
28
+ end.merge( **args ).verify.first
29
+ end
30
+
31
+ error "only spreads with two underyings of type »IB::Stock« are supported" unless legs.size==2 && are_stocks[legs]
32
+
33
+ initialize_spread( legs.first ) do | the_spread |
34
+ c_l = legs.zip(ratio).map do |l,r|
35
+ action = r >0 ? :buy : :sell
36
+ the_spread.add_leg l, action: action, ratio: r.abs
37
+ end
38
+ the_spread.description = the_description( the_spread )
39
+ the_spread.symbol = legs.map( &:symbol ).sort.join(",") # alphabetical order
40
+ the_spread.combo_params = {'NonGuaranteed' => true}
41
+ end
42
+ end
43
+
44
+ def the_description spread
45
+ info= spread.legs.map( &:symbol ).zip(spread.combo_legs.map( &:weight ))
46
+ "<StockSpread #{info.map{|c| c.join(":")}.join(" , ")} (#{spread.currency} )>"
47
+
48
+ end
49
+
50
+ # always route a order as NonGuaranteed
51
+ def order_requirements
52
+ end
53
+
54
+ end # class
55
+ end # module
56
+ end # module ib
@@ -0,0 +1,70 @@
1
+ module IB
2
+ module Straddle
3
+ extend SpreadPrototype
4
+ class << self
5
+
6
+
7
+ # Fabricate a Straddle from a Master-Option
8
+ # -----------------------------------------
9
+ # If one Leg is known, the other is simply build by flipping the right
10
+ #
11
+ # Call with
12
+ # IB::Spread::Straddle.fabricate an_option
13
+ def fabricate master
14
+
15
+ flip_right = ->(the_right){ the_right == :put ? :call : :put }
16
+ error "Argument must be a IB::Option" unless [ :option, :futures_option ].include?( master.sec_type )
17
+
18
+ initialize_spread( master ) do | the_spread |
19
+ the_spread.add_leg master.essential.verify.first
20
+ the_spread.add_leg( master.essential.merge( right: flip_right[master.right], local_symbol: "").verify.first )
21
+ error "Initialisation of Legs failed" if the_spread.legs.size != 2
22
+ the_spread.description = the_description( the_spread )
23
+ end
24
+ end
25
+
26
+ # Build Straddle out of an Underlying
27
+ # -----------------------------------------
28
+ # Needed attributes: :strike, :expiry
29
+ #
30
+ # Optional: :trading_class, :multiplier
31
+ #
32
+ # Call with
33
+ # IB::Spread::Straddle.build from: IB::Contract, strike: a_value, expiry: yyyymmm(dd)
34
+ def build from:, ** fields
35
+ if from.is_a? IB::Option
36
+ fabricate from.merge **fields
37
+ else
38
+ initialize_spread( from ) do | the_spread |
39
+ leg_prototype = IB::Option.new from.attributes
40
+ .slice( :currency, :symbol, :exchange, :expiry) # use only these fields
41
+ .merge(defaults) # add defaults
42
+ .merge( fields ) # override attributes with parameters
43
+ # puts leg_prototype.attributes
44
+
45
+ leg_prototype.sec_type = 'FOP' if from.is_a?( IB::Future )
46
+ the_spread.add_leg leg_prototype.merge( right: :put ).verify.first
47
+ the_spread.add_leg leg_prototype.merge( right: :call ).verify.first
48
+ error "Initialisation of Legs failed" if the_spread.legs.size != 2
49
+ the_spread.description = the_description( the_spread )
50
+ end
51
+ end
52
+ end
53
+
54
+ def defaults
55
+ super.merge expiry: IB::Option.next_expiry
56
+ end
57
+
58
+ def requirements
59
+ super.merge strike: "the strike of both options",
60
+ expiry: "Expiry expressed as »yyyymm(dd)« (String or Integer)"
61
+ end
62
+
63
+ def the_description spread
64
+ my_strike = spread.legs.first.strike
65
+ "<Straddle #{spread.symbol}(#{my_strike})[#{Date.parse(spread.legs.first.expiry).strftime("%b %Y")}]>"
66
+ end
67
+
68
+ end # class
69
+ end # module combo
70
+ end # module ib