ib-extensions 1.2 → 1.3
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 +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'
|