ig_markets 0.3 → 0.4

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,20 +1,111 @@
1
1
  module IGMarkets
2
2
  module CLI
3
3
  # Implements the `ig_markets positions` command.
4
- class Main
5
- desc 'positions', 'Prints open positions'
4
+ class Positions < Thor
5
+ desc 'list', 'Prints open positions'
6
6
 
7
- def positions
8
- begin_session do
7
+ def list
8
+ Main.begin_session(options) do |dealing_platform|
9
9
  dealing_platform.positions.all.each do |position|
10
- print <<-END
11
- #{position.deal_id}: \
12
- #{position.formatted_size} of #{position.market.epic} at #{position.level}, \
13
- profit/loss: #{Format.currency position.profit_loss, position.currency}
14
- END
10
+ Output.print_position position
15
11
  end
16
12
  end
17
13
  end
14
+
15
+ default_task :list
16
+
17
+ desc 'create', 'Creates a new position'
18
+
19
+ option :currency_code, required: true, desc: 'The 3 character currency code, must be one of the instrument\'s ' \
20
+ 'currencies'
21
+ option :direction, required: true, desc: 'The trade direction, must be \'buy\' or \'sell\''
22
+ option :epic, required: true, desc: 'The EPIC of the market to trade'
23
+ option :expiry, desc: 'The expiry date of the instrument (if applicable), format: yyyy-mm-dd'
24
+ option :force_open, type: :boolean, default: false, desc: 'Whether a force open is required'
25
+ option :guaranteed_stop, type: :boolean, default: false, desc: 'Whether a guaranteed stop is required'
26
+ option :level, type: :numeric, desc: 'Required if and only if --order-type is \'limit\' or \'quote\''
27
+ option :limit_distance, type: :numeric, desc: 'The distance away in pips to place the limit, if set then ' \
28
+ '--limit-level must not be used'
29
+ option :limit_level, type: :numeric, desc: 'The limit level, if set then --limit-distance must not be used'
30
+ option :order_type, default: 'market', desc: 'The order type, must be \'limit\', \'market\' or \'quote\''
31
+ option :quote_id, desc: 'The Lightstreamer quote ID, required when using --order-type=quote'
32
+ option :size, type: :numeric, required: true, desc: 'The size of the position'
33
+ option :stop_distance, type: :numeric, desc: 'The distance away in pips to place the stop, if set then ' \
34
+ '--stop-level must not be used'
35
+ option :stop_level, type: :numeric, desc: 'The stop level, if set then --stop-distance must not be used'
36
+ option :time_in_force, desc: 'The order fill strategy, either \'execute_and_eliminate\' or \'fill_or_kill\''
37
+ option :trailing_stop, type: :boolean, desc: 'Whether to use a trailing stop, defaults to false'
38
+ option :trailing_stop_increment, type: :numeric, desc: 'The increment step in pips for the trailing stop, ' \
39
+ 'required when --trailing-stop is specified'
40
+
41
+ def create
42
+ Main.begin_session(options) do |dealing_platform|
43
+ deal_reference = dealing_platform.positions.create position_attributes
44
+
45
+ puts "Deal reference: #{deal_reference}"
46
+
47
+ Output.print_deal_confirmation dealing_platform.deal_confirmation(deal_reference)
48
+ end
49
+ end
50
+
51
+ desc 'update <DEAL-ID>', 'Updates attributes of an existing position'
52
+
53
+ option :limit_level, type: :numeric, desc: 'The limit level'
54
+ option :stop_level, type: :numeric, desc: 'The stop level'
55
+ option :trailing_stop, type: :boolean, desc: 'Whether to use a trailing stop, defaults to false'
56
+ option :trailing_stop_distance, type: :numeric, desc: 'The distance away in pips to place the trailing stop'
57
+ option :trailing_stop_increment, type: :numeric, desc: 'The increment step in pips for the trailing stop'
58
+
59
+ def update(deal_id)
60
+ with_position(deal_id) do |position|
61
+ position.update position_attributes
62
+ end
63
+ end
64
+
65
+ desc 'close <DEAL-ID>', 'Closes or partially closes a position'
66
+
67
+ option :level, type: :numeric, desc: 'Required if and only if --order-type is \'limit\' or \'quote\''
68
+ option :order_type, default: 'market', desc: 'The order type, must be \'limit\', \'market\' or \'quote\''
69
+ option :quote_id, desc: 'The Lightstreamer quote ID, required when using --order-type=quote'
70
+ option :size, type: :numeric, desc: 'The size of the position to close, if not specified then the entire ' \
71
+ 'position will be closed'
72
+ option :time_in_force, desc: 'The order fill strategy, either \'execute_and_eliminate\' or \'fill_or_kill\''
73
+
74
+ def close(deal_id)
75
+ with_position(deal_id) do |position|
76
+ position.close position_attributes
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def position_attributes
83
+ attributes = options.each_with_object({}) do |(key, value), new_hash|
84
+ next unless [:currency_code, :direction, :epic, :expiry, :force_open, :guaranteed_stop, :level,
85
+ :limit_distance, :limit_level, :order_type, :quote_id, :size, :stop_distance, :stop_level,
86
+ :time_in_force, :trailing_stop, :trailing_stop_increment].include? key.to_sym
87
+
88
+ new_hash[key.to_sym] = value
89
+ end
90
+
91
+ Main.parse_date_time attributes, :expiry, Date, '%F', 'yyyy-mm-dd'
92
+
93
+ attributes
94
+ end
95
+
96
+ def with_position(deal_id)
97
+ Main.begin_session(options) do |dealing_platform|
98
+ position = dealing_platform.positions[deal_id]
99
+
100
+ raise 'no position with the specified ID' unless position
101
+
102
+ deal_reference = yield position
103
+
104
+ puts "Deal reference: #{deal_reference}"
105
+
106
+ Output.print_deal_confirmation dealing_platform.deal_confirmation(deal_reference)
107
+ end
108
+ end
18
109
  end
19
110
  end
20
111
  end
@@ -2,29 +2,15 @@ module IGMarkets
2
2
  module CLI
3
3
  # Implements the `ig_markets search` command.
4
4
  class Main < Thor
5
- desc 'search', 'Searches markets based on a query string'
5
+ desc 'search <QUERY>', 'Searches markets based on the specified query string'
6
6
 
7
- option :query, aliases: '-q', required: true, desc: 'The search query'
8
-
9
- def search
10
- begin_session do
11
- dealing_platform.markets.search(options[:query]).each do |market|
12
- print_market_overview market
7
+ def search(query)
8
+ self.class.begin_session(options) do |dealing_platform|
9
+ dealing_platform.markets.search(query).each do |market_overview|
10
+ Output.print_market_overview market_overview
13
11
  end
14
12
  end
15
13
  end
16
-
17
- private
18
-
19
- def print_market_overview(market)
20
- print <<-END
21
- #{market.epic}: \
22
- #{market.instrument_name}, \
23
- type: #{market.instrument_type}, \
24
- bid: #{market.bid} \
25
- offer: #{market.offer}
26
- END
27
- end
28
14
  end
29
15
  end
30
16
  end
@@ -2,34 +2,23 @@ module IGMarkets
2
2
  module CLI
3
3
  # Implements the `ig_markets sentiment` command.
4
4
  class Main
5
- desc 'sentiment', 'Prints sentiment for the specified market'
5
+ desc 'sentiment <MARKET>', 'Prints sentiment for the specified market'
6
6
 
7
- option :market, aliases: '-m', required: true, desc: 'The name of the market to print sentiment for'
8
- option :related, aliases: '-r', type: :boolean, desc: 'Whether to print sentiment for related markets as well'
7
+ option :related, type: :boolean, desc: 'Whether to print sentiment for related markets as well'
9
8
 
10
- def sentiment
11
- begin_session do
12
- result = dealing_platform.client_sentiment[options[:market]]
9
+ def sentiment(market)
10
+ self.class.begin_session(options) do |dealing_platform|
11
+ client_sentiment = dealing_platform.client_sentiment[market]
13
12
 
14
- print_sentiment result
13
+ Output.print_client_sentiment client_sentiment
15
14
 
16
15
  if options[:related]
17
- result.related_sentiments.each do |model|
18
- print_sentiment model
16
+ client_sentiment.related_sentiments.each do |model|
17
+ Output.print_client_sentiment model
19
18
  end
20
19
  end
21
20
  end
22
21
  end
23
-
24
- private
25
-
26
- def print_sentiment(model)
27
- print <<-END
28
- #{model.market_id}: \
29
- longs: #{model.long_position_percentage}%, \
30
- shorts: #{model.short_position_percentage}%
31
- END
32
- end
33
22
  end
34
23
  end
35
24
  end
@@ -1,27 +1,55 @@
1
1
  module IGMarkets
2
2
  module CLI
3
3
  # Implements the `ig_markets sprints` command.
4
- class Main
5
- desc 'sprints', 'Prints open sprint market positions'
4
+ class Sprints < Thor
5
+ desc 'list', 'Prints open sprint market positions'
6
6
 
7
- def sprints
8
- begin_session do
9
- dealing_platform.sprint_market_positions.all.each do |sprint|
10
- print_sprint_market_position sprint
7
+ def list
8
+ Main.begin_session(options) do |dealing_platform|
9
+ dealing_platform.sprint_market_positions.all.each do |sprint_market_position|
10
+ Output.print_sprint_market_position sprint_market_position
11
11
  end
12
12
  end
13
13
  end
14
14
 
15
+ default_task :list
16
+
17
+ desc 'create', 'Creates a new sprint market position'
18
+
19
+ option :direction, required: true, desc: 'The trade direction, must be \'buy\' or \'sell\')'
20
+ option :epic, required: true, desc: 'The EPIC of the market to trade'
21
+ option :expiry_period, required: true, desc: 'The expiry period in seconds, must be 1, 2, 5, 20 or 60'
22
+ option :size, required: true, desc: 'The position size'
23
+
24
+ def create
25
+ Main.begin_session(options) do |dealing_platform|
26
+ deal_reference = dealing_platform.sprint_market_positions.create new_sprint_market_position_attributes
27
+
28
+ puts "Deal reference: #{deal_reference}"
29
+
30
+ Output.print_deal_confirmation dealing_platform.deal_confirmation deal_reference
31
+ end
32
+ end
33
+
15
34
  private
16
35
 
17
- def print_sprint_market_position(sprint)
18
- print <<-END
19
- #{sprint.deal_id}: \
20
- #{Format.currency sprint.size, sprint.currency} on #{sprint.epic} \
21
- to be #{{ buy: 'above', sell: 'below' }.fetch(sprint.direction)} #{sprint.strike_level} \
22
- in #{Format.seconds sprint.seconds_till_expiry}, \
23
- payout: #{Format.currency sprint.payout_amount, sprint.currency}
24
- END
36
+ def new_sprint_market_position_attributes
37
+ {
38
+ direction: options[:direction],
39
+ epic: options[:epic],
40
+ expiry_period: expiry_period,
41
+ size: options[:size]
42
+ }
43
+ end
44
+
45
+ def expiry_period
46
+ {
47
+ '1' => :one_minute,
48
+ '2' => :two_minutes,
49
+ '5' => :five_minutes,
50
+ '20' => :twenty_minutes,
51
+ '60' => :sixty_minutes
52
+ }.fetch options[:expiry_period]
25
53
  end
26
54
  end
27
55
  end
@@ -2,16 +2,17 @@ module IGMarkets
2
2
  module CLI
3
3
  # Implements the `ig_markets transactions` command.
4
4
  class Main
5
- desc 'transactions', 'Prints recent transactions'
5
+ desc 'transactions', 'Prints account transactions'
6
6
 
7
- option :days, default: 3, type: :numeric, desc: 'The number of days to print recent transactions for'
7
+ option :days, type: :numeric, required: true, desc: 'The number of days to print account transactions for'
8
+ option :start_date, desc: 'The start date to print account transactions from, format: yyyy-mm-dd'
8
9
 
9
10
  def transactions
10
- begin_session do
11
- transactions = dealing_platform.account.recent_transactions(seconds).sort_by(&:date)
11
+ self.class.begin_session(options) do |dealing_platform|
12
+ transactions = gather_transactions(dealing_platform, options[:days], options[:start_date]).sort_by(&:date)
12
13
 
13
14
  transactions.each do |transaction|
14
- print_transaction transaction
15
+ Output.print_transaction transaction
15
16
  end
16
17
 
17
18
  print_transaction_totals transactions
@@ -20,6 +21,16 @@ module IGMarkets
20
21
 
21
22
  private
22
23
 
24
+ def gather_transactions(dealing_platform, days, start_date = nil)
25
+ if start_date
26
+ start_date = Date.strptime options[:start_date], '%F'
27
+
28
+ dealing_platform.account.transactions_in_date_range start_date, start_date + days.to_i
29
+ else
30
+ dealing_platform.account.recent_transactions days
31
+ end
32
+ end
33
+
23
34
  def transaction_totals(transactions)
24
35
  transactions.each_with_object({}) do |transaction, hash|
25
36
  profit_loss = transaction.profit_and_loss_amount
@@ -31,19 +42,9 @@ module IGMarkets
31
42
  end
32
43
  end
33
44
 
34
- def print_transaction(transaction)
35
- print <<-END
36
- #{transaction.reference}: #{transaction.date.strftime '%Y-%m-%d'}, \
37
- #{transaction.formatted_transaction_type}, \
38
- #{"#{transaction.size} of " if transaction.size}\
39
- #{transaction.instrument_name}, \
40
- profit/loss: #{Format.currency transaction.profit_and_loss_amount, transaction.currency}
41
- END
42
- end
43
-
44
45
  def print_transaction_totals(transactions)
45
46
  transaction_totals(transactions).each do |currency, value|
46
- print <<-END
47
+ puts <<-END
47
48
 
48
49
  Totals for currency '#{currency}':
49
50
  Interest: #{Format.currency value[:interest], currency}
@@ -1,33 +1,72 @@
1
1
  module IGMarkets
2
2
  module CLI
3
3
  # Implements the `ig_markets watchlists` command.
4
- class Main
5
- desc 'watchlists', 'Prints all watchlists and their markets'
4
+ class Watchlists < Thor
5
+ desc 'list', 'Prints all watchlists and their markets'
6
6
 
7
- def watchlists
8
- begin_session do
7
+ def list
8
+ Main.begin_session(options) do |dealing_platform|
9
9
  dealing_platform.watchlists.all.each do |watchlist|
10
- print_watchlist watchlist
10
+ Output.print_watchlist watchlist
11
11
 
12
- watchlist.markets.each do |market|
12
+ watchlist.markets.each do |market_overview|
13
13
  print ' - '
14
- print_market_overview market
14
+ Output.print_market_overview market_overview
15
15
  end
16
- print "\n"
16
+
17
+ puts ''
18
+ end
19
+ end
20
+ end
21
+
22
+ default_task :list
23
+
24
+ desc 'create <NAME> [<EPIC> <EPIC> ...]', 'Creates a new watchlist with the specified name and EPICs'
25
+
26
+ def create(name, *epics)
27
+ Main.begin_session(options) do |dealing_platform|
28
+ new_watchlist = dealing_platform.watchlists.create(name, *epics)
29
+
30
+ puts "New watchlist ID: #{new_watchlist.id}"
31
+ end
32
+ end
33
+
34
+ desc 'add-markets <WATCHLIST-ID> <EPIC> [<EPIC> ...]', 'Adds the specified markets to a watchlist'
35
+
36
+ def add_markets(watchlist_id, *epics)
37
+ with_watchlist(watchlist_id) do |watchlist|
38
+ epics.each do |epic|
39
+ watchlist.add_market epic
17
40
  end
18
41
  end
19
42
  end
20
43
 
44
+ desc 'remove-markets <WATCHLIST-ID> <EPIC> [<EPIC> ...]', 'Removes the specified markets from a watchlist'
45
+
46
+ def remove_markets(watchlist_id, *epics)
47
+ with_watchlist(watchlist_id) do |watchlist|
48
+ epics.each do |epic|
49
+ watchlist.remove_market epic
50
+ end
51
+ end
52
+ end
53
+
54
+ desc 'delete <WATCHLIST-ID>', 'Deletes the watchlist with the specified ID'
55
+
56
+ def delete(watchlist_id)
57
+ with_watchlist(watchlist_id, &:delete)
58
+ end
59
+
21
60
  private
22
61
 
23
- def print_watchlist(watchlist)
24
- print <<-END
25
- #{watchlist.id}: \
26
- #{watchlist.name}, \
27
- editable: #{watchlist.editable}, \
28
- deleteable: #{watchlist.deleteable}, \
29
- default: #{watchlist.default_system_watchlist}
30
- END
62
+ def with_watchlist(watchlist_id)
63
+ Main.begin_session(options) do |dealing_platform|
64
+ watchlist = dealing_platform.watchlists[watchlist_id]
65
+
66
+ raise 'no watchlist with the specified ID' unless watchlist
67
+
68
+ yield watchlist
69
+ end
31
70
  end
32
71
  end
33
72
  end
@@ -29,13 +29,13 @@ module IGMarkets
29
29
  @dealing_platform.gather "history/activity/#{from_date}/#{to_date}", :activities, AccountActivity
30
30
  end
31
31
 
32
- # Returns all account activities that occurred in the most recent specified number of seconds.
32
+ # Returns all account activities that occurred in the most recent specified number of days.
33
33
  #
34
- # @param [Integer, Float] seconds The number of seconds to return recent activities for.
34
+ # @param [Fixnum, Float] days The number of days to return recent activities for.
35
35
  #
36
36
  # @return [Array<AccountActivity>]
37
- def recent_activities(seconds)
38
- @dealing_platform.gather "history/activity/#{(seconds * 1000.0).to_i}", :activities, AccountActivity
37
+ def recent_activities(days)
38
+ @dealing_platform.gather "history/activity/#{milliseconds(days)}", :activities, AccountActivity
39
39
  end
40
40
 
41
41
  # Returns all transactions that occurred in the specified date range.
@@ -56,16 +56,16 @@ module IGMarkets
56
56
  @dealing_platform.gather url, :transactions, AccountTransaction
57
57
  end
58
58
 
59
- # Returns all transactions that occurred in the last specified number of seconds.
59
+ # Returns all transactions that occurred in the last specified number of days.
60
60
  #
61
- # @param [Integer, Float] seconds The number of seconds to return recent transactions for.
61
+ # @param [Fixnum, Float] days The number of days to return recent transactions for.
62
62
  # @param [:all, :all_deal, :deposit, :withdrawal] transaction_type The type of transactions to return.
63
63
  #
64
64
  # @return [Array<AccountTransaction>]
65
- def recent_transactions(seconds, transaction_type = :all)
65
+ def recent_transactions(days, transaction_type = :all)
66
66
  validate_transaction_type transaction_type
67
67
 
68
- url = "history/transactions/#{transaction_type.to_s.upcase}/#{(seconds * 1000.0).to_i}"
68
+ url = "history/transactions/#{transaction_type.to_s.upcase}/#{milliseconds(days)}"
69
69
 
70
70
  @dealing_platform.gather url, :transactions, AccountTransaction
71
71
  end
@@ -87,6 +87,15 @@ module IGMarkets
87
87
  def format_date(date)
88
88
  date.strftime '%d-%m-%Y'
89
89
  end
90
+
91
+ # Converts a number of days into a number of milliseconds.
92
+ #
93
+ # @param [Fixnum, Float] days The number of days.
94
+ #
95
+ # @return [Fixnum]
96
+ def milliseconds(days)
97
+ (days.to_f * 24 * 60 * 60 * 1000).to_i
98
+ end
90
99
  end
91
100
  end
92
101
  end
@@ -36,7 +36,7 @@ module IGMarkets
36
36
  # currencies (see {Instrument#currencies}). Required.
37
37
  # @option attributes [:buy, :sell] :direction The position direction. Required.
38
38
  # @option attributes [String] :epic The EPIC of the instrument to create a position for. Required.
39
- # @option attributes [Time] :expiry The expiry date of the instrument, if it has one. Optional.
39
+ # @option attributes [Date] :expiry The expiry date of the instrument, if it has one. Optional.
40
40
  # @option attributes [Boolean] :force_open Whether a force open is required. Defaults to `false`.
41
41
  # @option attributes [Boolean] :guaranteed_stop Whether a guaranteed stop is required. Defaults to `false`.
42
42
  # @option attributes [Float] :level Required if and only if `:order_type` is `:limit` or `:quote`.
@@ -68,11 +68,6 @@ module IGMarkets
68
68
  # @return [String] The resulting deal reference, use {DealingPlatform#deal_confirmation} to check the result of
69
69
  # the position creation.
70
70
  def create(attributes)
71
- attributes[:force_open] = false unless attributes.key? :force_open
72
- attributes[:guaranteed_stop] = false unless attributes.key? :guaranteed_stop
73
- attributes[:order_type] ||= :market
74
- attributes[:time_in_force] = :execute_and_eliminate if attributes[:order_type] == :market
75
-
76
71
  model = PositionCreateAttributes.new attributes
77
72
  model.validate
78
73
 
@@ -89,7 +84,7 @@ module IGMarkets
89
84
  attribute :currency_code, String, regex: Regex::CURRENCY
90
85
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
91
86
  attribute :epic, String, regex: Regex::EPIC
92
- attribute :expiry, Time, format: '%d-%b-%y'
87
+ attribute :expiry, Date, format: '%d-%b-%y'
93
88
  attribute :force_open, Boolean
94
89
  attribute :guaranteed_stop, Boolean
95
90
  attribute :level, Float
@@ -104,10 +99,16 @@ module IGMarkets
104
99
  attribute :trailing_stop, Boolean
105
100
  attribute :trailing_stop_increment, Fixnum
106
101
 
102
+ def initialize(attributes = {})
103
+ super
104
+
105
+ set_defaults
106
+ end
107
+
107
108
  # Runs a series of validations on this model's attributes to check whether it is ready to be sent to the IG
108
109
  # Markets API.
109
110
  def validate
110
- validate_required_attributes_present
111
+ validate_required_attributes
111
112
  Position.validate_order_type_constraints attributes
112
113
  validate_trailing_stop_constraints
113
114
  validate_stop_and_limit_constraints
@@ -116,8 +117,15 @@ module IGMarkets
116
117
 
117
118
  private
118
119
 
120
+ def set_defaults
121
+ self.force_open = false if force_open.nil?
122
+ self.guaranteed_stop = false if guaranteed_stop.nil?
123
+ self.order_type ||= :market
124
+ self.time_in_force = :execute_and_eliminate if order_type == :market
125
+ end
126
+
119
127
  # Checks that all required attributes for position creation are present.
120
- def validate_required_attributes_present
128
+ def validate_required_attributes
121
129
  required = [:currency_code, :direction, :epic, :force_open, :guaranteed_stop, :order_type, :size,
122
130
  :time_in_force]
123
131
 
@@ -40,19 +40,15 @@ module IGMarkets
40
40
  # currencies (see {Instrument#currencies}). Required.
41
41
  # @option attributes [:buy, :sell] :direction Order direction. Required.
42
42
  # @option attributes [String] :epic The EPIC of the instrument for the order. Required.
43
- # @option attributes [Time] :expiry The expiry date of the instrument (if applicable). Optional.
43
+ # @option attributes [Date] :expiry The expiry date of the instrument (if applicable). Optional.
44
44
  # @option attributes [Boolean] :force_open Whether a force open is required. Defaults to `false`.
45
- # @option attributes [Time] :good_till_date The date that the working order will live till. Must be set if
46
- # `:time_in_force` is `:good_till_date`.
45
+ # @option attributes [Time] :good_till_date The date that the working order will live till. If not specified then
46
+ # the working order will remain until it is cancelled.
47
47
  # @option attributes [Boolean] :guaranteed_stop Whether a guaranteed stop is required. Defaults to `false`.
48
- # @option attributes [Float] :level The level at which the order will be triggered.
48
+ # @option attributes [Float] :level The level at which the order will be triggered. Required.
49
49
  # @option attributes [Fixnum] :limit_distance The distance away in pips to place the limit. Optional.
50
50
  # @option attributes [Float] :size The size of the working order. Required.
51
51
  # @option attributes [Fixnum] :stop_distance The distance away in pips to place the stop. Optional.
52
- # @option attributes [:good_till_cancelled, :good_till_date] :time_in_force The lifespan of the working order.
53
- # `:good_till_cancelled` means the working order will live until it is explicitly cancelled.
54
- # `:good_till_date` means the working order will live until the date specified by
55
- # `:good_till_date`. Defaults to `:good_till_cancelled`.
56
52
  # @option attributes [:limit, :stop] :type `:limit` means the working order is intended to buy at a price lower
57
53
  # than at present, or to sell at a price higher than at present, i.e. there is an expectation
58
54
  # of a market reversal at the specified `:level`. `:stop` means the working order is intended
@@ -64,7 +60,7 @@ module IGMarkets
64
60
  def create(attributes)
65
61
  attributes[:force_open] ||= false
66
62
  attributes[:guaranteed_stop] ||= false
67
- attributes[:time_in_force] ||= :good_till_cancelled
63
+ attributes[:time_in_force] = attributes[:good_till_date] ? :good_till_date : :good_till_cancelled
68
64
 
69
65
  model = build_working_order_model attributes
70
66
 
@@ -85,10 +81,6 @@ module IGMarkets
85
81
  raise ArgumentError, "#{attribute} attribute must be set" if attributes[attribute].nil?
86
82
  end
87
83
 
88
- if model.time_in_force == :good_till_date && !model.good_till_date.is_a?(Time)
89
- raise ArgumentError, 'good_till_date must be set when time_in_force is :good_till_date'
90
- end
91
-
92
84
  model
93
85
  end
94
86
 
@@ -97,9 +89,9 @@ module IGMarkets
97
89
  attribute :currency_code, String, regex: Regex::CURRENCY
98
90
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
99
91
  attribute :epic, String, regex: Regex::EPIC
100
- attribute :expiry, Time, format: '%d-%b-%y'
92
+ attribute :expiry, Date, format: '%d-%b-%y'
101
93
  attribute :force_open, Boolean
102
- attribute :good_till_date, Time, format: '%F %T'
94
+ attribute :good_till_date, Time, format: '%Y/%m/%d %R:%S'
103
95
  attribute :guaranteed_stop, Boolean
104
96
  attribute :level, Float
105
97
  attribute :limit_distance, Fixnum
@@ -64,15 +64,24 @@ module IGMarkets
64
64
  raise ArgumentError, "#{self}: invalid or missing time format" unless options[:format].is_a? String
65
65
 
66
66
  if value.is_a?(String) || value.is_a?(Fixnum)
67
- begin
68
- Time.strptime "#{value}#{options[:time_zone]}", "#{options[:format]}#{'%z' if options[:time_zone]}"
69
- rescue ArgumentError
70
- raise ArgumentError, "#{self}: failed parsing time '#{value}' with format '#{options[:format]}'"
71
- end
67
+ parse_time_from_string value.to_s, options
72
68
  else
73
69
  value
74
70
  end
75
71
  end
72
+
73
+ def parse_time_from_string(value, options)
74
+ format = options[:format]
75
+ time_zone = options[:time_zone]
76
+
77
+ time_zone ||= '+0000' unless format == '%Q'
78
+
79
+ begin
80
+ Time.strptime "#{value}#{time_zone}", "#{format}#{'%z' if time_zone}"
81
+ rescue ArgumentError
82
+ raise ArgumentError, "#{self}: failed parsing time '#{value}' with format '#{format}'"
83
+ end
84
+ end
76
85
  end
77
86
  end
78
87
  end
@@ -90,7 +90,8 @@ module IGMarkets
90
90
  # @option options [String] :format When `type` is `Date` or `Time` this specifies the format for parsing String
91
91
  # and `Fixnum` instances assigned to this attribute.
92
92
  # @option options [String] :time_zone When `type` is `Time` this specifies the time zone to append to
93
- # `String` values assigned to this attribute prior to parsing them with `:format`. Optional.
93
+ # `String` values assigned to this attribute prior to parsing them with `:format`. Defaults to
94
+ # `+0000` (UTC) unless `:format` is `%Q`.
94
95
  #
95
96
  # @macro [attach] attribute
96
97
  # The $1 attribute.
@@ -4,9 +4,18 @@ module IGMarkets
4
4
  # @return [OpenSSL::PKey::RSA] The public key used by {#encrypt}, can also be set using {#encoded_public_key=}.
5
5
  attr_accessor :public_key
6
6
 
7
- # @return [String] The timestamp used by {#encrypt}.
7
+ # @return [String] The time stamp used by {#encrypt}.
8
8
  attr_accessor :time_stamp
9
9
 
10
+ # Initializes this password encryptor with the specified encoded public key and timestamp.
11
+ #
12
+ # @param [String] encoded_public_key
13
+ # @param [String] time_stamp
14
+ def initialize(encoded_public_key = nil, time_stamp = nil)
15
+ self.encoded_public_key = encoded_public_key if encoded_public_key
16
+ self.time_stamp = time_stamp
17
+ end
18
+
10
19
  # Takes an encoded public key and calls {#public_key=} with the decoded key.
11
20
  #
12
21
  # @param [String] encoded_public_key The public key encoded in Base64.