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.
- checksums.yaml +7 -0
- data/.gitignore +52 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CLAUDE.md +131 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +120 -0
- data/Guardfile +24 -0
- data/LICENSE +674 -0
- data/LLM_GUIDE.md +388 -0
- data/README.md +114 -0
- data/Rakefile +11 -0
- data/VERSION +1 -0
- data/api.gemspec +50 -0
- data/bin/console +96 -0
- data/bin/console.yml +3 -0
- data/bin/setup +8 -0
- data/bin/simple +91 -0
- data/changelog.md +32 -0
- data/conditions/ib/execution_condition.rb +31 -0
- data/conditions/ib/margin_condition.rb +28 -0
- data/conditions/ib/order_condition.rb +29 -0
- data/conditions/ib/percent_change_condition.rb +34 -0
- data/conditions/ib/price_condition.rb +44 -0
- data/conditions/ib/time_condition.rb +42 -0
- data/conditions/ib/volume_condition.rb +36 -0
- data/lib/class_extensions.rb +167 -0
- data/lib/ib/base.rb +109 -0
- data/lib/ib/base_properties.rb +178 -0
- data/lib/ib/connection.rb +573 -0
- data/lib/ib/constants.rb +402 -0
- data/lib/ib/contract.rb +30 -0
- data/lib/ib/errors.rb +52 -0
- data/lib/ib/messages/abstract_message.rb +68 -0
- data/lib/ib/messages/incoming/abstract_message.rb +116 -0
- data/lib/ib/messages/incoming/abstract_tick.rb +25 -0
- data/lib/ib/messages/incoming/account_message.rb +26 -0
- data/lib/ib/messages/incoming/alert.rb +34 -0
- data/lib/ib/messages/incoming/contract_data.rb +105 -0
- data/lib/ib/messages/incoming/contract_message.rb +13 -0
- data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
- data/lib/ib/messages/incoming/execution_data.rb +50 -0
- data/lib/ib/messages/incoming/histogram_data.rb +30 -0
- data/lib/ib/messages/incoming/historical_data.rb +65 -0
- data/lib/ib/messages/incoming/historical_data_update.rb +50 -0
- data/lib/ib/messages/incoming/managed_accounts.rb +21 -0
- data/lib/ib/messages/incoming/market_depth.rb +34 -0
- data/lib/ib/messages/incoming/market_depth_l2.rb +15 -0
- data/lib/ib/messages/incoming/next_valid_id.rb +19 -0
- data/lib/ib/messages/incoming/open_order.rb +290 -0
- data/lib/ib/messages/incoming/order_status.rb +85 -0
- data/lib/ib/messages/incoming/portfolio_value.rb +47 -0
- data/lib/ib/messages/incoming/position_data.rb +21 -0
- data/lib/ib/messages/incoming/positions_multi.rb +15 -0
- data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
- data/lib/ib/messages/incoming/receive_fa.rb +30 -0
- data/lib/ib/messages/incoming/scanner_data.rb +54 -0
- data/lib/ib/messages/incoming/tick_by_tick.rb +77 -0
- data/lib/ib/messages/incoming/tick_efp.rb +18 -0
- data/lib/ib/messages/incoming/tick_generic.rb +12 -0
- data/lib/ib/messages/incoming/tick_option.rb +60 -0
- data/lib/ib/messages/incoming/tick_price.rb +60 -0
- data/lib/ib/messages/incoming/tick_size.rb +55 -0
- data/lib/ib/messages/incoming/tick_string.rb +13 -0
- data/lib/ib/messages/incoming.rb +292 -0
- data/lib/ib/messages/outgoing/abstract_message.rb +84 -0
- data/lib/ib/messages/outgoing/bar_request_message.rb +247 -0
- data/lib/ib/messages/outgoing/new-place-order.rb +193 -0
- data/lib/ib/messages/outgoing/old-place-order.rb +147 -0
- data/lib/ib/messages/outgoing/place_order.rb +149 -0
- data/lib/ib/messages/outgoing/request_account_summary.rb +79 -0
- data/lib/ib/messages/outgoing/request_historical_data.rb +182 -0
- data/lib/ib/messages/outgoing/request_market_data.rb +102 -0
- data/lib/ib/messages/outgoing/request_market_depth.rb +57 -0
- data/lib/ib/messages/outgoing/request_real_time_bars.rb +48 -0
- data/lib/ib/messages/outgoing/request_scanner_subscription.rb +73 -0
- data/lib/ib/messages/outgoing/request_tick_by_tick_data.rb +21 -0
- data/lib/ib/messages/outgoing.rb +410 -0
- data/lib/ib/messages.rb +139 -0
- data/lib/ib/order_condition.rb +26 -0
- data/lib/ib/plugins.rb +27 -0
- data/lib/ib/prepare_data.rb +61 -0
- data/lib/ib/raw_message_parser.rb +99 -0
- data/lib/ib/socket.rb +83 -0
- data/lib/ib/support.rb +236 -0
- data/lib/ib/version.rb +6 -0
- data/lib/ib-api.rb +44 -0
- data/lib/server_versions.rb +145 -0
- data/lib/support/array_function.rb +28 -0
- data/lib/support/logging.rb +45 -0
- data/models/ib/account.rb +72 -0
- data/models/ib/account_value.rb +33 -0
- data/models/ib/bag.rb +55 -0
- data/models/ib/bar.rb +31 -0
- data/models/ib/combo_leg.rb +127 -0
- data/models/ib/contract.rb +411 -0
- data/models/ib/contract_detail.rb +118 -0
- data/models/ib/execution.rb +67 -0
- data/models/ib/forex.rb +12 -0
- data/models/ib/future.rb +64 -0
- data/models/ib/index.rb +14 -0
- data/models/ib/option.rb +149 -0
- data/models/ib/option_detail.rb +84 -0
- data/models/ib/order.rb +720 -0
- data/models/ib/order_state.rb +155 -0
- data/models/ib/portfolio_value.rb +86 -0
- data/models/ib/spread.rb +176 -0
- data/models/ib/stock.rb +25 -0
- data/models/ib/underlying.rb +32 -0
- data/plugins/ib/advanced-account.rb +442 -0
- data/plugins/ib/alerts/base-alert.rb +125 -0
- data/plugins/ib/alerts/gateway-alerts.rb +15 -0
- data/plugins/ib/alerts/order-alerts.rb +73 -0
- data/plugins/ib/auto-adjust.rb +0 -0
- data/plugins/ib/connection-tools.rb +122 -0
- data/plugins/ib/eod.rb +326 -0
- data/plugins/ib/greeks.rb +102 -0
- data/plugins/ib/managed-accounts.rb +274 -0
- data/plugins/ib/market-price.rb +150 -0
- data/plugins/ib/option-chain.rb +167 -0
- data/plugins/ib/order-flow.rb +157 -0
- data/plugins/ib/order-prototypes/abstract.rb +67 -0
- data/plugins/ib/order-prototypes/adaptive.rb +40 -0
- data/plugins/ib/order-prototypes/all-in-one.rb +46 -0
- data/plugins/ib/order-prototypes/combo.rb +46 -0
- data/plugins/ib/order-prototypes/forex.rb +40 -0
- data/plugins/ib/order-prototypes/limit.rb +193 -0
- data/plugins/ib/order-prototypes/market.rb +116 -0
- data/plugins/ib/order-prototypes/pegged.rb +169 -0
- data/plugins/ib/order-prototypes/premarket.rb +31 -0
- data/plugins/ib/order-prototypes/stop.rb +202 -0
- data/plugins/ib/order-prototypes/volatility.rb +39 -0
- data/plugins/ib/order-prototypes.rb +118 -0
- data/plugins/ib/probability-of-expiring.rb +109 -0
- data/plugins/ib/process-orders.rb +155 -0
- data/plugins/ib/roll.rb +86 -0
- data/plugins/ib/spread-prototypes/butterfly.rb +77 -0
- data/plugins/ib/spread-prototypes/calendar.rb +97 -0
- data/plugins/ib/spread-prototypes/stock-spread.rb +56 -0
- data/plugins/ib/spread-prototypes/straddle.rb +70 -0
- data/plugins/ib/spread-prototypes/strangle.rb +93 -0
- data/plugins/ib/spread-prototypes/vertical.rb +83 -0
- data/plugins/ib/spread-prototypes.rb +70 -0
- data/plugins/ib/symbols/abstract.rb +136 -0
- data/plugins/ib/symbols/bonds.rb +28 -0
- data/plugins/ib/symbols/cfd.rb +19 -0
- data/plugins/ib/symbols/combo.rb +46 -0
- data/plugins/ib/symbols/commodity.rb +17 -0
- data/plugins/ib/symbols/forex.rb +41 -0
- data/plugins/ib/symbols/futures.rb +127 -0
- data/plugins/ib/symbols/index.rb +43 -0
- data/plugins/ib/symbols/options.rb +99 -0
- data/plugins/ib/symbols/stocks.rb +44 -0
- data/plugins/ib/symbols/version.rb +5 -0
- data/plugins/ib/symbols.rb +118 -0
- data/plugins/ib/verify.rb +226 -0
- data/symbols/w20.yml +210 -0
- data/t.txt +20 -0
- data/update.md +71 -0
- 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
|