ib-extensions 1.2 → 1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -3
- data/Gemfile.lock +26 -26
- data/README.md +56 -10
- data/bin/console +10 -3
- data/bin/gateway +14 -9
- data/changelog.md +54 -0
- data/ib-extensions.gemspec +6 -4
- data/lib/ib/alerts/base-alert.rb +10 -13
- data/lib/ib/eod.rb +239 -127
- data/lib/ib/extensions/version.rb +1 -1
- data/lib/ib/extensions.rb +5 -0
- data/lib/ib/gateway/account-infos.rb +74 -47
- data/lib/ib/gateway/order-handling.rb +57 -25
- data/lib/ib/gateway.rb +45 -31
- data/lib/ib/market-price.rb +34 -34
- data/lib/ib/models/account.rb +177 -144
- data/lib/ib/models/bag.rb +19 -0
- data/lib/ib/models/contract.rb +16 -0
- data/lib/ib/models/future.rb +20 -0
- data/lib/ib/models/option.rb +20 -13
- data/lib/ib/option-chain.rb +4 -6
- data/lib/ib/option-greeks.rb +27 -21
- data/lib/ib/order_prototypes/all-in-one.rb +46 -0
- data/lib/ib/plot-poec.rb +60 -0
- data/lib/ib/probability_of_expiring.rb +109 -0
- data/lib/ib/spread-prototypes.rb +1 -0
- data/lib/ib/spread_prototypes/butterfly.rb +2 -4
- data/lib/ib/spread_prototypes/calendar.rb +25 -23
- data/lib/ib/spread_prototypes/straddle.rb +3 -3
- data/lib/ib/spread_prototypes/strangle.rb +8 -9
- data/lib/ib/spread_prototypes/vertical.rb +6 -7
- data/lib/ib/verify.rb +34 -39
- data/lib/ib-gateway.rb +12 -0
- metadata +53 -5
@@ -0,0 +1,46 @@
|
|
1
|
+
module IB
|
2
|
+
|
3
|
+
#Combo-Orders are used for NonGuaranteed Orders only.
|
4
|
+
#»Normal« Option-Spreads are transmited by ordinary Limit-Orders
|
5
|
+
module Combo
|
6
|
+
### Basic Order Prototype: Combo with two limits
|
7
|
+
extend OrderPrototype
|
8
|
+
class << self
|
9
|
+
def defaults
|
10
|
+
## todo implement serialisation of key/tag Hash to camelCased-keyValue-List
|
11
|
+
# super.merge order_type: :limit , combo_params: { non_guaranteed: true}
|
12
|
+
# for the time being, we use the array representation
|
13
|
+
super.merge order_type: :limit , combo_params: [ ['NonGuaranteed', true] ]
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def requirements
|
18
|
+
Limit.requirements
|
19
|
+
end
|
20
|
+
|
21
|
+
def aliases
|
22
|
+
Limit.aliases
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def summary
|
27
|
+
<<-HERE
|
28
|
+
Create combination orders. It is constructed through options, stock and futures legs
|
29
|
+
(stock legs can be included if the order is routed through SmartRouting).
|
30
|
+
|
31
|
+
Although a combination/spread order is constructed of separate legs, it is executed
|
32
|
+
as a single transaction if it is routed directly to an exchange. For combination orders
|
33
|
+
that are SmartRouted, each leg may be executed separately to ensure best execution.
|
34
|
+
|
35
|
+
The »NonGuaranteed«-Flag is set to "false". A Pair of two securites should always be
|
36
|
+
routed »Guaranteed«, otherwise separate orders are prefered.
|
37
|
+
|
38
|
+
If a Bag-Order with »NonGuarateed :true« should be submitted, the Order-Type would be
|
39
|
+
REL+MKT, LMT+MKT, or REL+LMT
|
40
|
+
--------
|
41
|
+
Products: Options, Stocks, Futures
|
42
|
+
HERE
|
43
|
+
end # def
|
44
|
+
end # class
|
45
|
+
end # module combo
|
46
|
+
end # module ib
|
data/lib/ib/plot-poec.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'distribution'
|
2
|
+
require 'gnuplot'
|
3
|
+
|
4
|
+
## source: ChatGpt
|
5
|
+
# Set the variables
|
6
|
+
s = 100 # current stock price
|
7
|
+
k = 110 # strike price
|
8
|
+
t = 0.5 # time to expiry (in years)
|
9
|
+
r = 0.02 # risk-free interest rate
|
10
|
+
sigma = 0.2 # implied volatility
|
11
|
+
p = 0.7 # probability
|
12
|
+
|
13
|
+
# Calculate d1 and d2
|
14
|
+
d1 = (Math.log(s/k) + (r + 0.5*sigma**2)*t) / (sigma * Math.sqrt(t))
|
15
|
+
d2 = d1 - sigma * Math.sqrt(t)
|
16
|
+
|
17
|
+
# Calculate the Z-score for the desired probability
|
18
|
+
z_score = Distribution::Normal.inv_cdf(p + (1-p)/2)
|
19
|
+
|
20
|
+
# Calculate the upper and lower bounds of the 70% probability range
|
21
|
+
upper_bound = s * Math.exp((r - 0.5*sigma**2)*t + sigma*Math.sqrt(t)*z_score)
|
22
|
+
lower_bound = s * Math.exp((r - 0.5*sigma**2)*t + sigma*Math.sqrt(t)*(-z_score))
|
23
|
+
|
24
|
+
# Create the plot
|
25
|
+
Gnuplot.open do |gp|
|
26
|
+
Gnuplot::Plot.new(gp) do |plot|
|
27
|
+
plot.title 'Probability Density Function with 70% probability range'
|
28
|
+
plot.xlabel 'Stock Price'
|
29
|
+
plot.ylabel 'Probability Density'
|
30
|
+
|
31
|
+
# Set the x-axis range
|
32
|
+
plot.xrange "[#{s*0.6}:#{s*1.4}]"
|
33
|
+
|
34
|
+
# Plot the probability density function
|
35
|
+
x = (s*0.6..s*1.4).step(0.1).to_a
|
36
|
+
y = x.map { |xi| Distribution::Normal.pdf((Math.log(xi/s) + (r - 0.5*sigma**2)*t) / (sigma * Math.sqrt(t))) / (xi*sigma*Math.sqrt(t)) }
|
37
|
+
plot.data << Gnuplot::DataSet.new([x, y]) do |ds|
|
38
|
+
ds.with = 'lines'
|
39
|
+
ds.linewidth = 2
|
40
|
+
ds.linecolor = 'blue'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Plot the upper and lower bounds of the 70% probability range
|
44
|
+
plot.data << Gnuplot::DataSet.new([lower_bound, 0]) do |ds|
|
45
|
+
ds.with = 'lines'
|
46
|
+
ds.linewidth = 2
|
47
|
+
ds.linecolor = 'red'
|
48
|
+
end
|
49
|
+
plot.data << Gnuplot::DataSet.new([upper_bound, 0]) do |ds|
|
50
|
+
ds.with = 'lines'
|
51
|
+
ds.linewidth = 2
|
52
|
+
ds.linecolor = 'red'
|
53
|
+
end
|
54
|
+
plot.data << Gnuplot::DataSet.new([[lower_bound, upper_bound], [0, 0]]) do |ds|
|
55
|
+
ds.with = 'filledcurve x1=1 x2=2'
|
56
|
+
ds.fillcolor = 'red'
|
57
|
+
ds.fillstyle = 'transparent solid 0.2'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module IB
|
2
|
+
module ProbabilityOfExpiring
|
3
|
+
|
4
|
+
# Use by calling
|
5
|
+
# a = Stock.new symbol: 'A'
|
6
|
+
#
|
7
|
+
require 'prime'
|
8
|
+
require 'distribution'
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
def probability_of_assignment **args
|
13
|
+
( probability_of_expiring(**args) - 1 ).abs
|
14
|
+
end
|
15
|
+
def probability_of_expiring **args
|
16
|
+
@probability_of_expiring = calculate_probability_of_expiring(**args) if @probability_of_expiring.nil? || ! args.empty?
|
17
|
+
@probability_of_expiring
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
=begin
|
22
|
+
Here are the steps to calculate the probability of expiry cone for a stock in
|
23
|
+
the next six months using the Black-Scholes model:
|
24
|
+
|
25
|
+
* Determine the current stock price and the strike price for the option you
|
26
|
+
are interested in. Let's say the current stock price is $100 and the strike
|
27
|
+
price is $110. * Determine the time to expiry. In this case, we are
|
28
|
+
interested in the next six months, so the time to expiry is 0.5 years. *
|
29
|
+
Determine the implied volatility of the stock. Implied volatility is a measure
|
30
|
+
of the expected volatility of the stock over the life of the option, and can be
|
31
|
+
estimated from the option prices in the market.
|
32
|
+
|
33
|
+
* Use the Black-Scholes formula to calculate the probability of the stock
|
34
|
+
expiring within the range of prices that make up the expiry cone. The formula
|
35
|
+
is:
|
36
|
+
|
37
|
+
P = N(d2)
|
38
|
+
|
39
|
+
Where P is the probability of the stock expiring within the expiry cone, and
|
40
|
+
N is the cumulative distribution function of the standard normal
|
41
|
+
distribution. d2 is calculated as:
|
42
|
+
|
43
|
+
d2 = (ln(S/K) + (r - 0.5 * σ^2) * T) / (σ * sqrt(T))
|
44
|
+
|
45
|
+
Where S is the current stock price, K is the strike price, r is the risk-free
|
46
|
+
interest rate, σ is the implied volatility, and T is the time to expiry.
|
47
|
+
|
48
|
+
Look up the value of N(d2) in a standard normal distribution table, or use a
|
49
|
+
calculator or spreadsheet program that can calculate cumulative distribution
|
50
|
+
functions.
|
51
|
+
|
52
|
+
The result is the probability of the stock expiring within the expiry cone.
|
53
|
+
For example, if N(d2) is 0.35, then the probability of the stock expiring
|
54
|
+
within the expiry cone is 35%.
|
55
|
+
|
56
|
+
(ChatGPT)
|
57
|
+
=end
|
58
|
+
def calculate_probability_of_expiring price: nil,
|
59
|
+
interest: 0.03,
|
60
|
+
iv: nil,
|
61
|
+
strike: nil,
|
62
|
+
expiry: nil,
|
63
|
+
ref_date: Date.today
|
64
|
+
|
65
|
+
if iv.nil? && self.respond_to?( :greek )
|
66
|
+
IB::Connection.current.logger.info "Probability_of_expiring: using current IV and Underlying-Price for calculation"
|
67
|
+
request_greeks if greek.nil?
|
68
|
+
iv = greek.implied_volatility
|
69
|
+
price = greek.under_price if price.nil?
|
70
|
+
end
|
71
|
+
error "ProbabilityOfExpiringCone needs iv as input" if iv.nil? || iv.zero?
|
72
|
+
|
73
|
+
if price.nil?
|
74
|
+
price = if self.strike.to_i.zero?
|
75
|
+
market_price
|
76
|
+
else
|
77
|
+
underlying.market_price
|
78
|
+
end
|
79
|
+
end
|
80
|
+
error "ProbabilityOfExpiringCone needs price as input" if price.to_i.zero?
|
81
|
+
|
82
|
+
|
83
|
+
strike ||= self.strike
|
84
|
+
error "ProbabilityOfExpiringCone needs strike as input" if strike.to_i.zero?
|
85
|
+
|
86
|
+
if expiry.nil?
|
87
|
+
if last_trading_day == ''
|
88
|
+
error "ProbabilityOfExpiringCone needs expiry as input"
|
89
|
+
else
|
90
|
+
expiry = last_trading_day
|
91
|
+
end
|
92
|
+
end
|
93
|
+
time_to_expiry = ( Date.parse( expiry.to_s ) - ref_date ).to_i
|
94
|
+
|
95
|
+
# # Calculate d1 and d2
|
96
|
+
d1 = (Math.log(price/strike.to_f) + (interest + 0.5*iv**2)*time_to_expiry) / (iv * Math.sqrt(time_to_expiry))
|
97
|
+
d2 = d1 - iv * Math.sqrt(time_to_expiry)
|
98
|
+
#
|
99
|
+
# # Calculate the probability of expiry cone
|
100
|
+
Distribution::Normal.cdf(d2)
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Contract
|
106
|
+
include ProbabilityOfExpiring
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
data/lib/ib/spread-prototypes.rb
CHANGED
@@ -24,6 +24,7 @@ module IB
|
|
24
24
|
|
25
25
|
def initialize_spread ref_contract = nil, **attributes
|
26
26
|
error "Initializing of Spread failed – contract is missing" unless ref_contract.is_a?(IB::Contract)
|
27
|
+
# make sure that :exchange, :symbol and :currency are present
|
27
28
|
the_contract = ref_contract.merge( **attributes ).verify.first
|
28
29
|
error "Underlying for Spread is not valid: #{ref_contract.to_human}" if the_contract.nil?
|
29
30
|
the_spread= IB::Spread.new the_contract.attributes.slice( :exchange, :symbol, :currency )
|
@@ -25,8 +25,7 @@ module IB
|
|
25
25
|
error "fabrication is based on a master option. Please specify as first argument" unless master.is_a?(IB::Option)
|
26
26
|
strike = master.strike
|
27
27
|
master.right = :put unless master.right == :call
|
28
|
-
l=
|
29
|
-
# puts "master: #{master.attributes.inspect}"
|
28
|
+
l= master.verify
|
30
29
|
if l.empty?
|
31
30
|
error "Invalid Parameters. No Contract found #{master.to_human}"
|
32
31
|
elsif l.size > 1
|
@@ -43,7 +42,7 @@ module IB
|
|
43
42
|
strikes = [front, master.strike, back]
|
44
43
|
strikes.zip([1, -2, 1]).each do |strike, ratio|
|
45
44
|
action = ratio >0 ? :buy : :sell
|
46
|
-
|
45
|
+
leg = IB::Option.new( master.attributes.merge( strike: strike )).verify.first.essential
|
47
46
|
the_spread.add_leg leg, action: action, ratio: ratio.abs
|
48
47
|
end
|
49
48
|
the_spread.description = the_description( the_spread )
|
@@ -53,7 +52,6 @@ module IB
|
|
53
52
|
|
54
53
|
def build from: , front:, back:, **options
|
55
54
|
underlying_attributes = { expiry: IB::Symbols::Futures.next_expiry, right: :put }.merge( from.attributes.slice( :symbol, :currency, :exchange, :strike )).merge( options )
|
56
|
-
puts underlying_attributes.inspect
|
57
55
|
fabricate IB::Option.new( underlying_attributes), front: front, back: back
|
58
56
|
end
|
59
57
|
|
@@ -16,16 +16,13 @@ module IB
|
|
16
16
|
def fabricate master, the_other_expiry
|
17
17
|
|
18
18
|
error "Argument must be a IB::Future or IB::Option" unless [:option, :future_option, :future ].include? master.sec_type
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
error "Initialisation of Legs failed" if the_spread.legs.size != 2
|
27
|
-
the_spread.description = the_description( the_spread )
|
28
|
-
end
|
19
|
+
m = master.verify.first
|
20
|
+
the_other_expiry = the_other_expiry.values.first if the_other_expiry.is_a?(Hash)
|
21
|
+
back = IB::Spread.transform_distance m.expiry, the_other_expiry
|
22
|
+
calendar = m.roll expiry: back
|
23
|
+
error "Initialisation of Legs failed" if calendar.legs.size != 2
|
24
|
+
calendar.description = the_description( calendar )
|
25
|
+
calendar # return fabricated spread
|
29
26
|
end
|
30
27
|
|
31
28
|
|
@@ -46,25 +43,30 @@ module IB
|
|
46
43
|
fields[:expiry] = from.expiry unless fields.key?(:expiry)
|
47
44
|
fields[:trading_class] = from.trading_class unless fields.key?(:trading_class) || from.trading_class.empty?
|
48
45
|
fields[:multiplier] = from.multiplier unless fields.key?(:multiplier) || from.multiplier.to_i.zero?
|
49
|
-
|
46
|
+
details = from.verify.first.contract_detail
|
50
47
|
IB::Contract.new( con_id: details.under_con_id,
|
51
|
-
currency: from.currency)
|
52
|
-
.verify!
|
53
|
-
.essential
|
48
|
+
currency: from.currency).verify.first.essential
|
54
49
|
else
|
55
50
|
from
|
56
51
|
end
|
57
52
|
kind = { :front => fields.delete(:front), :back => fields.delete(:back) }
|
58
|
-
error "Specifiaction of :front and :back expiries
|
53
|
+
error "Specifiaction of :front and :back expiries necessary, got: #{kind.inspect}" if kind.values.any?(nil)
|
59
54
|
initialize_spread( underlying ) do | the_spread |
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
kind[:back] =
|
55
|
+
leg_prototype = IB::Option.new underlying.attributes
|
56
|
+
.slice( :currency, :symbol, :exchange)
|
57
|
+
.merge(defaults)
|
58
|
+
.merge( fields )
|
59
|
+
kind[:back] = IB::Spread.transform_distance kind[:front], kind[:back]
|
65
60
|
leg_prototype.sec_type = 'FOP' if underlying.is_a?(IB::Future)
|
66
|
-
|
67
|
-
|
61
|
+
leg1 = leg_prototype.merge(expiry: kind[:front] ).verify.first
|
62
|
+
leg2 = leg_prototype.merge(expiry: kind[:back] ).verify.first
|
63
|
+
unless leg2.is_a? IB::Option
|
64
|
+
leg2_trading_class = ''
|
65
|
+
leg2 = leg_prototype.merge(expiry: kind[:back] ).verify.first
|
66
|
+
|
67
|
+
end
|
68
|
+
the_spread.add_leg leg1 , action: :buy
|
69
|
+
the_spread.add_leg leg2 , action: :sell
|
68
70
|
error "Initialisation of Legs failed" if the_spread.legs.size != 2
|
69
71
|
the_spread.description = the_description( the_spread )
|
70
72
|
end
|
@@ -78,7 +80,7 @@ module IB
|
|
78
80
|
|
79
81
|
def the_description spread
|
80
82
|
x= [ spread.combo_legs.map(&:weight) , spread.legs.map( &:last_trading_day )].transpose
|
81
|
-
|
83
|
+
"<Calendar #{spread.symbol} #{spread.legs.first.right}(#{spread.legs.first.strike})[#{x.map{|w,l_t_d| "#{w} :#{Date.parse(l_t_d).strftime("%b %Y")} "}.join( '|+|' )} >"
|
82
84
|
end
|
83
85
|
end # class
|
84
86
|
end # module vertical
|
@@ -43,11 +43,11 @@ module IB
|
|
43
43
|
leg_prototype = IB::Option.new from.attributes
|
44
44
|
.slice( :currency, :symbol, :exchange)
|
45
45
|
.merge(defaults)
|
46
|
-
|
46
|
+
.merge( fields )
|
47
47
|
|
48
48
|
leg_prototype.sec_type = 'FOP' if from.is_a?(IB::Future)
|
49
|
-
|
50
|
-
|
49
|
+
the_spread.add_leg leg_prototype.merge( right: :put ).verify.first
|
50
|
+
the_spread.add_leg leg_prototype.merge( right: :call ).verify.first
|
51
51
|
error "Initialisation of Legs failed" if the_spread.legs.size != 2
|
52
52
|
the_spread.description = the_description( the_spread )
|
53
53
|
end
|
@@ -60,15 +60,14 @@ module IB
|
|
60
60
|
end
|
61
61
|
kind = { :p => fields.delete(:p), :c => fields.delete(:c) }
|
62
62
|
initialize_spread( underlying ) do | the_spread |
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
.merge( local_symbol: "" )
|
63
|
+
leg_prototype = IB::Option.new from.attributes
|
64
|
+
.slice( :currency, :symbol, :exchange)
|
65
|
+
.merge(defaults)
|
66
|
+
.merge( fields )
|
68
67
|
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
leg_prototype.sec_type = 'FOP' if underlying.is_a?(IB::Future)
|
69
|
+
the_spread.add_leg leg_prototype.merge( right: :put, strike: kind[:p] ).verify.first
|
70
|
+
the_spread.add_leg leg_prototype.merge( right: :call, strike: kind[:c] ).verify.first
|
72
71
|
error "Initialisation of Legs failed" if the_spread.legs.size != 2
|
73
72
|
the_spread.description = the_description( the_spread )
|
74
73
|
end
|
@@ -88,7 +87,7 @@ module IB
|
|
88
87
|
|
89
88
|
|
90
89
|
def the_description spread
|
91
|
-
|
90
|
+
"<Strangle #{spread.symbol}(#{spread.legs.map(&:strike).join(",")})[#{Date.parse(spread.legs.first.last_trading_day).strftime("%b %Y")}]>"
|
92
91
|
end
|
93
92
|
|
94
93
|
end # class
|
@@ -20,8 +20,8 @@ module IB
|
|
20
20
|
buy = master.strike if buy.zero?
|
21
21
|
sell = master.strike if sell.zero?
|
22
22
|
initialize_spread( master ) do | the_spread |
|
23
|
-
|
24
|
-
|
23
|
+
the_spread.add_leg master.merge(strike: sell).verify.first, action: :sell
|
24
|
+
the_spread.add_leg master.merge(strike: buy).verify.first, action: :buy
|
25
25
|
error "Initialisation of Legs failed" if the_spread.legs.size != 2
|
26
26
|
the_spread.description = the_description( the_spread )
|
27
27
|
end
|
@@ -54,16 +54,15 @@ module IB
|
|
54
54
|
from
|
55
55
|
end
|
56
56
|
kind = { :buy => fields.delete(:buy), :sell => fields.delete(:sell) }
|
57
|
-
error "
|
57
|
+
error "Specification of :buy and :sell necessary, got: #{kind.inspect}" if kind.values.any?(nil)
|
58
58
|
initialize_spread( underlying ) do | the_spread |
|
59
|
-
leg_prototype =
|
59
|
+
leg_prototype = Option.new underlying.attributes
|
60
60
|
.slice( :currency, :symbol, :exchange)
|
61
61
|
.merge(defaults)
|
62
62
|
.merge( fields )
|
63
|
-
.merge( local_symbol: "" )
|
64
63
|
leg_prototype.sec_type = 'FOP' if underlying.is_a?(IB::Future)
|
65
|
-
|
66
|
-
|
64
|
+
the_spread.add_leg leg_prototype.merge(strike: kind[:sell]).verify.first, action: :sell
|
65
|
+
the_spread.add_leg leg_prototype.merge(strike: kind[:buy] ).verify.first, action: :buy
|
67
66
|
error "Initialisation of Legs failed" if the_spread.legs.size != 2
|
68
67
|
the_spread.description = the_description( the_spread )
|
69
68
|
end
|
data/lib/ib/verify.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module IB
|
2
2
|
# define a custom ErrorClass which can be fired if a verification fails
|
3
|
-
|
3
|
+
class VerifyError < StandardError
|
4
|
+
|
5
|
+
end
|
4
6
|
# end
|
5
7
|
|
6
8
|
class Contract
|
@@ -49,12 +51,15 @@ module IB
|
|
49
51
|
# IB::Symbols::W500.map{|c| c.verify(thread: true){ |vc| do_something }}.join
|
50
52
|
|
51
53
|
def verify thread: nil, &b
|
52
|
-
|
53
|
-
|
54
|
+
if thread
|
55
|
+
Thread.new { _verify &b }
|
56
|
+
else
|
57
|
+
_verify &b
|
58
|
+
end
|
54
59
|
end # def
|
55
60
|
|
56
61
|
# returns a hash
|
57
|
-
def
|
62
|
+
def necessary_attributes
|
58
63
|
|
59
64
|
v= { stock: { currency: 'USD', exchange: 'SMART', symbol: nil},
|
60
65
|
option: { currency: 'USD', exchange: 'SMART', right: 'P', expiry: nil, strike: nil, symbol: nil},
|
@@ -70,8 +75,9 @@ module IB
|
|
70
75
|
def verify!
|
71
76
|
c = 0
|
72
77
|
IB::Connection.logger.warn "Contract.verify! is depreciated. Use \"contract = contract.verify.first\" instead"
|
73
|
-
|
74
|
-
|
78
|
+
c= verify.first
|
79
|
+
self.attributes = c.invariant_attributes
|
80
|
+
self.contract_detail = c.contract_detail
|
75
81
|
self
|
76
82
|
end
|
77
83
|
|
@@ -88,47 +94,43 @@ module IB
|
|
88
94
|
# if :update is true, the attributes of the Contract itself are adapted
|
89
95
|
#
|
90
96
|
# otherwise the Contract is untouched
|
91
|
-
def _verify
|
97
|
+
def _verify &b # :nodoc:
|
92
98
|
ib = Connection.current
|
99
|
+
error "No Connection" unless ib.is_a? Connection
|
93
100
|
# we generate a Request-Message-ID on the fly
|
94
|
-
|
101
|
+
error "Either con_id or sec_type have to be set", :verify if con_id.to_i.zero? && sec_type.blank?
|
95
102
|
# define local vars which are updated within the query-block
|
96
|
-
|
103
|
+
received_contracts = []
|
97
104
|
queue = Queue.new
|
98
|
-
|
105
|
+
message_id = nil
|
99
106
|
|
100
107
|
# a tws-request is suppressed for bags and if the contract_detail-record is present
|
101
|
-
|
108
|
+
tws_request_not_necessary = bag? || contract_detail.is_a?( ContractDetail )
|
102
109
|
|
103
|
-
if
|
110
|
+
if tws_request_not_necessary
|
104
111
|
yield self if block_given?
|
105
|
-
return self
|
112
|
+
return [self] # return an array!
|
106
113
|
else # subscribe to ib-messages and describe what to do
|
107
114
|
a = ib.subscribe(:Alert, :ContractData, :ContractDataEnd) do |msg|
|
108
115
|
case msg
|
109
116
|
when Messages::Incoming::Alert
|
117
|
+
## do not throw an error here, asynchronous operation!
|
118
|
+
## just notice failure in log and return nil instead of contract-object
|
110
119
|
if msg.code == 200 && msg.error_id == message_id
|
111
120
|
ib.logger.error { "Not a valid Contract :: #{self.to_human} " }
|
112
121
|
queue.close
|
113
122
|
end
|
114
123
|
when Messages::Incoming::ContractData
|
115
124
|
if msg.request_id.to_i == message_id
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
msg.contract
|
123
|
-
end
|
124
|
-
if update
|
125
|
-
self.attributes = msg.contract.attributes
|
126
|
-
self.contract_detail = msg.contract_detail unless msg.contract_detail.nil?
|
127
|
-
end
|
125
|
+
c = if block_given?
|
126
|
+
yield msg.contract
|
127
|
+
else
|
128
|
+
msg.contract
|
129
|
+
end
|
130
|
+
queue.push c unless c.nil?
|
128
131
|
end
|
129
132
|
when Messages::Incoming::ContractDataEnd
|
130
|
-
queue.
|
131
|
-
|
133
|
+
queue.close if msg.request_id.to_i == message_id
|
132
134
|
end # case
|
133
135
|
end # subscribe
|
134
136
|
|
@@ -138,19 +140,12 @@ module IB
|
|
138
140
|
# if contract_to_be_queried.present? # is nil if query_contract fails
|
139
141
|
message_id = ib.send_message :RequestContractData, :contract => query_contract
|
140
142
|
|
141
|
-
|
142
|
-
|
143
|
-
# j=0; loop{ sleep(0.01); j+=1; break if queue.closed? || queue.pop || j> 100; }
|
144
|
-
# ib.logger.error{ "#{to_human} --> No ContractData recieved " } if j >= 100 && !queue.closed?
|
145
|
-
ib.unsubscribe a
|
146
|
-
end
|
147
|
-
if thread.nil?
|
148
|
-
th.join # wait for the thread to finish
|
149
|
-
recieved_contracts # return queue of contracts
|
150
|
-
else
|
151
|
-
th # return active thread
|
143
|
+
while r = queue.pop
|
144
|
+
received_contracts << r
|
152
145
|
end
|
146
|
+
ib.unsubscribe a
|
153
147
|
end
|
148
|
+
received_contracts # return contracts
|
154
149
|
end
|
155
150
|
|
156
151
|
# Generates an IB::Contract with the required attributes to retrieve a unique contract from the TWS
|
@@ -189,7 +184,7 @@ module IB
|
|
189
184
|
# Contract.build item_attributehash[necessary_items].merge(:sec_type=> sec_type) # return this
|
190
185
|
Contract.build self.invariant_attributes # return this
|
191
186
|
else # its always possible, to retrieve a Contract if con_id and exchange or are present
|
192
|
-
Contract.new con_id: con_id , :exchange => exchange.presence || item_attributehash[
|
187
|
+
Contract.new con_id: con_id , :exchange => exchange.presence || item_attributehash[necessary_attributes][:exchange].presence || 'SMART' # return this
|
193
188
|
end # if
|
194
189
|
end # def
|
195
190
|
end
|
data/lib/ib-gateway.rb
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
+
require "distribution"
|
2
|
+
require "polars"
|
1
3
|
require 'ib-api'
|
2
4
|
require 'ib/verify'
|
3
5
|
require 'ib/spread-prototypes'
|
4
6
|
require 'ib/order-prototypes'
|
7
|
+
require "ib/eod"
|
8
|
+
require "ib/market-price"
|
9
|
+
require "ib/option-chain"
|
10
|
+
require "ib/option-greeks"
|
11
|
+
require "ib/models/contract.rb"
|
12
|
+
require "ib/models/bag.rb"
|
13
|
+
require "ib/models/account.rb"
|
14
|
+
require "ib/models/option.rb"
|
15
|
+
require "ib/models/future.rb"
|
16
|
+
require "ib/probability_of_expiring"
|
5
17
|
require 'ib/gateway'
|