ig_markets 0.12 → 0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7e50f2a597b16e6d091f0eff038350ec67847f60
4
- data.tar.gz: 769b2056a2a1db136b05947fccbe42127ff54809
3
+ metadata.gz: d08c5bda8c3b5efc6a42dff92de064a76e42fa24
4
+ data.tar.gz: defcb9eaeb04b87f3772911e66b849a22ab091e2
5
5
  SHA512:
6
- metadata.gz: 091827f393098b7b5b19df4686832ce96dd630038aee32ab15af550bfbdae5c4236bb238993198c9caae96273d02b83d8f34dd8d287dc4456b068e974969a3f0
7
- data.tar.gz: 4ee120e6a912fdeef9ee98752c15559800ee3b406f168d1bb68524ee213b42a92afb7638dcf1675747bab6e6ed634f2a7030a41cb009b2235ff9ef5f090d17ad
6
+ metadata.gz: 80efc7ada830d2c5758c0c58dd5f2e666c0c4f315bd196e1de8c25423b8a901be0ac4cac40ddc6f189ca8bedf3d48001caa925b149fa7afb62340f3e32e41a6b
7
+ data.tar.gz: 4eace6c040b07b374f878c9a621a8e090781758b345da829a841f0b8868c406b3f15b6e27f8166627fae33efebed7a4282e4b0367afce45492477c2e965fd861
data/CHANGELOG.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # IG Markets Changelog
2
2
 
3
- ### 0.12 - June 21, 2016
3
+ ### 0.13 Unreleased
4
+
5
+ - Added `ig_markets performance` command that summarizes an account's dealing performance over a specified period
6
+ - Added `ig_markets markets` command which prints the current state of all the passed EPICs
7
+ - Correctly handle instrument periods that are formatted as `DD-MMM-YY`
8
+ - The `:type` option to `IGMarkets::AccountMethods::transactions` is now validated
9
+ - The platform type previously referred to as `production` is now referred to as `live` to better match standard IG
10
+ terminology, existing calls to `IGMarkets::DealingPlatform#sign_in` and `IGMarkets::Session#sign_in` will need to be
11
+ updated if they explicitly specified `:production`
12
+
13
+ ### 0.12 — June 21, 2016
4
14
 
5
15
  - Unrecognized attributes returned by the IG Markets API now trigger a warning rather than causing an error, this makes
6
16
  the library able to handle new return values being added to existing APIs
@@ -116,7 +126,7 @@
116
126
  - `IGMarkets::Model` now has separate `Date` and `Time` attribute types, and a new `:time_zone` option is used for
117
127
  `Time` attributes that have a known time zone. Previous uses of `DateTime` should be replaced with either `Date` or
118
128
  `Time`.
119
- - Changed `size` attribute to always be of type `Float` on all models.
129
+ - Changed `size` attribute to always be of type `Float` on all models
120
130
  - Changed `limit_distance` and `stop_distance` attributes to always be of type `Fixnum` on all models
121
131
  - Added `IGMarkets::Format` module
122
132
  - Added `#expired?` and `#seconds_till_expiry` to `IGMarkets::SprintMarketPosition`
data/README.md CHANGED
@@ -22,7 +22,7 @@ Includes support for:
22
22
  * Watchlists
23
23
  * Client sentiment
24
24
 
25
- An IG Markets production or demo trading account is needed in order to use this gem.
25
+ An IG Markets live or demo trading account is needed in order to use this gem.
26
26
 
27
27
  ## License
28
28
 
@@ -71,10 +71,12 @@ commands and their subcommands is:
71
71
  - `ig_markets confirmation DEAL-REFERENCE`
72
72
  - `ig_markets console`
73
73
  - `ig_markets help [COMMAND]`
74
+ - `ig_markets markets EPICS`
74
75
  - `ig_markets orders [list]`
75
76
  - `ig_markets orders create ...`
76
77
  - `ig_markets orders update DEAL-ID ...`
77
78
  - `ig_markets orders delete DEAL-ID`
79
+ - `ig_markets performance --days N [...]`
78
80
  - `ig_markets positions [list] [...]`
79
81
  - `ig_markets positions create ...`
80
82
  - `ig_markets positions update DEAL-ID ...`
@@ -104,6 +106,9 @@ ig_markets transactions --days 7 --instrument EUR/USD --no-interest
104
106
  # Search for EURUSD currency markets
105
107
  ig_markets search EURUSD --type currencies
106
108
 
109
+ # Print details for the EURUSD currency pair and the Dow Jones Industrial Average
110
+ ig_markets markets CS.D.EURUSD.CFD.IP IX.D.DOW.IFD.IP
111
+
107
112
  # Print current positions in aggregate
108
113
  ig_markets positions --aggregate
109
114
 
@@ -130,6 +135,9 @@ ig_markets orders create --direction buy --epic CS.D.EURUSD.CFD.IP --level 1.1 -
130
135
  # Print daily prices for EURUSD from the last two weeks
131
136
  ig_markets prices --epic CS.D.EURUSD.CFD.IP --resolution day --number 14
132
137
 
138
+ # Print account dealing performance from the last 90 days, broken down by the EPICs that were traded
139
+ ig_markets performance --days 90
140
+
133
141
  # Log in and open a Ruby console which can be used to query the IG API, printing all REST requests
134
142
  ig_markets console --verbose
135
143
  ```
@@ -157,7 +165,7 @@ ig.account.activities from: Date.today - 7
157
165
  ig.account.activities from: Date.today - 14, to: Date.today - 7
158
166
  ig.account.transactions from: Date.today - 7
159
167
  ig.account.transactions from: Date.today - 14, to: Date.today - 7
160
- ig.account.transactions from: Date.today - 14, to: Date.today - 7, type: :deal
168
+ ig.account.transactions from: Date.today - 14, to: Date.today - 7, type: :withdrawal
161
169
 
162
170
  # Dealing
163
171
  ig.deal_confirmation 'deal_reference'
@@ -187,7 +195,7 @@ ig.working_orders['deal_id'].delete
187
195
  # Markets
188
196
  ig.markets.hierarchy
189
197
  ig.markets.search 'EURUSD'
190
- ig.markets['CS.D.EURUSD.CFD.IP']
198
+ ig.markets.find 'CS.D.EURUSD.CFD.IP', 'IX.D.DOW.IFD.IP'
191
199
  ig.markets['CS.D.EURUSD.CFD.IP'].historical_prices resolution: :hour, number: 48
192
200
  ig.markets['CS.D.EURUSD.CFD.IP'].historical_prices resolution: :second, from: Time.now - 120,
193
201
  to: Time.now - 60
@@ -39,7 +39,7 @@ module IGMarkets
39
39
  attribute :description
40
40
  attribute :details, Details
41
41
  attribute :epic, String, regex: Regex::EPIC
42
- attribute :period, Time, nil_if: %w(- DFB), format: ['%FT%T', '%b-%y']
42
+ attribute :period, Time, nil_if: %w(- DFB), format: ['%FT%T', '%d-%b-%y', '%b-%y']
43
43
  attribute :status, Symbol, allowed_values: [:accepted, :rejected, :unknown]
44
44
  attribute :type, Symbol, allowed_values: [:edit_stop_and_limit, :position, :system, :working_order]
45
45
  end
@@ -0,0 +1,18 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets markets` command.
4
+ class Main < Thor
5
+ desc 'markets EPICS', 'Prints the state of the markets with the specified EPICs'
6
+
7
+ def markets(*epics)
8
+ self.class.begin_session(options) do |dealing_platform|
9
+ markets = dealing_platform.markets.find(*epics)
10
+
11
+ table = MarketsTable.new markets
12
+
13
+ puts table
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,73 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets performance` command.
4
+ class Main
5
+ desc 'performance', 'Prints a summary of trading performance over a period'
6
+
7
+ option :days, type: :numeric, required: true, desc: 'The number of days to print performance for'
8
+ option :from, desc: 'The start date to show performance from, format: yyyy-mm-dd'
9
+
10
+ def performance
11
+ self.class.begin_session(options) do |dealing_platform|
12
+ performances = gather_performances dealing_platform
13
+ lookup_instrument_names performances, dealing_platform
14
+
15
+ table = PerformancesTable.new performances
16
+
17
+ puts table
18
+
19
+ print_summary performances
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def gather_performances(dealing_platform)
26
+ performances = deal_transactions_by_epic(dealing_platform).map do |epic, transactions|
27
+ profit_loss = transactions.map(&:profit_and_loss_amount).inject(&:+)
28
+
29
+ { epic: epic, transactions: transactions, profit_loss: profit_loss }
30
+ end
31
+
32
+ performances.sort_by { |a| a[:profit_loss] }
33
+ end
34
+
35
+ def deal_transactions_by_epic(dealing_platform)
36
+ activities = dealing_platform.account.activities history_options
37
+
38
+ deal_transactions(dealing_platform).group_by do |transaction|
39
+ activities.detect do |activity|
40
+ Regexp.new("^Position(\/s| partially) closed:.*#{transaction.reference}").match activity.description
41
+ end.epic
42
+ end
43
+ end
44
+
45
+ def deal_transactions(dealing_platform)
46
+ dealing_platform.account.transactions(history_options).select do |transaction|
47
+ transaction.transaction_type == :deal
48
+ end
49
+ end
50
+
51
+ def lookup_instrument_names(performances, dealing_platform)
52
+ markets = dealing_platform.markets.find(*performances.map { |a| a[:epic] })
53
+
54
+ performances.each do |a|
55
+ a[:instrument_name] = markets.detect { |market| market.instrument.epic == a[:epic] }.instrument.name
56
+ end
57
+ end
58
+
59
+ def print_summary(performances)
60
+ profit_loss = performances.map { |h| h[:profit_loss] }.inject(:+)
61
+ currency = performances.first[:transactions].first.currency
62
+
63
+ puts <<-END
64
+
65
+ Note: this table only shows the profit/loss made from dealing, it does not include interest payments,
66
+ dividends, or other adjustments that may have occurred over this period.
67
+
68
+ Total: #{Format.currency(profit_loss, currency).colorize(profit_loss < 0 ? :red : :green)}
69
+ END
70
+ end
71
+ end
72
+ end
73
+ end
@@ -6,7 +6,7 @@ module IGMarkets
6
6
  class_option :username, required: true, desc: 'The username for the session'
7
7
  class_option :password, required: true, desc: 'The password for the session'
8
8
  class_option :api_key, required: true, desc: 'The API key for the session'
9
- class_option :demo, type: :boolean, desc: 'Use the demo platform (default is production)'
9
+ class_option :demo, type: :boolean, desc: 'Use the demo platform (default is the live platform)'
10
10
  class_option :verbose, type: :boolean, desc: 'Whether to print the raw REST API requests and responses'
11
11
 
12
12
  desc 'orders [SUBCOMAND=list ...]', 'Command for working with orders'
@@ -59,7 +59,7 @@ module IGMarkets
59
59
  #
60
60
  # @param [Thor::CoreExt::HashWithIndifferentAccess] options The Thor options hash.
61
61
  def begin_session(options)
62
- platform = options[:demo] ? :demo : :production
62
+ platform = options[:demo] ? :demo : :live
63
63
 
64
64
  RequestPrinter.enabled = true if options[:verbose]
65
65
 
@@ -9,7 +9,7 @@ module IGMarkets
9
9
  end
10
10
 
11
11
  def headings
12
- ['Type', 'EPIC', 'Instrument', 'Status', 'Expiry', 'Bid', 'Offer', 'High', 'Low', 'Change (net)', 'Change (%)']
12
+ ['EPIC', 'Type', 'Instrument', 'Status', 'Expiry', 'Bid', 'Offer', 'High', 'Low', 'Change (net)', 'Change (%)']
13
13
  end
14
14
 
15
15
  def right_aligned_columns
@@ -17,8 +17,8 @@ module IGMarkets
17
17
  end
18
18
 
19
19
  def row(market_overview)
20
- [market_overview.instrument_type, market_overview.epic, market_overview.instrument_name,
21
- market_status(market_overview), market_overview.expiry, levels(market_overview)]
20
+ [market_overview.epic, market_overview.instrument_type, market_overview.instrument_name,
21
+ market_overview.market_status, market_overview.expiry, levels(market_overview)]
22
22
  end
23
23
 
24
24
  def cell_color(value, _model, _row_index, column_index)
@@ -36,12 +36,6 @@ module IGMarkets
36
36
  Format.level market_overview.send(attribute)
37
37
  end
38
38
  end
39
-
40
- def market_status(market_overview)
41
- { closed: 'Closed', edits_only: 'Edits only', offline: 'Offline', on_auction: 'On auction',
42
- on_auction_no_edits: 'On auction no edits', suspended: 'Suspended', tradeable: 'Tradeable' }
43
- .fetch market_overview.market_status
44
- end
45
39
  end
46
40
  end
47
41
  end
@@ -0,0 +1,13 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::Market} instances in a table.
4
+ class MarketsTable < MarketOverviewsTable
5
+ private
6
+
7
+ def row(market)
8
+ [market.instrument.epic, market.instrument.type, market.instrument.name, market.snapshot.market_status,
9
+ market.instrument.expiry, levels(market.snapshot)]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out account performance details in a table.
4
+ class PerformancesTable < Table
5
+ private
6
+
7
+ def default_title
8
+ 'Dealing performance'
9
+ end
10
+
11
+ def headings
12
+ ['EPIC', 'Instrument name', '# of closed deals', 'Profit/loss']
13
+ end
14
+
15
+ def right_aligned_columns
16
+ [2, 3]
17
+ end
18
+
19
+ def row(model)
20
+ transactions = model.fetch :transactions
21
+
22
+ [model.fetch(:epic), model.fetch(:instrument_name), transactions.size,
23
+ Format.currency(model.fetch(:profit_loss), transactions.first.currency)]
24
+ end
25
+
26
+ def cell_color(value, _transaction, _row_index, column_index)
27
+ return unless headings[column_index] == 'Profit/loss'
28
+
29
+ if value =~ /-/
30
+ :red
31
+ else
32
+ :green
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -45,6 +45,10 @@ module IGMarkets
45
45
  def transactions(options)
46
46
  options[:type] ||= :all
47
47
 
48
+ unless [:all, :all_deal, :deposit, :withdrawal].include? options[:type]
49
+ raise ArgumentError, "Invalid transaction type: #{options[:type]}"
50
+ end
51
+
48
52
  history_request_complete url: 'history/transactions', url_parameters: history_url_parameters(options),
49
53
  api_version: API_V2, collection_name: :transactions, model_class: Transaction,
50
54
  date_attribute: :date_utc
@@ -50,12 +50,12 @@ module IGMarkets
50
50
  @working_orders = WorkingOrderMethods.new self
51
51
  end
52
52
 
53
- # Signs in to the IG Markets Dealing Platform, either the production platform or the demo platform.
53
+ # Signs in to the IG Markets Dealing Platform, either the live platform or the demo platform.
54
54
  #
55
55
  # @param [String] username The account username.
56
56
  # @param [String] password The account password.
57
57
  # @param [String] api_key The account API key.
58
- # @param [:production, :demo] platform The platform to use.
58
+ # @param [:live, :demo] platform The platform to use.
59
59
  #
60
60
  # @return [ClientAccountSummary] The client account summary returned by the sign in request.
61
61
  def sign_in(username, password, api_key, platform)
@@ -102,10 +102,10 @@ module IGMarkets
102
102
  # of each attribute as defined on the models. All model instances returned by this method will have their
103
103
  # `@dealing_platform` instance variable set.
104
104
  #
105
- # @param [Class] model_class The type of model to create from `source`.
105
+ # @param [Class] model_class The top-level model class to create from `source`.
106
106
  # @param [nil, Hash, Array, Model] source The source object to construct the model(s) from. If `nil` then `nil` is
107
107
  # returned. If an instance of `model_class` subclass then a deep copy of it is
108
- # returned. If a `Hash` then it will be interprted as the attributes for a new
108
+ # returned. If a `Hash` then it will be interpreted as the attributes for a new
109
109
  # instance of `model_class. If an `Array` then each entry will be passed through
110
110
  # this method individually.
111
111
  #
@@ -113,13 +113,11 @@ module IGMarkets
113
113
  def instantiate_models(model_class, source)
114
114
  return nil if source.nil?
115
115
 
116
- source = source.attributes if source.is_a? model_class
116
+ source = prepare_source model_class, source
117
117
 
118
118
  if source.is_a? Array
119
119
  source.map { |entry| instantiate_models model_class, entry }
120
120
  elsif source.is_a? Hash
121
- source = model_class.adjusted_api_attributes source if model_class.respond_to? :adjusted_api_attributes
122
-
123
121
  instantiate_model_from_attributes_hash model_class, source
124
122
  else
125
123
  raise ArgumentError, "#{model_class}: can't instantiate from a source of type #{source.class}"
@@ -128,6 +126,17 @@ module IGMarkets
128
126
 
129
127
  private
130
128
 
129
+ # This method is a helper for {#instantiate_models} that prepares a source object for instantiation.
130
+ def prepare_source(model_class, source)
131
+ source = source.attributes if source.is_a? model_class
132
+
133
+ if source.is_a?(Hash) && model_class.respond_to?(:adjusted_api_attributes)
134
+ source = model_class.adjusted_api_attributes source
135
+ end
136
+
137
+ source
138
+ end
139
+
131
140
  # This method is a companion to {#instantiate_models} and creates a single instance of `model_class` from the passed
132
141
  # attributes hash, setting the `@dealing_platform` instance variable on the new model instance.
133
142
  def instantiate_model_from_attributes_hash(model_class, attributes)
@@ -1,7 +1,7 @@
1
1
  module IGMarkets
2
2
  # Manages a session with the IG Markets REST API, including signing in, signing out, and the sending of requests.
3
3
  # In order to sign in, {#username}, {#password}, {#api_key} and {#platform} must be set. {#platform} must be
4
- # either `:demo` or `:production` depending on which platform is being targeted.
4
+ # either `:demo` or `:live` depending on which platform is being targeted.
5
5
  class Session
6
6
  # @return [String] The username to use to authenticate this session.
7
7
  attr_accessor :username
@@ -12,7 +12,7 @@ module IGMarkets
12
12
  # @return [String] The API key to use to authenticate this session.
13
13
  attr_accessor :api_key
14
14
 
15
- # @return [:demo, :production] The platform variant to log into for this session.
15
+ # @return [:demo, :live] The platform variant to log into for this session.
16
16
  attr_accessor :platform
17
17
 
18
18
  # @return [String] The client session security access token for the currently logged in session, or `nil` if there
@@ -103,7 +103,7 @@ module IGMarkets
103
103
 
104
104
  HOST_URLS = {
105
105
  demo: 'https://demo-api.ig.com/gateway/deal/',
106
- production: 'https://api.ig.com/gateway/deal/'
106
+ live: 'https://api.ig.com/gateway/deal/'
107
107
  }.freeze
108
108
 
109
109
  def validate_authentication
@@ -8,7 +8,7 @@ module IGMarkets
8
8
  attribute :date_utc, Time, format: '%FT%T'
9
9
  attribute :instrument_name
10
10
  attribute :open_level, String, nil_if: %w(- 0)
11
- attribute :period, Time, nil_if: %w(- DFB), format: ['%FT%T', '%b-%y']
11
+ attribute :period, Time, nil_if: %w(- DFB), format: ['%FT%T', '%d-%b-%y', '%b-%y']
12
12
  attribute :profit_and_loss
13
13
  attribute :reference
14
14
  attribute :size, String, nil_if: '-'
@@ -1,4 +1,4 @@
1
1
  module IGMarkets
2
2
  # The version of this gem.
3
- VERSION = '0.12'.freeze
3
+ VERSION = '0.13'.freeze
4
4
  end
data/lib/ig_markets.rb CHANGED
@@ -55,6 +55,8 @@ require 'ig_markets/cli/commands/account_command'
55
55
  require 'ig_markets/cli/commands/activities_command'
56
56
  require 'ig_markets/cli/commands/confirmation_command'
57
57
  require 'ig_markets/cli/commands/console_command'
58
+ require 'ig_markets/cli/commands/markets_command'
59
+ require 'ig_markets/cli/commands/performance_command'
58
60
  require 'ig_markets/cli/commands/prices_command'
59
61
  require 'ig_markets/cli/commands/search_command'
60
62
  require 'ig_markets/cli/commands/sentiment_command'
@@ -65,6 +67,8 @@ require 'ig_markets/cli/tables/activities_table'
65
67
  require 'ig_markets/cli/tables/client_sentiments_table'
66
68
  require 'ig_markets/cli/tables/historical_price_result_snapshots_table'
67
69
  require 'ig_markets/cli/tables/market_overviews_table'
70
+ require 'ig_markets/cli/tables/markets_table'
71
+ require 'ig_markets/cli/tables/performances_table'
68
72
  require 'ig_markets/cli/tables/positions_table'
69
73
  require 'ig_markets/cli/tables/sprint_market_positions_table'
70
74
  require 'ig_markets/cli/tables/transactions_table'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ig_markets
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.12'
4
+ version: '0.13'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Viney
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-21 00:00:00.000000000 Z
11
+ date: 2016-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -213,7 +213,9 @@ files:
213
213
  - lib/ig_markets/cli/commands/activities_command.rb
214
214
  - lib/ig_markets/cli/commands/confirmation_command.rb
215
215
  - lib/ig_markets/cli/commands/console_command.rb
216
+ - lib/ig_markets/cli/commands/markets_command.rb
216
217
  - lib/ig_markets/cli/commands/orders_command.rb
218
+ - lib/ig_markets/cli/commands/performance_command.rb
217
219
  - lib/ig_markets/cli/commands/positions_command.rb
218
220
  - lib/ig_markets/cli/commands/prices_command.rb
219
221
  - lib/ig_markets/cli/commands/search_command.rb
@@ -228,6 +230,8 @@ files:
228
230
  - lib/ig_markets/cli/tables/client_sentiments_table.rb
229
231
  - lib/ig_markets/cli/tables/historical_price_result_snapshots_table.rb
230
232
  - lib/ig_markets/cli/tables/market_overviews_table.rb
233
+ - lib/ig_markets/cli/tables/markets_table.rb
234
+ - lib/ig_markets/cli/tables/performances_table.rb
231
235
  - lib/ig_markets/cli/tables/positions_table.rb
232
236
  - lib/ig_markets/cli/tables/sprint_market_positions_table.rb
233
237
  - lib/ig_markets/cli/tables/table.rb