ib-extensions 1.1 → 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.
@@ -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