ib-extensions 1.0

Sign up to get free protection for your applications and to get access to all the features.
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