ib-extensions 1.1 → 1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,25 +1,27 @@
1
1
  require 'ib/verify'
2
2
  require 'ib/market-price'
3
3
  module IB
4
- # define a custom ErrorClass which can be fired if a verification fails
5
- class VerifyError < StandardError
6
- end
7
4
 
8
5
  class Contract
9
6
 
10
7
 
11
8
 
12
- # returns the Option Chain of the contract (if available)
9
+ # returns the Option Chain (monthly options, expiry: third friday)
10
+ # of the contract (if available)
11
+ #
13
12
  #
14
13
  ## parameters
15
- ### right:: :call, :put, :straddle
16
- ### ref_price:: :request or a numeric value
14
+ ### right:: :call, :put, :straddle ( default: :put )
15
+ ### ref_price:: :request or a numeric value ( default: :request )
17
16
  ### sort:: :strike, :expiry
18
17
  ### exchange:: List of Exchanges to be queried (Blank for all available Exchanges)
19
- def option_chain ref_price: :request, right: :put, sort: :strike, exchange: ''
18
+ ### trading_class ( optional )
19
+ def option_chain ref_price: :request, right: :put, sort: :strike, exchange: '', trading_class: nil
20
20
 
21
21
  ib = Connection.current
22
- finalize = Queue.new
22
+
23
+ # binary interthread communication
24
+ finalize = Queue.new
23
25
 
24
26
  ## Enable Cashing of Definition-Matrix
25
27
  @option_chain_definition ||= []
@@ -58,9 +60,8 @@ class Contract
58
60
  sec_type: c[:sec_type]
59
61
 
60
62
  finalize.pop # wait until data appeared
61
- #i=0; loop { sleep 0.1; break if i> 1000 || finalize; i+=1 }
62
63
 
63
- ib.unsubscribe sub_sdop , sub_ocd
64
+ ib.unsubscribe sub_sdop, sub_ocd
64
65
  else
65
66
  Connection.logger.info { "#{to_human} : using cached data" }
66
67
  end
@@ -69,18 +70,18 @@ class Contract
69
70
  # select values and assign to options
70
71
  #
71
72
  unless @option_chain_definition.blank?
72
- requested_strikes = if block_given?
73
+ requested_strikes = if block_given?
73
74
  ref_price = market_price if ref_price == :request
74
75
  if ref_price.nil?
75
- ref_price = @option_chain_definition[:strikes].min +
76
- ( @option_chain_definition[:strikes].max -
77
- @option_chain_definition[:strikes].min ) / 2
76
+ ref_price = @option_chain_definition[:strikes].min +
77
+ ( @option_chain_definition[:strikes].max -
78
+ @option_chain_definition[:strikes].min ) / 2
78
79
  Connection.logger.warn { "#{to_human} :: market price not set – using midpoint of available strikes instead: #{ref_price.to_f}" }
79
80
  end
80
81
  atm_strike = @option_chain_definition[:strikes].min_by { |x| (x - ref_price).abs }
81
82
  the_grouped_strikes = @option_chain_definition[:strikes].group_by{|e| e <=> atm_strike}
82
83
  begin
83
- the_strikes = yield the_grouped_strikes
84
+ the_strikes = yield the_grouped_strikes
84
85
  the_strikes.unshift atm_strike unless the_strikes.first == atm_strike # the first item is the atm-strike
85
86
  the_strikes
86
87
  rescue
@@ -92,34 +93,34 @@ class Contract
92
93
  end
93
94
 
94
95
  # third Friday of a month
95
- monthly_expirations = @option_chain_definition[:expirations].find_all{|y| (15..21).include? y.day }
96
+ monthly_expirations = @option_chain_definition[:expirations].find_all {|y| (15..21).include? y.day }
96
97
  # puts @option_chain_definition.inspect
97
- option_prototype = -> ( ltd, strike ) do
98
- IB::Option.new symbol: symbol,
99
- exchange: @option_chain_definition[:exchange],
100
- trading_class: @option_chain_definition[:trading_class],
101
- multiplier: @option_chain_definition[:multiplier],
102
- currency: currency,
103
- last_trading_day: ltd,
104
- strike: strike,
105
- right: right
98
+ option_prototype = -> ( ltd, strike ) do
99
+ IB::Option.new( symbol: symbol,
100
+ exchange: @option_chain_definition[:exchange],
101
+ trading_class: @option_chain_definition[:trading_class],
102
+ multiplier: @option_chain_definition[:multiplier],
103
+ currency: currency,
104
+ last_trading_day: ltd,
105
+ strike: strike,
106
+ right: right).verify &.first
106
107
  end
107
108
  options_by_expiry = -> ( schema ) do
108
109
  # Array: [ yymm -> Options] prepares for the correct conversion to a Hash
109
110
  Hash[ monthly_expirations.map do | l_t_d |
110
- [ l_t_d.strftime('%y%m').to_i , schema.map{ | strike | option_prototype[ l_t_d, strike ]}.compact ]
111
+ [ l_t_d.strftime('%y%m').to_i , schema.map { | strike | option_prototype[ l_t_d, strike ]}.compact ]
111
112
  end ] # by Hash[ ]
112
113
  end
113
114
  options_by_strike = -> ( schema ) do
114
115
  Hash[ schema.map do | strike |
115
- [ strike , monthly_expirations.map{ | l_t_d | option_prototype[ l_t_d, strike ]}.compact ]
116
+ [ strike , monthly_expirations.map { | l_t_d | option_prototype[ l_t_d, strike ]}.compact ]
116
117
  end ] # by Hash[ ]
117
118
  end
118
119
 
119
120
  if sort == :strike
120
- options_by_strike[ requested_strikes ]
121
+ options_by_strike[ requested_strikes ]
121
122
  else
122
- options_by_expiry[ requested_strikes ]
123
+ options_by_expiry[ requested_strikes ]
123
124
  end
124
125
  else
125
126
  Connection.logger.error "#{to_human} ::No Options available"
@@ -128,8 +129,8 @@ class Contract
128
129
  end # def
129
130
 
130
131
  # return a set of AtTheMoneyOptions
131
- def atm_options ref_price: :request, right: :put
132
- option_chain( right: right, ref_price: ref_price, sort: :expiry) do | chain |
132
+ def atm_options ref_price: :request, right: :put, **params
133
+ option_chain( right: right, ref_price: ref_price, sort: :expiry, **params) do | chain |
133
134
  chain[0]
134
135
  end
135
136
 
@@ -137,8 +138,8 @@ class Contract
137
138
  end
138
139
 
139
140
  # return InTheMoneyOptions
140
- def itm_options count: 5, right: :put, ref_price: :request, sort: :strike
141
- option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
141
+ def itm_options count: 5, right: :put, ref_price: :request, sort: :strike, exchange: ''
142
+ option_chain( right: right, ref_price: ref_price, sort: sort, exchange: exchange ) do | chain |
142
143
  if right == :put
143
144
  above_market_price_strikes = chain[1][0..count-1]
144
145
  else
@@ -148,8 +149,8 @@ class Contract
148
149
  end # def
149
150
 
150
151
  # return OutOfTheMoneyOptions
151
- def otm_options count: 5, right: :put, ref_price: :request, sort: :strike
152
- option_chain( right: right, ref_price: ref_price, sort: sort ) do | chain |
152
+ def otm_options count: 5, right: :put, ref_price: :request, sort: :strike, exchange: ''
153
+ option_chain( right: right, ref_price: ref_price, sort: sort, exchange: exchange ) do | chain |
153
154
  if right == :put
154
155
  # puts "Chain: #{chain}"
155
156
  below_market_price_strikes = chain[-1][-count..-1].reverse
@@ -160,34 +161,6 @@ class Contract
160
161
  end
161
162
 
162
163
 
163
- def associate_ticdata
164
-
165
- tws= IB::Connection.current # get the initialized ib-ruby instance
166
- the_id = nil
167
- finalize= false
168
- # switch to delayed data
169
- tws.send_message :RequestMarketDataType, :market_data_type => :delayed
170
-
171
- s_id = tws.subscribe(:TickSnapshotEnd) { |msg| finalize = true if msg.ticker_id == the_id }
172
-
173
- sub_id = tws.subscribe(:TickPrice, :TickSize, :TickGeneric, :TickOption) do |msg|
174
- self.bars << msg.the_data if msg.ticker_id == the_id
175
- end
176
-
177
- # initialize »the_id« that is used to identify the received tick messages
178
- # by firing the market data request
179
- the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
180
-
181
- #keep the method-call running until the request finished
182
- #and cancel subscriptions to the message handler.
183
- Thread.new do
184
- i=0; loop{ i+=1; sleep 0.1; break if finalize || i > 1000 }
185
- tws.unsubscribe sub_id
186
- tws.unsubscribe s_id
187
- #puts "#{symbol} data gathered"
188
- end # method returns the (running) thread
189
-
190
- end # def
191
164
  end # class
192
165
 
193
166
 
@@ -1,44 +1,45 @@
1
1
  module IB
2
2
 
3
3
 
4
-
5
4
  class Option
6
- # Ask for the Greeks and implied Vola
7
- #
5
+ # Ask for the Greeks and implied Vola
6
+ #
8
7
  # The result can be customized by a provided block.
9
- #
10
- # IB::Symbols::Options.aapl.greeks{ |x| x }
11
- # -> {"bid"=>0.10142e3, "ask"=>0.10144e3, "last"=>0.10142e3, "close"=>0.10172e3}
12
- #
8
+ #
9
+ # IB::Symbols::Options.aapl.greeks{ |x| x }
10
+ # -> {"bid"=>0.10142e3, "ask"=>0.10144e3, "last"=>0.10142e3, "close"=>0.10172e3}
11
+ #
13
12
  # Possible values for Parameter :what --> :all :model, :bid, :ask, :bidask, :last
14
- #
13
+ #
15
14
  def request_greeks delayed: true, what: :model, thread: false
16
15
 
17
- tws= Connection.current # get the initialized ib-ruby instance
16
+ tws = Connection.current # get the initialized ib-ruby instance
18
17
  # define requested tick-attributes
19
- request_data_type = IB::MARKET_DATA_TYPES.rassoc( delayed ? :frozen_delayed : :frozen ).first
18
+ request_data_type = IB::MARKET_DATA_TYPES.rassoc( delayed ? :frozen_delayed : :frozen ).first
20
19
  # possible types = [ [ :delayed_model_option , :model_option ] , [:delayed_last_option , :last_option ],
21
- # [ :delayed_bid_option , :bid_option ], [ :delayed_ask_option , :ask_option ] ]
20
+ # [ :delayed_bid_option , :bid_option ], [ :delayed_ask_option , :ask_option ]]
22
21
  tws.send_message :RequestMarketDataType, :market_data_type => request_data_type
23
22
  tickdata = []
24
23
 
25
24
  self.greek = OptionDetail.new if greek.nil?
26
- greek.updated_at = Time.now
25
+ greek.updated_at = Time.now
26
+ greek.option = self
27
+ queue = Queue.new
27
28
 
28
29
  #keep the method-call running until the request finished
29
30
  #and cancel subscriptions to the message handler
30
31
  # method returns the (running) thread
31
32
  th = Thread.new do
32
33
  the_id = nil
33
- finalize= false
34
34
  # subscribe to TickPrices
35
- s_id = tws.subscribe(:TickSnapshotEnd) { |msg| finalize = true if msg.ticker_id == the_id }
36
- e_id = tws.subscribe(:Alert){|x| finalize = true if [200,353].include?( x.code) && x.error_id == the_id }
35
+ s_id = tws.subscribe(:TickSnapshotEnd) { |msg| queue.push(true) if msg.ticker_id == the_id }
36
+ e_id = tws.subscribe(:Alert){|x| queue.push(false) if [200,353].include?( x.code) && x.error_id == the_id }
37
+ t_id = tws.subscribe( :TickSnapshotEnd, :TickPrice, :TickString, :TickSize, :TickGeneric, :MarketDataType, :TickRequestParameters ) {|msg| msg }
37
38
  # TWS Error 200: No security definition has been found for the request
38
39
  # TWS Error 354: Requested market data is not subscribed.
39
40
 
40
41
  sub_id = tws.subscribe(:TickOption ) do |msg| #, :TickSize, :TickGeneric do |msg|
41
- if msg.ticker_id == the_id && tickdata.is_a?(Array) # do nothing if tickdata have already gathered
42
+ if msg.ticker_id == the_id # && tickdata.is_a?(Array) # do nothing if tickdata have already gathered
42
43
  case msg.type
43
44
  when /ask/
44
45
  greek.ask_price = msg.option_price unless msg.option_price.nil?
@@ -55,34 +56,36 @@ module IB
55
56
  (bf + msg.greeks.keys).each{ |a| greek.send( a.to_s+"=", msg.send( a)) }
56
57
  tickdata << msg if [ :all, :model ].include?( what )
57
58
  end
58
- tickdata = tickdata &.first unless [:bidask, :all].include? what
59
- finalize = true if tickdata.is_a?(IB::Messages::Incoming::TickOption) || (tickdata.size == 2 && what== :bidask) || (tickdata.size == 4 && what == :all)
59
+ # fast entry abortion ---> daiabled for now
60
+ # queue.push(true) if tickdata.is_a?(IB::Messages::Incoming::TickOption) || (tickdata.size == 2 && what== :bidask) || (tickdata.size == 4 && what == :all)
60
61
  end
61
62
  end # if sub_id
62
63
 
63
64
  # initialize »the_id« that is used to identify the received tick messages
64
65
  # by firing the market data request
65
- the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
66
+ iji = 0
67
+ loop do
68
+ the_id = tws.send_message :RequestMarketData, contract: self , snapshot: true
66
69
 
67
- begin
68
- # todo implement config-feature to set timeout in configuration (DRY-Feature)
69
- Timeout::timeout(5) do # max 5 sec.
70
- loop{ break if finalize ; sleep 0.05 }
71
- # reduce :close_price delayed_close to close a.s.o
72
- self.misc = tickdata if thread # store internally if in thread modus
73
- end
74
- rescue Timeout::Error
75
- Connection.logger.info{ "#{to_human} --> No Marketdata received " }
76
- end
77
- tws.unsubscribe sub_id, s_id, e_id
70
+ result = queue.pop
71
+ # reduce :close_price delayed_close to close a.s.o
72
+ if result == false
73
+ Connection.logger.info{ "#{to_human} --> No Marketdata received " }
74
+ else
75
+ self.misc = tickdata if thread # store internally if in thread modus
76
+ end
77
+ break if !tickdata.empty? || iji > 10
78
+ iji = iji + 1
79
+ Connection.logger.info{ "OptionGreeks::#{to_human} --> delayed processing. Trying again (#{iji}) " }
80
+ end
81
+ tws.unsubscribe sub_id, s_id, e_id, t_id
78
82
  end # thread
79
83
  if thread
80
84
  th # return thread
81
85
  else
82
86
  th.join
83
- tickdata # return
87
+ greek
84
88
  end
85
- end #
86
-
89
+ end
87
90
  end
88
91
  end
@@ -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
@@ -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
@@ -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=[] ; master.verify{|x| x.contract_detail= nil; l << x }
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
- leg = IB::Option.new( master.attributes.merge( strike: strike )).verify!.essential
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
- initialize_spread( master ) do | the_spread |
21
- the_spread.add_leg master, action: :buy
22
-
23
- the_other_expiry = the_other_expiry.values.first if the_other_expiry.is_a?(Hash)
24
- back = the_spread.transform_distance master.expiry, the_other_expiry
25
- the_spread.add_leg master.merge(expiry: back ), action: :sell
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
- details = nil; from.verify{|c| details = c.contract_detail }
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 nessesary, got: #{kind.inspect}" if kind.values.any?(nil)
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
- leg_prototype = IB::Option.new underlying.attributes
61
- .slice( :currency, :symbol, :exchange)
62
- .merge(defaults)
63
- .merge( fields )
64
- kind[:back] = the_spread.transform_distance kind[:front], 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
- the_spread.add_leg IB::Contract.build( leg_prototype.attributes.merge(expiry: kind[:front] )), action: :buy
67
- the_spread.add_leg IB::Contract.build( leg_prototype.attributes.merge(expiry: kind[:back])), action: :sell
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
- "<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( '|+|' )} >"
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
- .merge( fields )
46
+ .merge( fields )
47
47
 
48
48
  leg_prototype.sec_type = 'FOP' if from.is_a?(IB::Future)
49
- the_spread.add_leg IB::Contract.build leg_prototype.attributes.merge( right: :put )
50
- the_spread.add_leg IB::Contract.build leg_prototype.attributes.merge( right: :call )
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