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,122 @@
1
+ module IB
2
+
3
+ =begin
4
+ Plugin for advanced Connections
5
+
6
+ Public API
7
+ ==========
8
+
9
+ Extends IB::Connection
10
+
11
+ Provides
12
+ * IB::Connection.current.check_connection
13
+ * IB::Connection.current.safe_connect
14
+ * IB::Connection.reconect
15
+
16
+
17
+ =end
18
+
19
+ module ConnectionTools
20
+ # Handy method to ensure that a connection is established and active.
21
+ #
22
+ # The connection is reset on the IB-side at least once a day. Then the
23
+ # IB-Ruby-Connection has to be reestablished, too.
24
+ #
25
+ # check_connection reconnects if necessary and returns false if the connection is lost.
26
+ #
27
+ # Individial subscriptions have to be placed **after** checking the connection!
28
+ #
29
+ # It delays the process by 6 ms (500 MBit Cable connection, loc. Europe)
30
+ #
31
+ # a = Time.now; IB::Connection.current.check_connection; b= Time.now ;b-a
32
+ # => 0.00066005
33
+ #
34
+ def check_connection
35
+ q = Queue.new
36
+ count = 0
37
+ result = nil
38
+ z= subscribe( :CurrentTime ) { q.push true }
39
+ loop do
40
+ begin
41
+ send_message(:RequestCurrentTime) # 10 ms ##
42
+ th = Thread.new{ sleep 0.1 ; q.push nil }
43
+ result = q.pop
44
+ count+=1
45
+ break if result || count > 10
46
+ rescue IOError, Errno::ECONNREFUSED # connection lost
47
+ count +=1
48
+ retry
49
+ rescue IB::Error # not connected
50
+ logger.info{"not connected ... trying to reconnect "}
51
+ reconnect
52
+ z= subscribe( :CurrentTime ) { q.push true }
53
+ count = 0
54
+ retry
55
+ rescue Workflow::NoTransitionAllowed
56
+ logger.warn{ "Reconnect is not possible, actual state: #{workflow_state} cannot be reached after disconnection"}
57
+ raise
58
+ end
59
+ end
60
+ unsubscribe z
61
+ result # return value
62
+ end
63
+
64
+ #
65
+ # Tries to connect to the api. If the connection could not be established, waits
66
+ # 10 sec. or one minute and reconnects.
67
+ #
68
+ # Unsuccessful connecting attemps are logged.
69
+ #
70
+ #
71
+ protected
72
+ def try_connection maximal_count_of_retry=100
73
+
74
+ i= -1
75
+ begin
76
+ _try_connection
77
+ rescue Errno::ECONNREFUSED => e
78
+ i+=1
79
+ if i < maximal_count_of_retry
80
+ if i.zero?
81
+ logger.info 'No TWS!'
82
+ else
83
+ logger.info {"No TWS Retry #{i}/ #{maximal_count_of_retry} " }
84
+ end
85
+ sleep i<50 ? 10 : 60 # Die ersten 50 Versuche im 10 Sekunden Abstand, danach 1 Min.
86
+ retry
87
+ else
88
+ logger.info { "Giving up!!" }
89
+ return false
90
+ end
91
+ rescue Errno::EHOSTUNREACH => e
92
+ error "Cannot connect to specified host #{e}", :reader, true
93
+ return false
94
+ rescue SocketError => e
95
+ error 'Wrong Adress, connection not possible', :reader, true
96
+ return false
97
+ rescue IB::Error => e
98
+ logger.info e
99
+ end
100
+ self # return connection
101
+ end # def
102
+
103
+ def submit_to_alert_1102
104
+ current.subscribe( :Alert ) do
105
+ if [2102, 1101].include? msg.id.to_i # Connectivity between IB and Trader Workstation
106
+ #has been restored - data maintained.
107
+ current.disconnect!
108
+ sleep 0.1
109
+ current.check_connection
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+
116
+ class Connection
117
+ alias _try_connection try_connection
118
+ include ConnectionTools
119
+ end
120
+
121
+
122
+ end
data/plugins/ib/eod.rb ADDED
@@ -0,0 +1,326 @@
1
+ module IB
2
+ require 'active_support/core_ext/date/calculations'
3
+ require 'csv'
4
+
5
+ =begin
6
+
7
+ Plugin to support EndOfDay OHLC-Data for a contract
8
+
9
+ Public API
10
+ ==========
11
+
12
+ Extends IB::Contract
13
+
14
+ * eod
15
+
16
+ * request EndOfDay historical data
17
+
18
+ * returns an Array of OHLC-EOD-Records or a Polars-Dataframe populated with OHLC-Records for the contract
19
+ and populates IB::Contract#bars
20
+
21
+
22
+ * get_bars
23
+
24
+ * request historical data for custom ohlc-timeframes,
25
+
26
+ * from_csv and to_csv
27
+
28
+ * store and retrieve ohlc-data
29
+
30
+
31
+ =end
32
+
33
+ module Eod
34
+ module BuisinesDays
35
+ # https://stackoverflow.com/questions/4027768/calculate-number-of-business-days-between-two-days
36
+
37
+ # Calculates the number of business days in range (start_date, end_date]
38
+ #
39
+ # @param start_date [Date]
40
+ # @param end_date [Date]
41
+ #
42
+ # @return [Fixnum]
43
+ def self.business_days_between(start_date, end_date)
44
+ days_between = (end_date - start_date).to_i
45
+ return 0 unless days_between > 0
46
+
47
+ # Assuming we need to calculate days from 9th to 25th, 10-23 are covered
48
+ # by whole weeks, and 24-25 are extra days.
49
+ #
50
+ # Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
51
+ # 1 2 3 4 5 # 1 2 3 4 5
52
+ # 6 7 8 9 10 11 12 # 6 7 8 9 ww ww ww
53
+ # 13 14 15 16 17 18 19 # ww ww ww ww ww ww ww
54
+ # 20 21 22 23 24 25 26 # ww ww ww ww ed ed 26
55
+ # 27 28 29 30 31 # 27 28 29 30 31
56
+ whole_weeks, extra_days = days_between.divmod(7)
57
+
58
+ unless extra_days.zero?
59
+ # Extra days start from the week day next to start_day,
60
+ # and end on end_date's week date. The position of the
61
+ # start date in a week can be either before (the left calendar)
62
+ # or after (the right one) the end date.
63
+ #
64
+ # Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
65
+ # 1 2 3 4 5 # 1 2 3 4 5
66
+ # 6 7 8 9 10 11 12 # 6 7 8 9 10 11 12
67
+ # ## ## ## ## 17 18 19 # 13 14 15 16 ## ## ##
68
+ # 20 21 22 23 24 25 26 # ## 21 22 23 24 25 26
69
+ # 27 28 29 30 31 # 27 28 29 30 31
70
+ #
71
+ # If some of the extra_days fall on a weekend, they need to be subtracted.
72
+ # In the first case only corner days can be days off,
73
+ # and in the second case there are indeed two such days.
74
+ extra_days -= if start_date.tomorrow.wday <= end_date.wday
75
+ [start_date.tomorrow.sunday?, end_date.saturday?].count(true)
76
+ else
77
+ 2
78
+ end
79
+ end
80
+
81
+ (whole_weeks * 5) + extra_days
82
+ end
83
+ end
84
+
85
+ # eod
86
+ #
87
+ # Receive EOD-Data and store the data in the `:bars`-property of IB::Contract
88
+ #
89
+ # contract.eod duration: {String or Integer}, start: {Date}, to: {Date}, what: {see below}, polars: {true|false}
90
+ #
91
+ #
92
+ #
93
+ # The Enddate has to be specified (as Date Object), `:to`, default: Date.today
94
+ #
95
+ # The Duration can either be a String "yx D", "yd W", "yx M" or an Integer ( implies "D").
96
+ # *notice* "W" fetchtes weekly and "M" monthly bars
97
+ #
98
+ # A start date can be given with the `:start` parameter.
99
+ #
100
+ # The parameter `:what` specifies the kind of received data.
101
+ #
102
+ # Valid values: ( /lib/ib/constants.rb --> DATA_TYPES )
103
+ # :trades, :midpoint, :bid, :ask, :bid_ask,
104
+ # :historical_volatility, :option_implied_volatility,
105
+ # :option_volume, :option_open_interest
106
+ #
107
+ # Polars DataFrames
108
+ # -----------------
109
+ # If »polars: true« is specified the response is stored as PolarsDataframe.
110
+ # For further processing: https://github.com/ankane/polars-ruby
111
+ # https://pola-rs.github.io/polars/py-polars/html/index.html
112
+ #
113
+ # Error-handling
114
+ # --------------
115
+ # * Basically all Errors simply lead to log-entries:
116
+ # * the contract is not valid,
117
+ # * no market data subscriptions
118
+ # * other servers-side errors
119
+ #
120
+ # If the duration is longer then the maximum range, the response is
121
+ # cut to the maximum allowed range
122
+ #
123
+ # Customize the result
124
+ # --------------------
125
+ # The results are stored in the `:bars` property of the contract
126
+ #
127
+ #
128
+ # Limitations
129
+ # -----------
130
+ # To identify a request, the con_id of the asset is used
131
+ # Thus, parallel requests of a single asset with different time-frames will fail
132
+ #
133
+ # Examples
134
+ # --------
135
+ #
136
+ # puts Stock.new( symbol: :iwm).eod( start: Date.new(2019,10,9), duration: 3, polars: true)
137
+ # shape: (3, 8)
138
+ # ┌────────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐
139
+ # │ time ┆ open ┆ high ┆ low ┆ close ┆ volume ┆ wap ┆ trades │
140
+ # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
141
+ # │ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i64 ┆ f64 ┆ i64 │
142
+ # ╞════════════╪════════╪════════╪════════╪════════╪════════╪═════════╪════════╡
143
+ # │ 2019-10-08 ┆ 148.62 ┆ 149.37 ┆ 146.11 ┆ 146.45 ┆ 156625 ┆ 146.831 ┆ 88252 │
144
+ # │ 2019-10-09 ┆ 147.18 ┆ 148.0 ┆ 145.38 ┆ 145.85 ┆ 94337 ┆ 147.201 ┆ 51294 │
145
+ # │ 2019-10-10 ┆ 146.9 ┆ 148.74 ┆ 146.87 ┆ 148.24 ┆ 134549 ┆ 147.792 ┆ 71084 │
146
+ # └────────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘
147
+ #
148
+ # puts Stock.new( symbol: :iwm).eod( start: Date.new(2021,10,9), duration: '3W', polars: true)
149
+ # shape: (3, 8)
150
+ # ┌────────────┬────────┬────────┬────────┬────────┬─────────┬─────────┬────────┐
151
+ # │ time ┆ open ┆ high ┆ low ┆ close ┆ volume ┆ wap ┆ trades │
152
+ # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
153
+ # │ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i64 ┆ f64 ┆ i64 │
154
+ # ╞════════════╪════════╪════════╪════════╪════════╪═════════╪═════════╪════════╡
155
+ # │ 2021-10-01 ┆ 223.99 ┆ 227.68 ┆ 216.12 ┆ 222.8 ┆ 1295495 ┆ 222.226 ┆ 792711 │
156
+ # │ 2021-10-08 ┆ 221.4 ┆ 224.95 ┆ 216.76 ┆ 221.65 ┆ 1044233 ┆ 220.855 ┆ 621984 │
157
+ # │ 2021-10-15 ┆ 220.69 ┆ 228.41 ┆ 218.94 ┆ 225.05 ┆ 768065 ┆ 223.626 ┆ 437817 │
158
+ # └────────────┴────────┴────────┴────────┴────────┴─────────┴─────────┴────────┘
159
+ #
160
+ # puts Stock.new( symbol: :iwm).eod( start: Date.new(2022,10,1), duration: '3M', polars: true)
161
+ # shape: (3, 8)
162
+ # ┌────────────┬────────┬────────┬────────┬────────┬─────────┬─────────┬─────────┐
163
+ # │ time ┆ open ┆ high ┆ low ┆ close ┆ volume ┆ wap ┆ trades │
164
+ # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
165
+ # │ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i64 ┆ f64 ┆ i64 │
166
+ # ╞════════════╪════════╪════════╪════════╪════════╪═════════╪═════════╪═════════╡
167
+ # │ 2022-09-30 ┆ 181.17 ┆ 191.37 ┆ 162.77 ┆ 165.16 ┆ 4298969 ┆ 175.37 ┆ 2202407 │
168
+ # │ 2022-10-31 ┆ 165.5 ┆ 184.24 ┆ 162.5 ┆ 183.5 ┆ 4740014 ┆ 173.369 ┆ 2474286 │
169
+ # │ 2022-11-30 ┆ 184.51 ┆ 189.56 ┆ 174.11 ┆ 188.19 ┆ 3793861 ┆ 182.594 ┆ 1945674 │
170
+ # └────────────┴────────┴────────┴────────┴────────┴─────────┴─────────┴─────────┘
171
+ #
172
+ # puts Stock.new( symbol: :iwm).eod( start: Date.new(2020,1,1), duration: '3M', what: :option_implied_vol, polars: true
173
+ # atility )
174
+ # shape: (3, 8)
175
+ # ┌────────────┬──────────┬──────────┬──────────┬──────────┬────────┬──────────┬────────┐
176
+ # │ time ┆ open ┆ high ┆ low ┆ close ┆ volume ┆ wap ┆ trades │
177
+ # │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
178
+ # │ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i64 ┆ f64 ┆ i64 │
179
+ # ╞════════════╪══════════╪══════════╪══════════╪══════════╪════════╪══════════╪════════╡
180
+ # │ 2019-12-31 ┆ 0.134933 ┆ 0.177794 ┆ 0.115884 ┆ 0.138108 ┆ 0 ┆ 0.178318 ┆ 0 │
181
+ # │ 2020-01-31 ┆ 0.139696 ┆ 0.190494 ┆ 0.120646 ┆ 0.185732 ┆ 0 ┆ 0.19097 ┆ 0 │
182
+ # │ 2020-02-28 ┆ 0.185732 ┆ 0.436549 ┆ 0.134933 ┆ 0.39845 ┆ 0 ┆ 0.435866 ┆ 0 │
183
+ # └────────────┴──────────┴──────────┴──────────┴──────────┴────────┴──────────┴────────┘
184
+ #
185
+ def eod start: nil, to: nil, duration: nil , what: :trades, polars: false
186
+
187
+ # error "EOD:: Start-Date (parameter: to) must be a Date-Object" unless to.is_a? Date
188
+ normalize_duration = ->(d) do
189
+ error "incompatible duration: #{d.class}" unless d.is_a?(Integer) || d.is_a?(String)
190
+ if d.is_a?(Integer) || !["D","M","W","Y"].include?( d[-1].upcase )
191
+ d.to_i.to_s + "D"
192
+ else
193
+ d.gsub(" ","")
194
+ end.insert(-2, " ")
195
+ end
196
+
197
+ get_end_date = -> do
198
+ d = normalize_duration.call(duration)
199
+ case d[-1]
200
+ when "D"
201
+ start + d.to_i - 1
202
+ when 'W'
203
+ Date.commercial( start.year, start.cweek + d.to_i - 1, 1)
204
+ when 'M'
205
+ Date.new( start.year, start.month + d.to_i - 1 , start.day )
206
+ end
207
+ end
208
+
209
+ if to.nil?
210
+ # case eod start= Date.new ...
211
+ to = if start.present? && duration.nil?
212
+ # case eod start= Date.new
213
+ duration = BuisinesDays.business_days_between(start, Date.today).to_s + "D"
214
+ Date.today # assign to var: to
215
+ elsif start.present? && duration.present?
216
+ # case eod start= Date.new , duration: 'nN'
217
+ get_end_date.call # assign to var: to
218
+ elsif duration.present?
219
+ # case start is not present, we are collecting until the present day
220
+ Date.today # assign to var: to
221
+ else
222
+ duration = "1D"
223
+ Date.today
224
+ end
225
+ end
226
+ if duration.nil?
227
+ duration = BuisinesDays.business_days_between(start, to)
228
+ end
229
+
230
+ barsize = case normalize_duration.call(duration)[-1].upcase
231
+ when "W"
232
+ :week1
233
+ when "M"
234
+ :month1
235
+ else
236
+ :day1
237
+ end
238
+
239
+
240
+ get_bars( to.to_ib(time_zone) , normalize_duration[duration], barsize, what, polars )
241
+
242
+ end # def
243
+
244
+ # creates (or overwrites) the specified file (or symbol.csv) and saves bar-data
245
+ def to_csv file: "#{symbol}.csv"
246
+ if bars.present?
247
+ headers = bars.first.invariant_attributes.keys
248
+ CSV.open( file, 'w' ) {|f| f << headers ; bars.each {|y| f << y.invariant_attributes.values } }
249
+ end
250
+ end
251
+
252
+ # read csv-data into bars
253
+ def from_csv file: nil
254
+ file ||= "#{symbol}.csv"
255
+ self.bars = []
256
+ CSV.foreach( file, headers: true, header_converters: :symbol) do |row|
257
+ self.bars << IB::Bar.new( **row.to_h )
258
+ end
259
+ end
260
+
261
+ # get_bars:: Helper method to fetch historical data
262
+ #
263
+ # parameter: end_date_time:: A string representing the last datum to fetch.
264
+ # Date.to_ib and Time.to_ib return the correct format
265
+ # duration:: A String "yx D", "yd W", "yx M"
266
+ # barsize:: A valid BAR_SIZES-entry (/lib/ib/constants.rb)
267
+ # what_to_show:: A valid DATA_TYPES-entry (/lib/ib/constants.rb)
268
+ # polars:: Flag to indicate if a polars-dataframe should be returned
269
+
270
+ def get_bars(end_date_time, duration, bar_size, what_to_show, polars)
271
+
272
+ tws = IB::Connection.current
273
+ received = Queue.new
274
+ r = nil
275
+ # the hole response is transmitted at once!
276
+ a = tws.subscribe(IB::Messages::Incoming::HistoricalData) do |msg|
277
+ if msg.request_id == con_id
278
+ self.bars = if polars
279
+ # msg.results.each { |entry| puts " #{entry}" }
280
+ Polars::DataFrame.new msg.results.map( &:invariant_attributes )
281
+ else
282
+ msg.results
283
+ end
284
+ end
285
+ received.push Time.now
286
+ end
287
+ b = tws.subscribe( IB::Messages::Incoming::Alert) do |msg|
288
+ if [321,162,200,354].include? msg.code
289
+ tws.logger.warn msg.message
290
+ # TWS Error 200: No security definition has been found for the request
291
+ # TWS Error 354: Requested market data is not subscribed.
292
+ # TWS Error 162: Historical Market Data Service error
293
+ # TWS Error 321: Error validating request.-'bK' : cause -
294
+ # Historical data requests for durations longer than 365 days must be made in years.
295
+
296
+ received.close
297
+ end
298
+ end
299
+
300
+
301
+ tws.send_message IB::Messages::Outgoing::RequestHistoricalData.new(
302
+ :request_id => con_id,
303
+ :contract => self,
304
+ :end_date_time => end_date_time,
305
+ :duration => duration, # see ib/messages/outgoing/bar_request.rb => max duration for 5sec bar lookback is 10 000 - i.e. will yield 2000 bars
306
+ :bar_size => bar_size, # IB::BAR_SIZES.key(:hour)
307
+ :what_to_show => what_to_show,
308
+ :use_rth => 0,
309
+ :format_date => 2,
310
+ :keep_up_todate => 0)
311
+
312
+ received.pop # blocks until a message is ready on the queue or the queue is closed
313
+
314
+ tws.unsubscribe a
315
+ tws.unsubscribe b
316
+
317
+ block_given? ? bars.map{|y| yield y} : bars # return bars or result of block
318
+
319
+ end # def
320
+ end # module eod
321
+
322
+ class Contract
323
+ include Eod
324
+ end # class
325
+ end # module IB
326
+
@@ -0,0 +1,102 @@
1
+ module IB
2
+ =begin
3
+
4
+ Plugin to fetch Option Greeks
5
+
6
+ Provides Option.request_greeks and
7
+ Option.greeks to display the fetched data
8
+
9
+ =end
10
+
11
+ module Greeks
12
+
13
+ # Ask for the Greeks and implied Vola
14
+ #
15
+ # The result can be customized by a provided block.
16
+ #
17
+ # IB::Symbols::Options.aapl.greeks{ |x| x }
18
+ # -> {"bid"=>0.10142e3, "ask"=>0.10144e3, "last"=>0.10142e3, "close"=>0.10172e3}
19
+ #
20
+ # Possible values for Parameter :what --> :all :model, :bid, :ask, :bidask, :last
21
+ #
22
+ def request_greeks delayed: true, what: :model, thread: false
23
+
24
+ tws = Connection.current # get the initialized ib-ruby instance
25
+ # define requested tick-attributes
26
+ request_data_type = IB::MARKET_DATA_TYPES.rassoc( delayed ? :frozen_delayed : :frozen ).first
27
+ # possible types = [ [ :delayed_model_option , :model_option ] , [:delayed_last_option , :last_option ],
28
+ # [ :delayed_bid_option , :bid_option ], [ :delayed_ask_option , :ask_option ]]
29
+ tws.send_message :RequestMarketDataType, :market_data_type => request_data_type
30
+ tickdata = []
31
+
32
+ self.greek = OptionDetail.new if greek.nil?
33
+ greek.updated_at = Time.now
34
+ greek.option = self
35
+ queue = Queue.new
36
+
37
+ #keep the method-call running until the request finished
38
+ #and cancel subscriptions to the message handler
39
+ # method returns the (running) thread
40
+ th = Thread.new do
41
+ the_id = nil
42
+ # subscribe to TickPrices
43
+ s_id = tws.subscribe(:TickSnapshotEnd) { |msg| queue.push(true) if msg.ticker_id == the_id }
44
+ e_id = tws.subscribe(:Alert){|x| queue.push(false) if [200,353].include?( x.code) && x.error_id == the_id }
45
+ t_id = tws.subscribe( :TickSnapshotEnd, :TickPrice, :TickString, :TickSize, :TickGeneric, :MarketDataType, :TickRequestParameters ) {|msg| msg }
46
+ # TWS Error 200: No security definition has been found for the request
47
+ # TWS Error 354: Requested market data is not subscribed.
48
+
49
+ sub_id = tws.subscribe(:TickOption ) do |msg| #, :TickSize, :TickGeneric do |msg|
50
+ if msg.ticker_id == the_id # && tickdata.is_a?(Array) # do nothing if tickdata have already gathered
51
+ case msg.type
52
+ when /ask/
53
+ greek.ask_price = msg.option_price unless msg.option_price.nil?
54
+ tickdata << msg if [ :all, :ask, :bidask ].include?( what )
55
+
56
+ when /bid/
57
+ greek.bid_price = msg.option_price unless msg.option_price.nil?
58
+ tickdata << msg if [ :all, :bid, :bidask ].include?( what )
59
+ when /last/
60
+ tickdata << msg if msg.type =~ /last/
61
+ when /model/
62
+ # transfer attributs from TickOption to OptionDetail
63
+ bf =[ :option_price, :implied_volatility, :under_price, :pv_dividend ]
64
+ (bf + msg.greeks.keys).each{ |a| greek.send( a.to_s+"=", msg.send( a)) }
65
+ tickdata << msg if [ :all, :model ].include?( what )
66
+ end
67
+ # fast entry abortion ---> daiabled for now
68
+ # queue.push(true) if tickdata.is_a?(IB::Messages::Incoming::TickOption) || (tickdata.size == 2 && what== :bidask) || (tickdata.size == 4 && what == :all)
69
+ end
70
+ end # if sub_id
71
+
72
+ # initialize »the_id« that is used to identify the received tick messages
73
+ # by firing the market data request
74
+ iji = 0
75
+ loop do
76
+ the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
77
+
78
+ result = queue.pop
79
+ # reduce :close_price delayed_close to close a.s.o
80
+ if result == false
81
+ Connection.logger.info{ "#{to_human} --> No Marketdata received " }
82
+ else
83
+ self.misc = tickdata if thread # store internally if in thread modus
84
+ end
85
+ break if !tickdata.empty? || iji > 10
86
+ iji = iji + 1
87
+ Connection.logger.info{ "OptionGreeks::#{to_human} --> delayed processing. Trying again (#{iji}) " }
88
+ end
89
+ tws.unsubscribe sub_id, s_id, e_id, t_id
90
+ end # thread
91
+ if thread
92
+ th # return thread
93
+ else
94
+ th.join
95
+ greek
96
+ end
97
+ end
98
+ end
99
+ class Option
100
+ include Greeks
101
+ end
102
+ end