ig_markets 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -4
  3. data/README.md +70 -17
  4. data/lib/ig_markets.rb +27 -15
  5. data/lib/ig_markets/{account_activity.rb → activity.rb} +1 -1
  6. data/lib/ig_markets/cli/{account_command.rb → commands/account_command.rb} +5 -4
  7. data/lib/ig_markets/cli/commands/activities_command.rb +37 -0
  8. data/lib/ig_markets/cli/commands/confirmation_command.rb +14 -0
  9. data/lib/ig_markets/cli/{orders_command.rb → commands/orders_command.rb} +16 -21
  10. data/lib/ig_markets/cli/{positions_command.rb → commands/positions_command.rb} +31 -19
  11. data/lib/ig_markets/cli/commands/search_command.rb +18 -0
  12. data/lib/ig_markets/cli/commands/sentiment_command.rb +19 -0
  13. data/lib/ig_markets/cli/{sprints_command.rb → commands/sprints_command.rb} +10 -8
  14. data/lib/ig_markets/cli/commands/transactions_command.rb +61 -0
  15. data/lib/ig_markets/cli/{watchlists_command.rb → commands/watchlists_command.rb} +16 -12
  16. data/lib/ig_markets/cli/main.rb +65 -15
  17. data/lib/ig_markets/cli/tables/accounts_table.rb +30 -0
  18. data/lib/ig_markets/cli/tables/activities_table.rb +29 -0
  19. data/lib/ig_markets/cli/tables/client_sentiments_table.rb +31 -0
  20. data/lib/ig_markets/cli/tables/market_overviews_table.rb +47 -0
  21. data/lib/ig_markets/cli/tables/positions_table.rb +83 -0
  22. data/lib/ig_markets/cli/tables/sprint_market_positions_table.rb +55 -0
  23. data/lib/ig_markets/cli/tables/table.rb +103 -0
  24. data/lib/ig_markets/cli/tables/transactions_table.rb +41 -0
  25. data/lib/ig_markets/cli/tables/working_orders_table.rb +27 -0
  26. data/lib/ig_markets/dealing_platform/account_methods.rb +8 -8
  27. data/lib/ig_markets/dealing_platform/client_sentiment_methods.rb +4 -0
  28. data/lib/ig_markets/dealing_platform/market_methods.rb +1 -1
  29. data/lib/ig_markets/format.rb +20 -7
  30. data/lib/ig_markets/market_overview.rb +1 -1
  31. data/lib/ig_markets/model.rb +10 -1
  32. data/lib/ig_markets/model/typecasters.rb +1 -1
  33. data/lib/ig_markets/position.rb +12 -11
  34. data/lib/ig_markets/request_printer.rb +56 -0
  35. data/lib/ig_markets/response_parser.rb +11 -0
  36. data/lib/ig_markets/session.rb +7 -8
  37. data/lib/ig_markets/{account_transaction.rb → transaction.rb} +4 -17
  38. data/lib/ig_markets/version.rb +1 -1
  39. metadata +54 -16
  40. data/lib/ig_markets/cli/activities_command.rb +0 -31
  41. data/lib/ig_markets/cli/confirmation_command.rb +0 -16
  42. data/lib/ig_markets/cli/output.rb +0 -115
  43. data/lib/ig_markets/cli/search_command.rb +0 -16
  44. data/lib/ig_markets/cli/sentiment_command.rb +0 -24
  45. data/lib/ig_markets/cli/transactions_command.rb +0 -57
@@ -0,0 +1,47 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::MarketOverview} instances in a table.
4
+ class MarketOverviewsTable < Table
5
+ private
6
+
7
+ def default_title
8
+ 'Markets'
9
+ end
10
+
11
+ def headings
12
+ ['Type', 'EPIC', 'Instrument', 'Status', 'Expiry', 'Bid', 'Offer', 'High', 'Low', 'Change (net)', 'Change (%)']
13
+ end
14
+
15
+ def right_aligned_columns
16
+ [5, 6, 7, 8, 9, 10]
17
+ end
18
+
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)]
22
+ end
23
+
24
+ def cell_color(value, _model, _row_index, column_index)
25
+ return unless headings[column_index] =~ /Change/
26
+
27
+ if value =~ /-/
28
+ :red
29
+ else
30
+ :green
31
+ end
32
+ end
33
+
34
+ def levels(market_overview)
35
+ [:bid, :offer, :high, :low, :net_change, :percentage_change].map do |attribute|
36
+ Format.level market_overview.send(attribute)
37
+ end
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
+ end
46
+ end
47
+ end
@@ -0,0 +1,83 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::Position} instances in a table. Positions that share an EPIC
4
+ # can be aggregated into one row in the table if desired.
5
+ class PositionsTable < Table
6
+ def initialize(models, options = {})
7
+ models = aggregated_positions(models) if options[:aggregate]
8
+
9
+ super models, options
10
+ end
11
+
12
+ private
13
+
14
+ def default_title
15
+ 'Positions'
16
+ end
17
+
18
+ def headings
19
+ ['Date', 'EPIC', 'Type', 'Direction', 'Size', 'Level', 'Current', 'Limit', 'Stop', 'Profit/loss', 'Deal IDs']
20
+ end
21
+
22
+ def right_aligned_columns
23
+ [4, 5, 6, 7, 8, 9]
24
+ end
25
+
26
+ def row(position)
27
+ [position.created_date_utc, position.market.epic, position.market.instrument_type, position.direction,
28
+ position.size, position_prices(position), profit_loss(position), position.deal_id]
29
+ end
30
+
31
+ def cell_color(value, _model, _row_index, column_index)
32
+ return unless headings[column_index] == 'Profit/loss'
33
+
34
+ if value =~ /-/
35
+ :red
36
+ else
37
+ :green
38
+ end
39
+ end
40
+
41
+ def position_prices(position)
42
+ [:level, :close_level, :limit_level, :stop_level].map do |attribute|
43
+ Format.level position.send(attribute)
44
+ end
45
+ end
46
+
47
+ def profit_loss(position)
48
+ Format.currency position.profit_loss, position.currency
49
+ end
50
+
51
+ def aggregated_positions(positions)
52
+ grouped = positions.group_by do |position|
53
+ position.market.epic
54
+ end
55
+
56
+ grouped.map do |_, group|
57
+ group.size > 1 ? combine_positions(group) : group.first
58
+ end
59
+ end
60
+
61
+ def combine_positions(positions)
62
+ first = positions.first
63
+
64
+ Position.new(contract_size: first.contract_size, currency: first.currency,
65
+ deal_id: combine_position_deal_ids(positions), direction: first.direction,
66
+ level: combine_position_levels(positions), market: first.market,
67
+ size: positions.map(&:size).reduce(:+)).tap do |combined|
68
+ combined.level /= combined.size
69
+ end
70
+ end
71
+
72
+ def combine_position_levels(positions)
73
+ positions.map do |position|
74
+ position.level * position.size
75
+ end.reduce :+
76
+ end
77
+
78
+ def combine_position_deal_ids(positions)
79
+ positions.map(&:deal_id).join ', '
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,55 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::SprintMarketPosition} instances in a table.
4
+ class SprintMarketPositionsTable < Table
5
+ def initialize(sprints, options = {})
6
+ super
7
+
8
+ @markets = options.fetch :markets
9
+ end
10
+
11
+ private
12
+
13
+ def default_title
14
+ 'Sprint market positions'
15
+ end
16
+
17
+ def headings
18
+ ['EPIC', 'Direction', 'Size', 'Strike level', 'Current', 'Expires in (m:ss)', 'Payout', 'Deal ID']
19
+ end
20
+
21
+ def right_aligned_columns
22
+ [2, 3, 4, 5]
23
+ end
24
+
25
+ def row(sprint)
26
+ [sprint.epic, sprint.direction, Format.currency(sprint.size, sprint.currency), levels(sprint),
27
+ Format.seconds(sprint.seconds_till_expiry), Format.currency(sprint.payout_amount, sprint.currency),
28
+ sprint.deal_id]
29
+ end
30
+
31
+ def cell_color(_value, sprint, _row_index, column_index)
32
+ return unless headings[column_index] == 'Payout'
33
+
34
+ if current_level(sprint) > sprint.strike_level && sprint.direction == :buy ||
35
+ current_level(sprint) < sprint.strike_level && sprint.direction == :sell
36
+ :green
37
+ else
38
+ :red
39
+ end
40
+ end
41
+
42
+ def levels(sprint)
43
+ [Format.level(sprint.strike_level), Format.level(current_level(sprint))]
44
+ end
45
+
46
+ def current_level(sprint)
47
+ snapshot = @markets.detect do |market|
48
+ market.instrument.epic == sprint.epic
49
+ end.snapshot
50
+
51
+ (snapshot.bid + snapshot.offer) / 2.0
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,103 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Base class with shared table setup and display methods. Used to print output tables in the command-line client.
4
+ class Table
5
+ # Initializer that takes the array of models to display in this table.
6
+ #
7
+ # @param [Array] models The array of models.
8
+ # @param [Hash] options The options hash.
9
+ # @option options [String] :title The title for this table.
10
+ def initialize(models, options = {})
11
+ @models = models
12
+ @title = options[:title] || default_title
13
+ end
14
+
15
+ # Converts this table into a formatted string.
16
+ def to_s
17
+ table.to_s
18
+ end
19
+
20
+ private
21
+
22
+ def default_title
23
+ end
24
+
25
+ def headings
26
+ end
27
+
28
+ def right_aligned_columns
29
+ end
30
+
31
+ def row(_model)
32
+ end
33
+
34
+ def cell_color(_value, _model, _row_index, _column_index)
35
+ end
36
+
37
+ def table
38
+ Terminal::Table.new(title: @title, headings: headings, rows: rows).tap do |t|
39
+ Array(right_aligned_columns).each do |column|
40
+ t.align_column column, :right
41
+ end
42
+ end
43
+ end
44
+
45
+ def rows
46
+ @models.flatten.each_with_index.map do |model, row_index|
47
+ if model == :separator
48
+ :separator
49
+ else
50
+ row(model).flatten.each_with_index.map do |value, column_index|
51
+ cell_content value, model, row_index, column_index
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def cell_content(value, model, row_index, column_index)
58
+ color = cell_color value, model, row_index, column_index
59
+
60
+ content = format_cell_value(value).strip
61
+
62
+ color ? content.colorize(color) : content
63
+ end
64
+
65
+ def format_cell_value(value)
66
+ return format_boolean(value) if value.is_a?(TrueClass) || value.is_a?(FalseClass)
67
+ return format_float(value) if value.is_a? Float
68
+ return format_time(value) if value.is_a? Time
69
+ return format_date(value) if value.is_a? Date
70
+
71
+ format_string value
72
+ end
73
+
74
+ def format_boolean(value)
75
+ { true => 'Yes', false => 'No' }.fetch value
76
+ end
77
+
78
+ def format_float(value)
79
+ format '%g', value
80
+ end
81
+
82
+ def format_time(value)
83
+ value.utc.strftime '%F %T %Z'
84
+ end
85
+
86
+ def format_date(value)
87
+ value.strftime '%F'
88
+ end
89
+
90
+ def format_string(value)
91
+ value = if value.is_a? Symbol
92
+ value.to_s.tr '_', ' '
93
+ else
94
+ value.to_s
95
+ end
96
+
97
+ return '' if value.empty?
98
+
99
+ value[0].upcase + value[1..-1]
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,41 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::Transaction} instances in a table.
4
+ class TransactionsTable < Table
5
+ private
6
+
7
+ def default_title
8
+ 'Transactions'
9
+ end
10
+
11
+ def headings
12
+ ['Date', 'Reference', 'Type', 'Instrument', 'Size', 'Open', 'Close', 'Profit/loss']
13
+ end
14
+
15
+ def right_aligned_columns
16
+ [4, 5, 6, 7]
17
+ end
18
+
19
+ def row(transaction)
20
+ [transaction.date, transaction.reference, formatted_type(transaction.transaction_type),
21
+ transaction.instrument_name, transaction.size, Format.level(transaction.open_level),
22
+ Format.level(transaction.close_level),
23
+ Format.currency(transaction.profit_and_loss_amount, transaction.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
+
36
+ def formatted_type(type)
37
+ { deal: 'Deal', depo: 'Deposit', dividend: 'Dividend', exchange: 'Exchange', with: 'Withdrawal' }.fetch type
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Helper class that prints out an array of {IGMarkets::WorkingOrder} instances in a table.
4
+ class WorkingOrdersTable < Table
5
+ private
6
+
7
+ def default_title
8
+ 'Working orders'
9
+ end
10
+
11
+ def headings
12
+ ['Created date', 'EPIC', 'Currency', 'Type', 'Direction', 'Size', 'Level', 'Limit distance', 'Stop distance',
13
+ 'Good till date', 'Deal ID']
14
+ end
15
+
16
+ def right_aligned_columns
17
+ [5, 6, 7, 8]
18
+ end
19
+
20
+ def row(working_order)
21
+ [working_order.created_date_utc, working_order.epic, working_order.currency_code, working_order.order_type,
22
+ working_order.direction, working_order.order_size, Format.level(working_order.order_level),
23
+ working_order.limit_distance, working_order.stop_distance, working_order.good_till_date, working_order.deal_id]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -21,21 +21,21 @@ module IGMarkets
21
21
  # @param [Date] from_date The start date of the desired date range.
22
22
  # @param [Date] to_date The end date of the desired date range.
23
23
  #
24
- # @return [Array<AccountActivity>]
24
+ # @return [Array<Activity>]
25
25
  def activities_in_date_range(from_date, to_date)
26
26
  from_date = format_date from_date
27
27
  to_date = format_date to_date
28
28
 
29
- @dealing_platform.gather "history/activity/#{from_date}/#{to_date}", :activities, AccountActivity
29
+ @dealing_platform.gather "history/activity/#{from_date}/#{to_date}", :activities, Activity
30
30
  end
31
31
 
32
32
  # Returns all account activities that occurred in the most recent specified number of days.
33
33
  #
34
34
  # @param [Fixnum, Float] days The number of days to return recent activities for.
35
35
  #
36
- # @return [Array<AccountActivity>]
36
+ # @return [Array<Activity>]
37
37
  def recent_activities(days)
38
- @dealing_platform.gather "history/activity/#{milliseconds(days)}", :activities, AccountActivity
38
+ @dealing_platform.gather "history/activity/#{milliseconds(days)}", :activities, Activity
39
39
  end
40
40
 
41
41
  # Returns all transactions that occurred in the specified date range.
@@ -44,7 +44,7 @@ module IGMarkets
44
44
  # @param [Date] to_date The end date of the desired date range.
45
45
  # @param [:all, :all_deal, :deposit, :withdrawal] transaction_type The type of transactions to return.
46
46
  #
47
- # @return [Array<AccountTransaction>]
47
+ # @return [Array<Transaction>]
48
48
  def transactions_in_date_range(from_date, to_date, transaction_type = :all)
49
49
  validate_transaction_type transaction_type
50
50
 
@@ -53,7 +53,7 @@ module IGMarkets
53
53
 
54
54
  url = "history/transactions/#{transaction_type.to_s.upcase}/#{from_date}/#{to_date}"
55
55
 
56
- @dealing_platform.gather url, :transactions, AccountTransaction
56
+ @dealing_platform.gather url, :transactions, Transaction
57
57
  end
58
58
 
59
59
  # Returns all transactions that occurred in the last specified number of days.
@@ -61,13 +61,13 @@ module IGMarkets
61
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
- # @return [Array<AccountTransaction>]
64
+ # @return [Array<Transaction>]
65
65
  def recent_transactions(days, transaction_type = :all)
66
66
  validate_transaction_type transaction_type
67
67
 
68
68
  url = "history/transactions/#{transaction_type.to_s.upcase}/#{milliseconds(days)}"
69
69
 
70
- @dealing_platform.gather url, :transactions, AccountTransaction
70
+ @dealing_platform.gather url, :transactions, Transaction
71
71
  end
72
72
 
73
73
  private
@@ -18,6 +18,10 @@ module IGMarkets
18
18
  result = @dealing_platform.session.get "clientsentiment/#{market_id}", API_V1
19
19
 
20
20
  ClientSentiment.from(result).tap do |client_sentiment|
21
+ if client_sentiment.long_position_percentage == 0.0 && client_sentiment.short_position_percentage == 0.0
22
+ raise ArgumentError, "unknown market '#{market_id}'"
23
+ end
24
+
21
25
  client_sentiment.instance_variable_set :@dealing_platform, @dealing_platform
22
26
  end
23
27
  end
@@ -29,7 +29,7 @@ module IGMarkets
29
29
  def find(*epics)
30
30
  raise ArgumentError, 'at least one EPIC must be specified' if epics.empty?
31
31
 
32
- epics.each do |epic|
32
+ epics.flatten.each do |epic|
33
33
  raise ArgumentError, "invalid EPIC: #{epic}" unless epic.to_s =~ Regex::EPIC
34
34
  end
35
35
 
@@ -3,18 +3,31 @@ module IGMarkets
3
3
  module Format
4
4
  module_function
5
5
 
6
- # Returns a formatted string for the specified currency amount and currency symbol. Two decimal places are used for
7
- # all currencies except the Japanese Yen.
6
+ # Returns a formatted string for the specified level. At most four decimal places are used to format levels.
7
+ #
8
+ # @param [Float] value The level to format.
9
+ #
10
+ # @return [String] The formatted level, e.g. `"-130.4055"`
11
+ def level(value)
12
+ return '' unless value
13
+
14
+ Float(format('%.4f', value.to_f)).to_s
15
+ end
16
+
17
+ # Returns a formatted string for the specified currency amount and currency. Two decimal places are used for all
18
+ # currencies except the Japanese Yen.
8
19
  #
9
20
  # @param [Float, Fixnum] amount The currency amount to format.
10
- # @param [String] symbol The currency symbol.
21
+ # @param [String] currency The currency.
11
22
  #
12
23
  # @return [String] The formatted currency amount, e.g. `"USD -130.40"`, `"AUD 539.10"`, `"JPY 3560"`.
13
- def currency(amount, symbol)
14
- if ['JPY', '¥'].include? symbol
15
- "#{symbol} #{format '%i', amount.to_i}"
24
+ def currency(amount, currency)
25
+ return '' unless amount
26
+
27
+ if ['JPY', '¥'].include? currency
28
+ "#{currency} #{format '%i', amount.to_i}"
16
29
  else
17
- "#{symbol} #{format '%.2f', amount.to_f}"
30
+ "#{currency} #{format '%.2f', amount.to_f}"
18
31
  end
19
32
  end
20
33