ig_markets 0.3 → 0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.