ib-extensions 1.0

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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +6 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +9 -0
  7. data/Gemfile.lock +112 -0
  8. data/Guardfile +24 -0
  9. data/README.md +99 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +96 -0
  12. data/bin/console.yml +3 -0
  13. data/bin/gateway.rb +97 -0
  14. data/bin/setup +8 -0
  15. data/changelog.md +31 -0
  16. data/examples/cancel_orders +74 -0
  17. data/examples/eod +35 -0
  18. data/examples/input.rb +475 -0
  19. data/examples/market_price +57 -0
  20. data/examples/option_chain +67 -0
  21. data/examples/place_and_modify_order +162 -0
  22. data/examples/place_bracket_order +62 -0
  23. data/examples/place_butterfly_order +104 -0
  24. data/examples/place_combo_order +70 -0
  25. data/examples/place_limit_order +82 -0
  26. data/examples/place_the_limit_order +145 -0
  27. data/examples/volatility_research +139 -0
  28. data/examples/what_if_order +90 -0
  29. data/ib-extensions.gemspec +37 -0
  30. data/lib/ib-gateway.rb +5 -0
  31. data/lib/ib/alerts/base-alert.rb +128 -0
  32. data/lib/ib/alerts/gateway-alerts.rb +15 -0
  33. data/lib/ib/alerts/order-alerts.rb +68 -0
  34. data/lib/ib/eod.rb +152 -0
  35. data/lib/ib/extensions.rb +9 -0
  36. data/lib/ib/extensions/contract.rb +37 -0
  37. data/lib/ib/extensions/version.rb +5 -0
  38. data/lib/ib/flex.rb +150 -0
  39. data/lib/ib/gateway.rb +425 -0
  40. data/lib/ib/gateway/account-infos.rb +115 -0
  41. data/lib/ib/gateway/order-handling.rb +150 -0
  42. data/lib/ib/market-price.rb +134 -0
  43. data/lib/ib/models/account.rb +329 -0
  44. data/lib/ib/models/spread.rb +159 -0
  45. data/lib/ib/option-chain.rb +198 -0
  46. data/lib/ib/option-greeks.rb +88 -0
  47. data/lib/ib/order-prototypes.rb +110 -0
  48. data/lib/ib/order_prototypes/abstract.rb +67 -0
  49. data/lib/ib/order_prototypes/combo.rb +46 -0
  50. data/lib/ib/order_prototypes/forex.rb +40 -0
  51. data/lib/ib/order_prototypes/limit.rb +177 -0
  52. data/lib/ib/order_prototypes/market.rb +116 -0
  53. data/lib/ib/order_prototypes/pegged.rb +173 -0
  54. data/lib/ib/order_prototypes/premarket.rb +31 -0
  55. data/lib/ib/order_prototypes/stop.rb +202 -0
  56. data/lib/ib/order_prototypes/volatility.rb +39 -0
  57. data/lib/ib/spread-prototypes.rb +62 -0
  58. data/lib/ib/spread_prototypes/butterfly.rb +79 -0
  59. data/lib/ib/spread_prototypes/calendar.rb +85 -0
  60. data/lib/ib/spread_prototypes/stock-spread.rb +48 -0
  61. data/lib/ib/spread_prototypes/straddle.rb +75 -0
  62. data/lib/ib/spread_prototypes/strangle.rb +96 -0
  63. data/lib/ib/spread_prototypes/vertical.rb +84 -0
  64. data/lib/ib/verify.rb +226 -0
  65. metadata +206 -0
@@ -0,0 +1,37 @@
1
+ require_relative 'lib/ib/extensions/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "ib-extensions"
5
+ spec.version = IB::Extensions::VERSION
6
+ spec.authors = ["Hartmut Bischoff"]
7
+ spec.email = ["topofocus@gmail.com"]
8
+
9
+ spec.summary = %q{Part of IB-Ruby. Tools to to access the tws-api comfortably.}
10
+ spec.homepage = "https://ib-ruby.github.io/ib-doc/"
11
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
12
+
13
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.cm/ib-ruby/ib-extensions"
17
+ spec.metadata["changelog_uri"] = "https://github.cm/ib-ruby/ib-extensions/changelog.md"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+
29
+ spec.add_dependency "ox"
30
+ spec.add_development_dependency "bundler", "~> 2.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+ spec.add_development_dependency 'rspec-collection_matchers'
33
+ spec.add_development_dependency 'rspec-its'
34
+
35
+ spec.add_development_dependency "guard"
36
+ spec.add_development_dependency "guard-rspec"
37
+ end
@@ -0,0 +1,5 @@
1
+ require 'ib-api'
2
+ require 'ib/verify'
3
+ require 'ib/spread-prototypes'
4
+ require 'ib/order-prototypes'
5
+ require 'ib/gateway'
@@ -0,0 +1,128 @@
1
+ module IB
2
+ class Alert
3
+ =begin
4
+ The Singleton IB::Alert handles any response to IB:Messages::Incomming:Alert
5
+
6
+ Individual methods can be defined as well as methods responding to a group of error-codes.
7
+ The default-behavior is defined in the method_missing-method. This just logs the object at the debug level.
8
+
9
+ To use the IB::Alert facility, first a logger has to be assigned to the Class.
10
+ IB::Alert.logger = Logger.new(STDOUT)
11
+
12
+ Default-wrappers to completely ignore the error-message (ignore_alert)
13
+ and to log the object in a different log-level (log_alert_in [warn,info,error] ) are defined in base_alert
14
+ Just add
15
+ module IB
16
+ class Alert
17
+ log_alert_in_warn {list of codennumbers}
18
+ end
19
+ end
20
+ to your code
21
+
22
+
23
+ IB::Gateway calls the methods in response of subscribing to the :Alert signal by calling
24
+ IB::Alert.send("alert_#{msg.code}", msg )
25
+
26
+ To define a response to the code 134 ( Modify order failed) a method like
27
+ module IB
28
+ class Alert
29
+ def self.alert_134 msg
30
+ (your code)
31
+ end
32
+ end
33
+ end
34
+ has to be written.
35
+
36
+ Important: The class is accessed asynchronically. Be careful while raising interrupts.
37
+
38
+ =end
39
+
40
+ # acts as prototype for any generated method
41
+ #require 'active_support'
42
+
43
+ mattr_accessor :logger
44
+
45
+ def self.method_missing( method_id, msg , *args, &block )
46
+ if msg.is_a? IB::Messages::Incoming::Alert
47
+ logger.debug { msg.to_human }
48
+ else
49
+ logger.error { "Argument to IB::Alert is not a IB::Messages::Incoming::Alert" }
50
+ logger.error { "The object: #{msg.inspect} " }
51
+ end
52
+ rescue NoMethodError
53
+ unless logger.nil?
54
+ logger.error { "The Argument is not a valid IB::Messages:Incoming::Alert object"}
55
+ logger.error { "The object: #{msg.inspect} " }
56
+ else
57
+ puts "No Logging-Device specified"
58
+ puts "The object: #{msg.inspect} "
59
+ end
60
+ end
61
+
62
+
63
+
64
+ class << self
65
+
66
+ def ignore_alert *codes
67
+ codes.each do |n|
68
+ class_eval <<-EOD
69
+ def self.alert_#{n} msg
70
+ # even the log_debug entry is suppressed
71
+ end
72
+ EOD
73
+ end
74
+ end
75
+ def log_alert_in_info *codes
76
+ codes.each do |n|
77
+ class_eval <<-EOD
78
+ def self.alert_#{n} msg
79
+ logger.info { msg.to_human }
80
+ end
81
+ EOD
82
+ end
83
+ end
84
+ def log_alert_in_warn *codes
85
+ codes.each do |n|
86
+ class_eval <<-EOD
87
+ def self.alert_#{n} msg
88
+ logger.warn { msg.to_human }
89
+ end
90
+ EOD
91
+ end
92
+ end
93
+
94
+ def log_alert_in_error *codes
95
+ codes.each do |n|
96
+ class_eval <<-EOD
97
+ def self.alert_#{n} msg
98
+ if msg.error_id.present? && msg.error_id > 0
99
+ logger.error { msg.message + ' id: ' + msg.error_id.to_s }
100
+ else
101
+ logger.error { msg.message }
102
+ end
103
+ end
104
+ EOD
105
+ end
106
+ end
107
+ end
108
+
109
+ ignore_alert 200 , # is handled by IB::Contract.update_contract
110
+ 2100, # API client has been unsubscribed from account data
111
+ 2105,
112
+ 399 # your order will not be placed at the exchange until
113
+
114
+ log_alert_in_info 1102 #Connectivity between IB and Trader Workstation has been restored
115
+
116
+
117
+ log_alert_in_error 320, 321, 323, 324, #ServerError
118
+ ## 110, # The price does not conform to the minimum price variation
119
+ # 103, #duplicate order ## order-alerts
120
+ # 201, #deleted objecta ## order-alerts
121
+ 326 #Unable connect as the client id is already in use
122
+
123
+ log_alert_in_warn 354 #Requested market data is not subscribed
124
+
125
+
126
+
127
+ end
128
+ end
@@ -0,0 +1,15 @@
1
+ # These Alerts are always active
2
+ module IB
3
+ class Alert
4
+
5
+ def self.alert_2102 msg
6
+ # Connectivity between IB and Trader Workstation has been restored - data maintained.
7
+ sleep 0.1 # no need to wait too long.
8
+ if IB::Gateway.current.check_connection
9
+ IB::Gateway.logger.debug { "Alert 2102: Connection stable" }
10
+ else
11
+ IB::Gateway.current.reconnect
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,68 @@
1
+ module IB
2
+ class Alert
3
+
4
+ def self.alert_202 msg
5
+ # do anything in a secure mutex-synchronized-environment
6
+ any_order = IB::Gateway.current.account_data do | account |
7
+ order= account.locate_order( local_id: msg.error_id )
8
+ if order.present? && ( order.order_state.status != 'Cancelled' )
9
+ order.order_states.update_or_create( IB::OrderState.new( status: 'Cancelled',
10
+ perm_id: order.perm_id,
11
+ local_id: order.local_id ) ,
12
+ :status )
13
+
14
+ end
15
+ order # return_value
16
+ end
17
+ if any_order.compact.empty?
18
+ IB::Gateway.logger.error{"Alert 202: The deleted order was not registered: local_id #{msg.error_id}"}
19
+ end
20
+
21
+ end
22
+
23
+
24
+ class << self
25
+ =begin
26
+ IB::Alert#AddOrderstateAlert
27
+
28
+ The OrderState-Record is used to record the history of the order.
29
+ If selected Alert-Messages appear, they are added to the Order.order_state-Array.
30
+ The last Status is available as Order.order_state, all states are accessible by Order.order_states
31
+
32
+ The TWS-Message-text is stored to the »warning-text«-field.
33
+ The Status is always »rejected«.
34
+ If the first OrderState-object of a Order is »rejected«, the order is not placed at all.
35
+ Otherwise only the last action is not applied and the order is unchanged.
36
+
37
+ =end
38
+ def add_orderstate_alert *codes
39
+ codes.each do |n|
40
+ class_eval <<-EOD
41
+ def self.alert_#{n} msg
42
+
43
+ if msg.error_id.present?
44
+ IB::Gateway.current.account_data do | account |
45
+ order= account.locate_order( local_id: msg.error_id )
46
+ if order.present? && ( order.order_state.status != 'Rejected' )
47
+ order.order_states.update_or_create( IB::OrderState.new( status: 'Rejected' ,
48
+ perm_id: order.perm_id,
49
+ warning_text: '#{n}: '+ msg.message,
50
+ local_id: msg.error_id ), :status )
51
+
52
+ IB::Gateway.logger.error{ msg.to_human }
53
+ end # order present?
54
+ end # mutex-environment
55
+ end # branch
56
+ end # def
57
+ EOD
58
+ end # loop
59
+ end # def
60
+ end
61
+ add_orderstate_alert 103, # duplicate order
62
+ 201, # deleted object
63
+ 105, # Order being modified does not match original order
64
+ 462, # Cannot change to the new Time in Force:GTD
65
+ 329, # Cannot change to the new order type:STP
66
+ 10147 # OrderId 0 that needs to be cancelled is not found.
67
+ end # class Alert
68
+ end # module IB
@@ -0,0 +1,152 @@
1
+ module IB
2
+ require 'active_support/core_ext/date/calculations'
3
+ module BuisinesDays
4
+ # https://stackoverflow.com/questions/4027768/calculate-number-of-business-days-between-two-days
5
+
6
+ # Calculates the number of business days in range (start_date, end_date]
7
+ #
8
+ # @param start_date [Date]
9
+ # @param end_date [Date]
10
+ #
11
+ # @return [Fixnum]
12
+ def self.business_days_between(start_date, end_date)
13
+ days_between = (end_date - start_date).to_i
14
+ return 0 unless days_between > 0
15
+
16
+ # Assuming we need to calculate days from 9th to 25th, 10-23 are covered
17
+ # by whole weeks, and 24-25 are extra days.
18
+ #
19
+ # Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
20
+ # 1 2 3 4 5 # 1 2 3 4 5
21
+ # 6 7 8 9 10 11 12 # 6 7 8 9 ww ww ww
22
+ # 13 14 15 16 17 18 19 # ww ww ww ww ww ww ww
23
+ # 20 21 22 23 24 25 26 # ww ww ww ww ed ed 26
24
+ # 27 28 29 30 31 # 27 28 29 30 31
25
+ whole_weeks, extra_days = days_between.divmod(7)
26
+
27
+ unless extra_days.zero?
28
+ # Extra days start from the week day next to start_day,
29
+ # and end on end_date's week date. The position of the
30
+ # start date in a week can be either before (the left calendar)
31
+ # or after (the right one) the end date.
32
+ #
33
+ # Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
34
+ # 1 2 3 4 5 # 1 2 3 4 5
35
+ # 6 7 8 9 10 11 12 # 6 7 8 9 10 11 12
36
+ # ## ## ## ## 17 18 19 # 13 14 15 16 ## ## ##
37
+ # 20 21 22 23 24 25 26 # ## 21 22 23 24 25 26
38
+ # 27 28 29 30 31 # 27 28 29 30 31
39
+ #
40
+ # If some of the extra_days fall on a weekend, they need to be subtracted.
41
+ # In the first case only corner days can be days off,
42
+ # and in the second case there are indeed two such days.
43
+ extra_days -= if start_date.tomorrow.wday <= end_date.wday
44
+ [start_date.tomorrow.sunday?, end_date.saturday?].count(true)
45
+ else
46
+ 2
47
+ end
48
+ end
49
+
50
+ (whole_weeks * 5) + extra_days
51
+ end
52
+ end
53
+ class Contract
54
+ # Receive EOD-Data
55
+ #
56
+ # The Enddate has to be specified (as Date Object), t
57
+ #
58
+ # The Duration can either be specified as Sting " yx D" or as Integer.
59
+ # Altenative a start date can be specified with the :start parameter.
60
+ #
61
+ # The parameter :what specified the kind of received data:
62
+ # Valid values:
63
+ # :trades, :midpoint, :bid, :ask, :bid_ask,
64
+ # :historical_volatility, :option_implied_volatility,
65
+ # :option_volume, :option_open_interest
66
+ #
67
+ # The results can be preprocessed through a block, thus
68
+ #
69
+ # puts IB::Symbols::Index::stoxx.eod( duration: '10 d')){|r| r.to_human}
70
+ # <Bar: 2019-04-01 wap 0.0 OHLC 3353.67 3390.98 3353.67 3385.38 trades 1750 vol 0>
71
+ # <Bar: 2019-04-02 wap 0.0 OHLC 3386.18 3402.77 3382.84 3395.7 trades 1729 vol 0>
72
+ # <Bar: 2019-04-03 wap 0.0 OHLC 3399.93 3435.9 3399.93 3435.56 trades 1733 vol 0>
73
+ # <Bar: 2019-04-04 wap 0.0 OHLC 3434.34 3449.44 3425.19 3441.93 trades 1680 vol 0>
74
+ # <Bar: 2019-04-05 wap 0.0 OHLC 3445.05 3453.01 3437.92 3447.47 trades 1677 vol 0>
75
+ # <Bar: 2019-04-08 wap 0.0 OHLC 3446.15 3447.08 3433.47 3438.06 trades 1648 vol 0>
76
+ # <Bar: 2019-04-09 wap 0.0 OHLC 3437.07 3450.69 3416.67 3417.22 trades 1710 vol 0>
77
+ # <Bar: 2019-04-10 wap 0.0 OHLC 3418.36 3435.32 3418.36 3424.65 trades 1670 vol 0>
78
+ # <Bar: 2019-04-11 wap 0.0 OHLC 3430.73 3442.25 3412.15 3435.34 trades 1773 vol 0>
79
+ # <Bar: 2019-04-12 wap 0.0 OHLC 3432.16 3454.77 3425.84 3447.83 trades 1715 vol 0>
80
+ #
81
+ # «to_human« is not needed here because ist aliased with `to_s`
82
+ #
83
+ # puts Symbols::Stocks.wfc.eod( start: Date.new(2019,10,9), duration: 3 )
84
+ # <Bar: 2020-10-23 wap 23.3675 OHLC 23.55 23.55 23.12 23.28 trades 5778 vol 50096>
85
+ # <Bar: 2020-10-26 wap 22.7445 OHLC 22.98 22.99 22.6 22.7 trades 6873 vol 79560>
86
+ # <Bar: 2020-10-27 wap 22.086 OHLC 22.55 22.58 21.82 21.82 trades 7503 vol 97691>
87
+
88
+ # puts Symbols::Stocks.wfc.eod( to: Date.new(2019,10,9), duration: 3 )
89
+ # <Bar: 2019-10-04 wap 48.964 OHLC 48.61 49.25 48.54 49.21 trades 9899 vol 50561>
90
+ # <Bar: 2019-10-07 wap 48.9445 OHLC 48.91 49.29 48.75 48.81 trades 10317 vol 50189>
91
+ # <Bar: 2019-10-08 wap 47.9165 OHLC 48.25 48.34 47.55 47.82 trades 12607 vol 53577>
92
+ #
93
+ def eod start:nil, to: Date.today, duration: nil , what: :trades
94
+
95
+ tws = IB::Connection.current
96
+ recieved = Queue.new
97
+ r = nil
98
+ # the hole response is transmitted at once!
99
+ a= tws.subscribe(IB::Messages::Incoming::HistoricalData) do |msg|
100
+ if msg.request_id == con_id
101
+ # msg.results.each { |entry| puts " #{entry}" }
102
+ r = block_given? ? msg.results.map{|y| yield y} : msg.results
103
+ end
104
+ recieved.push Time.now
105
+ end
106
+ b = tws.subscribe( IB::Messages::Incoming::Alert) do |msg|
107
+ if [321,162,200].include? msg.code
108
+ tws.logger.info msg.message
109
+ # TWS Error 200: No security definition has been found for the request
110
+ # TWS Error 354: Requested market data is not subscribed.
111
+ # TWS Error 162 # Historical Market Data Service error
112
+ recieved =[]
113
+ end
114
+ end
115
+
116
+
117
+ duration = if duration.present?
118
+ duration.is_a?(String) ? duration : duration.to_s + " D"
119
+ elsif start.present?
120
+ BuisinesDays.business_days_between(start, to).to_s + " D"
121
+ else
122
+ "1 D"
123
+ end
124
+
125
+ tws.send_message IB::Messages::Outgoing::RequestHistoricalData.new(
126
+ :request_id => con_id,
127
+ :contract => self,
128
+ :end_date_time => to.to_time.to_ib, # Time.now.to_ib,
129
+ :duration => duration, # ?
130
+ :bar_size => :day1, # IB::BAR_SIZES.key(:hour)?
131
+ :what_to_show => what,
132
+ :use_rth => 0,
133
+ :format_date => 2,
134
+ :keep_up_todate => 0)
135
+
136
+ Timeout::timeout(50) do # max 5 sec.
137
+ sleep 0.1
138
+ last_time = recieved.pop # blocks until a message is ready on the queue
139
+ loop do
140
+ sleep 0.1
141
+ break if recieved.empty? # finish if no more data received
142
+ end
143
+ tws.unsubscribe a
144
+ tws.unsubscribe b
145
+
146
+ r # the collected result
147
+
148
+ end
149
+ end # def
150
+ end # class
151
+ end # module
152
+
@@ -0,0 +1,9 @@
1
+ ## require any file, except gateway and related
2
+ require "ib/extensions/version"
3
+ require "ib/verify"
4
+ require "ib/eod"
5
+ require "ib/market-price"
6
+ require "ib/option-chain"
7
+ require "ib/option-greeks"
8
+ require "ib/order-prototypes"
9
+ require "ib/spread-prototypes"
@@ -0,0 +1,37 @@
1
+
2
+ def associate_ticdata
3
+
4
+ tws= IB::Gateway.tws # get the initialized ib-ruby instance
5
+ the_id = nil
6
+ finalize= false
7
+ # switch to delayed data
8
+ tws.send_message :RequestMarketDataType, :market_data_type => :delayed
9
+
10
+ s_id = tws.subscribe(:TickSnapshotEnd) { |msg| finalize = true if msg.ticker_id == the_id }
11
+
12
+ sub_id = tws.subscribe(:TickPrice, :TickSize, :TickGeneric, :TickOption) do |msg|
13
+ self.bars << msg.the_data if msg.ticker_id == the_id
14
+ end
15
+
16
+ # initialize »the_id« that is used to identify the received tick messages
17
+ # by firing the market data request
18
+ the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
19
+
20
+ #keep the method-call running until the request finished
21
+ #and cancel subscriptions to the message handler.
22
+ Thread.new do
23
+ i=0; loop{ i+=1; sleep 0.1; break if finalize || i > 1000 }
24
+ tws.unsubscribe sub_id
25
+ tws.unsubscribe s_id
26
+ puts "#{symbol} data gathered"
27
+ end # method returns the (running) thread
28
+
29
+ end # def
30
+ ###################### private methods
31
+
32
+ end # class
33
+
34
+
35
+
36
+
37
+ end # module