ig_markets 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -4
- data/README.md +70 -17
- data/lib/ig_markets.rb +27 -15
- data/lib/ig_markets/{account_activity.rb → activity.rb} +1 -1
- data/lib/ig_markets/cli/{account_command.rb → commands/account_command.rb} +5 -4
- data/lib/ig_markets/cli/commands/activities_command.rb +37 -0
- data/lib/ig_markets/cli/commands/confirmation_command.rb +14 -0
- data/lib/ig_markets/cli/{orders_command.rb → commands/orders_command.rb} +16 -21
- data/lib/ig_markets/cli/{positions_command.rb → commands/positions_command.rb} +31 -19
- data/lib/ig_markets/cli/commands/search_command.rb +18 -0
- data/lib/ig_markets/cli/commands/sentiment_command.rb +19 -0
- data/lib/ig_markets/cli/{sprints_command.rb → commands/sprints_command.rb} +10 -8
- data/lib/ig_markets/cli/commands/transactions_command.rb +61 -0
- data/lib/ig_markets/cli/{watchlists_command.rb → commands/watchlists_command.rb} +16 -12
- data/lib/ig_markets/cli/main.rb +65 -15
- data/lib/ig_markets/cli/tables/accounts_table.rb +30 -0
- data/lib/ig_markets/cli/tables/activities_table.rb +29 -0
- data/lib/ig_markets/cli/tables/client_sentiments_table.rb +31 -0
- data/lib/ig_markets/cli/tables/market_overviews_table.rb +47 -0
- data/lib/ig_markets/cli/tables/positions_table.rb +83 -0
- data/lib/ig_markets/cli/tables/sprint_market_positions_table.rb +55 -0
- data/lib/ig_markets/cli/tables/table.rb +103 -0
- data/lib/ig_markets/cli/tables/transactions_table.rb +41 -0
- data/lib/ig_markets/cli/tables/working_orders_table.rb +27 -0
- data/lib/ig_markets/dealing_platform/account_methods.rb +8 -8
- data/lib/ig_markets/dealing_platform/client_sentiment_methods.rb +4 -0
- data/lib/ig_markets/dealing_platform/market_methods.rb +1 -1
- data/lib/ig_markets/format.rb +20 -7
- data/lib/ig_markets/market_overview.rb +1 -1
- data/lib/ig_markets/model.rb +10 -1
- data/lib/ig_markets/model/typecasters.rb +1 -1
- data/lib/ig_markets/position.rb +12 -11
- data/lib/ig_markets/request_printer.rb +56 -0
- data/lib/ig_markets/response_parser.rb +11 -0
- data/lib/ig_markets/session.rb +7 -8
- data/lib/ig_markets/{account_transaction.rb → transaction.rb} +4 -17
- data/lib/ig_markets/version.rb +1 -1
- metadata +54 -16
- data/lib/ig_markets/cli/activities_command.rb +0 -31
- data/lib/ig_markets/cli/confirmation_command.rb +0 -16
- data/lib/ig_markets/cli/output.rb +0 -115
- data/lib/ig_markets/cli/search_command.rb +0 -16
- data/lib/ig_markets/cli/sentiment_command.rb +0 -24
- 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<
|
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,
|
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<
|
36
|
+
# @return [Array<Activity>]
|
37
37
|
def recent_activities(days)
|
38
|
-
@dealing_platform.gather "history/activity/#{milliseconds(days)}", :activities,
|
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<
|
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,
|
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<
|
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,
|
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
|
|
data/lib/ig_markets/format.rb
CHANGED
@@ -3,18 +3,31 @@ module IGMarkets
|
|
3
3
|
module Format
|
4
4
|
module_function
|
5
5
|
|
6
|
-
# Returns a formatted string for the specified
|
7
|
-
#
|
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]
|
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,
|
14
|
-
|
15
|
-
|
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
|
-
"#{
|
30
|
+
"#{currency} #{format '%.2f', amount.to_f}"
|
18
31
|
end
|
19
32
|
end
|
20
33
|
|