ig_markets 0.5 → 0.6
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 +4 -4
- data/CHANGELOG.md +18 -8
- data/README.md +40 -10
- data/lib/ig_markets.rb +3 -1
- data/lib/ig_markets/cli/commands/activities_command.rb +9 -7
- data/lib/ig_markets/cli/commands/orders_command.rb +8 -11
- data/lib/ig_markets/cli/commands/positions_command.rb +9 -7
- data/lib/ig_markets/cli/commands/prices_command.rb +66 -0
- data/lib/ig_markets/cli/commands/search_command.rb +19 -2
- data/lib/ig_markets/cli/commands/sprints_command.rb +2 -2
- data/lib/ig_markets/cli/config_file.rb +1 -1
- data/lib/ig_markets/cli/main.rb +25 -16
- data/lib/ig_markets/cli/tables/accounts_table.rb +10 -0
- data/lib/ig_markets/cli/tables/historical_price_result_snapshots_table.rb +25 -0
- data/lib/ig_markets/cli/tables/table.rb +2 -2
- data/lib/ig_markets/client_sentiment.rb +3 -1
- data/lib/ig_markets/dealing_platform.rb +49 -14
- data/lib/ig_markets/dealing_platform/account_methods.rb +13 -5
- data/lib/ig_markets/dealing_platform/client_sentiment_methods.rb +2 -4
- data/lib/ig_markets/dealing_platform/market_methods.rb +14 -6
- data/lib/ig_markets/dealing_platform/position_methods.rb +7 -7
- data/lib/ig_markets/dealing_platform/sprint_market_position_methods.rb +16 -2
- data/lib/ig_markets/dealing_platform/watchlist_methods.rb +4 -2
- data/lib/ig_markets/dealing_platform/working_order_methods.rb +2 -4
- data/lib/ig_markets/format.rb +10 -0
- data/lib/ig_markets/historical_price_result.rb +1 -1
- data/lib/ig_markets/instrument.rb +10 -10
- data/lib/ig_markets/market.rb +3 -3
- data/lib/ig_markets/model.rb +17 -25
- data/lib/ig_markets/model/typecasters.rb +20 -13
- data/lib/ig_markets/payload_formatter.rb +1 -1
- data/lib/ig_markets/position.rb +1 -1
- data/lib/ig_markets/session.rb +8 -8
- data/lib/ig_markets/sprint_market_position.rb +2 -2
- data/lib/ig_markets/transaction.rb +1 -1
- data/lib/ig_markets/version.rb +1 -1
- data/lib/ig_markets/watchlist.rb +6 -4
- data/lib/ig_markets/working_order.rb +2 -2
- metadata +4 -2
data/lib/ig_markets/cli/main.rb
CHANGED
@@ -7,6 +7,7 @@ module IGMarkets
|
|
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
9
|
class_option :demo, type: :boolean, desc: 'Use the demo platform (default is production)'
|
10
|
+
class_option :account_time_zone, default: '+0000', desc: 'The time zone of the account'
|
10
11
|
class_option :print_requests, type: :boolean, desc: 'Whether to print the raw REST API requests and responses'
|
11
12
|
|
12
13
|
desc 'orders [SUBCOMAND=list ...]', 'Command for working with orders'
|
@@ -33,23 +34,31 @@ module IGMarkets
|
|
33
34
|
|
34
35
|
RequestPrinter.enabled = true if options[:print_requests]
|
35
36
|
|
37
|
+
dealing_platform.account_time_zone = options[:account_time_zone]
|
38
|
+
|
36
39
|
dealing_platform.sign_in options[:username], options[:password], options[:api_key], platform
|
37
40
|
|
38
41
|
yield dealing_platform
|
39
42
|
rescue IGMarkets::RequestFailedError => error
|
40
|
-
|
41
|
-
exit 1
|
43
|
+
error "Request error: #{error.error}"
|
42
44
|
rescue ArgumentError => error
|
43
|
-
|
45
|
+
error "Argument error: #{error}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Writes the passed message to `stderr` and then exits the application.
|
49
|
+
#
|
50
|
+
# @param [String] message The error message.
|
51
|
+
def error(message)
|
52
|
+
warn message
|
44
53
|
exit 1
|
45
54
|
end
|
46
55
|
|
47
|
-
# The
|
56
|
+
# The {DealingPlatform} instance used by {begin_session}.
|
48
57
|
def dealing_platform
|
49
58
|
@dealing_platform ||= DealingPlatform.new
|
50
59
|
end
|
51
60
|
|
52
|
-
#
|
61
|
+
# Requests and displays the deal confirmation for the passed deal reference.
|
53
62
|
#
|
54
63
|
# @param [String] deal_reference The deal reference.
|
55
64
|
#
|
@@ -59,24 +68,24 @@ module IGMarkets
|
|
59
68
|
|
60
69
|
deal_confirmation = dealing_platform.deal_confirmation deal_reference
|
61
70
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
71
|
+
puts <<-END
|
72
|
+
Deal ID: #{deal_confirmation.deal_id}
|
73
|
+
Status: #{Format.symbol deal_confirmation.deal_status}
|
74
|
+
Result: #{Format.symbol deal_confirmation.status}
|
75
|
+
END
|
67
76
|
|
68
|
-
puts "
|
77
|
+
puts "Reason: #{Format.symbol deal_confirmation.reason}" unless deal_confirmation.deal_status == :accepted
|
69
78
|
end
|
70
79
|
|
71
|
-
# Parses and validates a Date or Time option received as a command-line argument. Raises `ArgumentError` if
|
72
|
-
# is been specified in an invalid format.
|
80
|
+
# Parses and validates a `Date` or `Time` option received as a command-line argument. Raises `ArgumentError` if
|
81
|
+
# it is been specified in an invalid format.
|
73
82
|
#
|
74
83
|
# @param [Hash] attributes The attributes hash.
|
75
84
|
# @param [Symbol] attribute The name of the date or time attribute to parse and validate.
|
76
85
|
# @param [Date, Time] klass The class to validate with.
|
77
86
|
# @param [String] format The `strptime` format string for the attribute.
|
78
|
-
# @param [String] display_format The human-readable version of `format` to put into
|
79
|
-
# a problem parsing the attribute.
|
87
|
+
# @param [String] display_format The human-readable version of `format` to put into the raised exception if
|
88
|
+
# there is a problem parsing the attribute.
|
80
89
|
#
|
81
90
|
# @return [void]
|
82
91
|
def parse_date_time(attributes, attribute, klass, format, display_format)
|
@@ -95,7 +104,7 @@ module IGMarkets
|
|
95
104
|
|
96
105
|
# Takes a Thor options hash and filters out its keys in the specified whitelist. Thor has an unusual behavior
|
97
106
|
# when an option is specified without a value: its value is set to the option's name. This method resets any
|
98
|
-
# such occurrences to nil
|
107
|
+
# such occurrences to `nil`.
|
99
108
|
#
|
100
109
|
# @param [Thor::CoreExt::HashWithIndifferentAccess] options The Thor options.
|
101
110
|
# @param [Array<Symbol>] whitelist The list of options allowed in the returned `Hash`.
|
@@ -25,6 +25,16 @@ module IGMarkets
|
|
25
25
|
Format.currency account.balance.send(attribute), account.currency
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
def cell_color(value, _model, _row_index, column_index)
|
30
|
+
return unless headings[column_index] == 'Profit/loss'
|
31
|
+
|
32
|
+
if value =~ /-/
|
33
|
+
:red
|
34
|
+
else
|
35
|
+
:green
|
36
|
+
end
|
37
|
+
end
|
28
38
|
end
|
29
39
|
end
|
30
40
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module IGMarkets
|
2
|
+
module CLI
|
3
|
+
# Helper class that prints out an array of {IGMarkets::HistoricalPriceResult::Snapshot} instances in a table.
|
4
|
+
class HistoricalPriceResultSnapshotsTable < Table
|
5
|
+
private
|
6
|
+
|
7
|
+
def headings
|
8
|
+
%w(Date Open Close Low High)
|
9
|
+
end
|
10
|
+
|
11
|
+
def right_aligned_columns
|
12
|
+
[1, 2, 3, 4]
|
13
|
+
end
|
14
|
+
|
15
|
+
def row(snapshot)
|
16
|
+
[snapshot.snapshot_time, format_price(snapshot.open_price), format_price(snapshot.close_price),
|
17
|
+
format_price(snapshot.low_price), format_price(snapshot.high_price)]
|
18
|
+
end
|
19
|
+
|
20
|
+
def format_price(price)
|
21
|
+
(price.ask + price.bid) / 2.0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -80,7 +80,7 @@ module IGMarkets
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def format_time(value)
|
83
|
-
value.
|
83
|
+
value.localtime.strftime '%F %T %Z'
|
84
84
|
end
|
85
85
|
|
86
86
|
def format_date(value)
|
@@ -89,7 +89,7 @@ module IGMarkets
|
|
89
89
|
|
90
90
|
def format_string(value)
|
91
91
|
value = if value.is_a? Symbol
|
92
|
-
|
92
|
+
Format.symbol value
|
93
93
|
else
|
94
94
|
value.to_s
|
95
95
|
end
|
@@ -10,7 +10,9 @@ module IGMarkets
|
|
10
10
|
#
|
11
11
|
# @return [Array<ClientSentiment>]
|
12
12
|
def related_sentiments
|
13
|
-
@dealing_platform.
|
13
|
+
result = @dealing_platform.session.get("clientsentiment/related/#{market_id}").fetch :client_sentiments
|
14
|
+
|
15
|
+
@dealing_platform.instantiate_models ClientSentiment, result
|
14
16
|
end
|
15
17
|
end
|
16
18
|
end
|
@@ -38,6 +38,11 @@ module IGMarkets
|
|
38
38
|
# @return [WorkingOrderMethods] Methods for working with working orders.
|
39
39
|
attr_reader :working_orders
|
40
40
|
|
41
|
+
# @return [String] The time zone of the account, e.g. `'+1000'` or `'-0800'`. This is required in order for certain
|
42
|
+
# dates and times reported by this library to be correct, due to the fact that the IG Markets API does not reliably
|
43
|
+
# report time zone details in all attributes. Defaults to `'+0000'`.
|
44
|
+
attr_accessor :account_time_zone
|
45
|
+
|
41
46
|
def initialize
|
42
47
|
@session = Session.new
|
43
48
|
|
@@ -48,6 +53,8 @@ module IGMarkets
|
|
48
53
|
@sprint_market_positions = SprintMarketPositionMethods.new self
|
49
54
|
@watchlists = WatchlistMethods.new self
|
50
55
|
@working_orders = WorkingOrderMethods.new self
|
56
|
+
|
57
|
+
@account_time_zone = '+0000'
|
51
58
|
end
|
52
59
|
|
53
60
|
# Signs in to the IG Markets Dealing Platform, either the production platform or the demo platform.
|
@@ -74,30 +81,58 @@ module IGMarkets
|
|
74
81
|
#
|
75
82
|
# @return [DealConfirmation]
|
76
83
|
def deal_confirmation(deal_reference)
|
77
|
-
DealConfirmation
|
84
|
+
instantiate_models DealConfirmation, session.get("confirms/#{deal_reference}")
|
78
85
|
end
|
79
86
|
|
80
87
|
# Returns details on the IG Markets applications for the accounts associated with this login.
|
81
88
|
#
|
82
89
|
# @return [Array<Application>]
|
83
90
|
def applications
|
84
|
-
Application
|
91
|
+
instantiate_models Application, session.get('operations/application')
|
85
92
|
end
|
86
93
|
|
87
|
-
#
|
88
|
-
#
|
94
|
+
# This method is used to instantiate the various `Model` subclasses from data returned by the IG Markets API. It
|
95
|
+
# recurses through arrays and sub-hashes present in `source`, instantiating the required models based on the types
|
96
|
+
# of each attribute as defined on the models. All model instances returned by this method will have their
|
97
|
+
# `@dealing_platform` instance variable set.
|
89
98
|
#
|
90
|
-
# @param [
|
91
|
-
# @param [
|
92
|
-
#
|
93
|
-
#
|
99
|
+
# @param [Class] model_class The type of model to create from `source`.
|
100
|
+
# @param [nil, Hash, Array, Model] source The source object to construct the model(s) from. If `nil` then `nil` is
|
101
|
+
# returned. If an instance of `model_class` subclass then a deep copy of it is
|
102
|
+
# returned. If a `Hash` then it will be interprted as the attributes for a new
|
103
|
+
# instance of `model_class. If an `Array` then each entry will be passed through
|
104
|
+
# this method individually.
|
94
105
|
#
|
95
|
-
# @return [Array]
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
106
|
+
# @return [nil, `model_class`, Array<`model_class`>] The resulting instantiated model(s).
|
107
|
+
def instantiate_models(model_class, source)
|
108
|
+
return nil if source.nil?
|
109
|
+
|
110
|
+
source = source.attributes if source.is_a? model_class
|
111
|
+
|
112
|
+
if source.is_a? Array
|
113
|
+
source.map { |entry| instantiate_models model_class, entry }
|
114
|
+
elsif source.is_a? Hash
|
115
|
+
source = model_class.adjusted_api_attributes source if model_class.respond_to? :adjusted_api_attributes
|
116
|
+
|
117
|
+
instantiate_model_from_attributes_hash model_class, source
|
118
|
+
else
|
119
|
+
raise ArgumentError, "#{model_class}: can't instantiate from a source of type #{source.class}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# This method is a companion to {#instantiate_models} and creates a single instance of `model_class` from the passed
|
126
|
+
# attributes hash, setting the `@dealing_platform` instance variable on the new model instance.
|
127
|
+
def instantiate_model_from_attributes_hash(model_class, attributes)
|
128
|
+
model_class.new.tap do |model|
|
129
|
+
model.instance_variable_set :@dealing_platform, self
|
130
|
+
|
131
|
+
attributes.each do |attribute, value|
|
132
|
+
type = model_class.attribute_type attribute
|
133
|
+
value = instantiate_models(type, value) if type < Model
|
134
|
+
|
135
|
+
model.send "#{attribute}=", value
|
101
136
|
end
|
102
137
|
end
|
103
138
|
end
|
@@ -13,7 +13,9 @@ module IGMarkets
|
|
13
13
|
#
|
14
14
|
# @return [Array<Account>]
|
15
15
|
def all
|
16
|
-
@dealing_platform.
|
16
|
+
result = @dealing_platform.session.get('accounts').fetch :accounts
|
17
|
+
|
18
|
+
@dealing_platform.instantiate_models Account, result
|
17
19
|
end
|
18
20
|
|
19
21
|
# Returns all account activities that occurred in the specified date range.
|
@@ -26,7 +28,9 @@ module IGMarkets
|
|
26
28
|
from_date = format_date from_date
|
27
29
|
to_date = format_date to_date
|
28
30
|
|
29
|
-
@dealing_platform.
|
31
|
+
result = @dealing_platform.session.get("history/activity/#{from_date}/#{to_date}").fetch :activities
|
32
|
+
|
33
|
+
@dealing_platform.instantiate_models Activity, result
|
30
34
|
end
|
31
35
|
|
32
36
|
# Returns all account activities that occurred in the most recent specified number of days.
|
@@ -35,7 +39,9 @@ module IGMarkets
|
|
35
39
|
#
|
36
40
|
# @return [Array<Activity>]
|
37
41
|
def recent_activities(days)
|
38
|
-
@dealing_platform.
|
42
|
+
result = @dealing_platform.session.get("history/activity/#{milliseconds(days)}").fetch :activities
|
43
|
+
|
44
|
+
@dealing_platform.instantiate_models Activity, result
|
39
45
|
end
|
40
46
|
|
41
47
|
# Returns all transactions that occurred in the specified date range.
|
@@ -52,8 +58,9 @@ module IGMarkets
|
|
52
58
|
to_date = format_date to_date
|
53
59
|
|
54
60
|
url = "history/transactions/#{transaction_type.to_s.upcase}/#{from_date}/#{to_date}"
|
61
|
+
result = @dealing_platform.session.get(url).fetch :transactions
|
55
62
|
|
56
|
-
@dealing_platform.
|
63
|
+
@dealing_platform.instantiate_models Transaction, result
|
57
64
|
end
|
58
65
|
|
59
66
|
# Returns all transactions that occurred in the last specified number of days.
|
@@ -66,8 +73,9 @@ module IGMarkets
|
|
66
73
|
validate_transaction_type transaction_type
|
67
74
|
|
68
75
|
url = "history/transactions/#{transaction_type.to_s.upcase}/#{milliseconds(days)}"
|
76
|
+
result = @dealing_platform.session.get(url).fetch :transactions
|
69
77
|
|
70
|
-
@dealing_platform.
|
78
|
+
@dealing_platform.instantiate_models Transaction, result
|
71
79
|
end
|
72
80
|
|
73
81
|
private
|
@@ -15,14 +15,12 @@ module IGMarkets
|
|
15
15
|
#
|
16
16
|
# @return [ClientSentiment]
|
17
17
|
def [](market_id)
|
18
|
-
result = @dealing_platform.session.get "clientsentiment/#{market_id}"
|
18
|
+
result = @dealing_platform.session.get "clientsentiment/#{market_id}"
|
19
19
|
|
20
|
-
|
20
|
+
@dealing_platform.instantiate_models(ClientSentiment, result).tap do |client_sentiment|
|
21
21
|
if client_sentiment.long_position_percentage == 0.0 && client_sentiment.short_position_percentage == 0.0
|
22
22
|
raise ArgumentError, "unknown market '#{market_id}'"
|
23
23
|
end
|
24
|
-
|
25
|
-
client_sentiment.instance_variable_set :@dealing_platform, @dealing_platform
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
@@ -18,7 +18,9 @@ module IGMarkets
|
|
18
18
|
def hierarchy(node_id = nil)
|
19
19
|
url = ['marketnavigation', node_id].compact.join '/'
|
20
20
|
|
21
|
-
|
21
|
+
result = @dealing_platform.session.get url
|
22
|
+
|
23
|
+
@dealing_platform.instantiate_models MarketHierarchyResult, result
|
22
24
|
end
|
23
25
|
|
24
26
|
# Returns details for the markets with the passed EPICs.
|
@@ -27,13 +29,17 @@ module IGMarkets
|
|
27
29
|
#
|
28
30
|
# @return [Array<Market>]
|
29
31
|
def find(*epics)
|
30
|
-
|
32
|
+
epics = epics.flatten
|
33
|
+
|
34
|
+
return [] if epics.empty?
|
31
35
|
|
32
|
-
epics.
|
36
|
+
epics.each do |epic|
|
33
37
|
raise ArgumentError, "invalid EPIC: #{epic}" unless epic.to_s =~ Regex::EPIC
|
34
38
|
end
|
35
39
|
|
36
|
-
@dealing_platform.
|
40
|
+
result = @dealing_platform.session.get("markets?epics=#{epics.join(',')}", API_V2).fetch :market_details
|
41
|
+
|
42
|
+
@dealing_platform.instantiate_models Market, result
|
37
43
|
end
|
38
44
|
|
39
45
|
# Searches markets using a search term and returns an array of results.
|
@@ -42,7 +48,9 @@ module IGMarkets
|
|
42
48
|
#
|
43
49
|
# @return [Array<MarketOverview>]
|
44
50
|
def search(search_term)
|
45
|
-
@dealing_platform.
|
51
|
+
result = @dealing_platform.session.get("markets?searchTerm=#{search_term}").fetch :markets
|
52
|
+
|
53
|
+
@dealing_platform.instantiate_models MarketOverview, result
|
46
54
|
end
|
47
55
|
|
48
56
|
# Returns market details for the market with the specified EPIC, or `nil` if there is no market with that EPIC.
|
@@ -52,7 +60,7 @@ module IGMarkets
|
|
52
60
|
#
|
53
61
|
# @return [Market]
|
54
62
|
def [](epic)
|
55
|
-
find(epic)
|
63
|
+
find(epic).first
|
56
64
|
end
|
57
65
|
end
|
58
66
|
end
|
@@ -20,13 +20,13 @@ module IGMarkets
|
|
20
20
|
|
21
21
|
# Returns the position with the specified deal ID, or `nil` if there is no position with that ID.
|
22
22
|
#
|
23
|
-
# @param [String] deal_id The deal ID of the
|
23
|
+
# @param [String] deal_id The deal ID of the position to return.
|
24
24
|
#
|
25
25
|
# @return [Position]
|
26
26
|
def [](deal_id)
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
all.detect do |position|
|
28
|
+
position.deal_id == deal_id
|
29
|
+
end
|
30
30
|
end
|
31
31
|
|
32
32
|
# Creates a new position.
|
@@ -161,9 +161,9 @@ module IGMarkets
|
|
161
161
|
end
|
162
162
|
|
163
163
|
def position_from_attributes(attributes)
|
164
|
-
|
165
|
-
|
166
|
-
|
164
|
+
attributes = attributes.fetch(:position).merge market: attributes.fetch(:market)
|
165
|
+
|
166
|
+
@dealing_platform.instantiate_models Position, attributes
|
167
167
|
end
|
168
168
|
|
169
169
|
private_constant :PositionCreateAttributes
|
@@ -13,7 +13,21 @@ module IGMarkets
|
|
13
13
|
#
|
14
14
|
# @return [Array<SprintMarketPosition>]
|
15
15
|
def all
|
16
|
-
@dealing_platform.
|
16
|
+
result = @dealing_platform.session.get('positions/sprintmarkets').fetch :sprint_market_positions
|
17
|
+
|
18
|
+
@dealing_platform.instantiate_models SprintMarketPosition, result
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the sprint market position with the specified deal ID, or `nil` if there is no sprint market position
|
22
|
+
# with that ID.
|
23
|
+
#
|
24
|
+
# @param [String] deal_id The deal ID of the sprint market position to return.
|
25
|
+
#
|
26
|
+
# @return [SprintMarketPosition]
|
27
|
+
def [](deal_id)
|
28
|
+
all.detect do |sprint_market_position|
|
29
|
+
sprint_market_position.deal_id == deal_id
|
30
|
+
end
|
17
31
|
end
|
18
32
|
|
19
33
|
# Creates a new sprint market position.
|
@@ -30,7 +44,7 @@ module IGMarkets
|
|
30
44
|
def create(attributes)
|
31
45
|
payload = PayloadFormatter.format SprintMarketPositionCreateAttributes.new attributes
|
32
46
|
|
33
|
-
@dealing_platform.session.post('positions/sprintmarkets', payload
|
47
|
+
@dealing_platform.session.post('positions/sprintmarkets', payload).fetch :deal_reference
|
34
48
|
end
|
35
49
|
|
36
50
|
# Internal model used by {#create}
|