ig_markets 0.1 → 0.2

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +32 -16
  4. data/bin/ig_markets +29 -0
  5. data/lib/ig_markets.rb +15 -0
  6. data/lib/ig_markets/account_activity.rb +1 -1
  7. data/lib/ig_markets/account_transaction.rb +1 -1
  8. data/lib/ig_markets/application.rb +2 -2
  9. data/lib/ig_markets/cli/account_command.rb +40 -0
  10. data/lib/ig_markets/cli/activities_command.rb +33 -0
  11. data/lib/ig_markets/cli/confirmation_command.rb +32 -0
  12. data/lib/ig_markets/cli/main.rb +39 -0
  13. data/lib/ig_markets/cli/orders_command.rb +28 -0
  14. data/lib/ig_markets/cli/positions_command.rb +20 -0
  15. data/lib/ig_markets/cli/search_command.rb +30 -0
  16. data/lib/ig_markets/cli/sentiment_command.rb +35 -0
  17. data/lib/ig_markets/cli/sprints_command.rb +28 -0
  18. data/lib/ig_markets/cli/transactions_command.rb +56 -0
  19. data/lib/ig_markets/cli/watchlists_command.rb +34 -0
  20. data/lib/ig_markets/deal_confirmation.rb +4 -4
  21. data/lib/ig_markets/dealing_platform/position_methods.rb +3 -3
  22. data/lib/ig_markets/dealing_platform/sprint_market_position_methods.rb +1 -1
  23. data/lib/ig_markets/dealing_platform/watchlist_methods.rb +1 -3
  24. data/lib/ig_markets/dealing_platform/working_order_methods.rb +10 -10
  25. data/lib/ig_markets/format.rb +36 -0
  26. data/lib/ig_markets/historical_price_result.rb +1 -1
  27. data/lib/ig_markets/instrument.rb +2 -2
  28. data/lib/ig_markets/market.rb +10 -10
  29. data/lib/ig_markets/model.rb +23 -60
  30. data/lib/ig_markets/model/typecasters.rb +78 -0
  31. data/lib/ig_markets/payload_formatter.rb +19 -6
  32. data/lib/ig_markets/position.rb +7 -21
  33. data/lib/ig_markets/request_failed_error.rb +1 -1
  34. data/lib/ig_markets/sprint_market_position.rb +18 -3
  35. data/lib/ig_markets/version.rb +1 -1
  36. data/lib/ig_markets/working_order.rb +11 -11
  37. metadata +39 -64
  38. data/.codeclimate.yml +0 -15
  39. data/.gitignore +0 -9
  40. data/.rspec +0 -2
  41. data/.rubocop.yml +0 -2
  42. data/.travis.yml +0 -10
  43. data/.yardopts +0 -4
  44. data/Gemfile +0 -2
  45. data/ig_markets.gemspec +0 -28
  46. data/spec/factories/ig_markets/account.rb +0 -14
  47. data/spec/factories/ig_markets/account_activity.rb +0 -21
  48. data/spec/factories/ig_markets/account_balance.rb +0 -8
  49. data/spec/factories/ig_markets/account_transaction.rb +0 -15
  50. data/spec/factories/ig_markets/application.rb +0 -21
  51. data/spec/factories/ig_markets/client_sentiment.rb +0 -7
  52. data/spec/factories/ig_markets/deal_confirmation.rb +0 -20
  53. data/spec/factories/ig_markets/historical_price_result.rb +0 -7
  54. data/spec/factories/ig_markets/historical_price_result_data_allowance.rb +0 -7
  55. data/spec/factories/ig_markets/historical_price_result_price.rb +0 -7
  56. data/spec/factories/ig_markets/historical_price_result_snapshot.rb +0 -10
  57. data/spec/factories/ig_markets/instrument.rb +0 -32
  58. data/spec/factories/ig_markets/instrument_currency.rb +0 -9
  59. data/spec/factories/ig_markets/instrument_expiry_details.rb +0 -6
  60. data/spec/factories/ig_markets/instrument_margin_deposit_band.rb +0 -8
  61. data/spec/factories/ig_markets/instrument_opening_hours.rb +0 -6
  62. data/spec/factories/ig_markets/instrument_rollover_details.rb +0 -6
  63. data/spec/factories/ig_markets/instrument_slippage_factor.rb +0 -6
  64. data/spec/factories/ig_markets/market.rb +0 -7
  65. data/spec/factories/ig_markets/market_dealing_rules.rb +0 -11
  66. data/spec/factories/ig_markets/market_dealing_rules_rule_details.rb +0 -6
  67. data/spec/factories/ig_markets/market_hierarchy_result.rb +0 -6
  68. data/spec/factories/ig_markets/market_hierarchy_result_hierarchy_node.rb +0 -6
  69. data/spec/factories/ig_markets/market_overview.rb +0 -22
  70. data/spec/factories/ig_markets/market_snapshot.rb +0 -17
  71. data/spec/factories/ig_markets/position.rb +0 -19
  72. data/spec/factories/ig_markets/sprint_market_position.rb +0 -16
  73. data/spec/factories/ig_markets/watchlist.rb +0 -9
  74. data/spec/factories/ig_markets/working_order.rb +0 -21
  75. data/spec/ig_markets/account_transaction_spec.rb +0 -30
  76. data/spec/ig_markets/dealing_platform/account_methods_spec.rb +0 -58
  77. data/spec/ig_markets/dealing_platform/client_sentiment_methods_spec.rb +0 -29
  78. data/spec/ig_markets/dealing_platform/market_methods_spec.rb +0 -80
  79. data/spec/ig_markets/dealing_platform/position_methods_spec.rb +0 -137
  80. data/spec/ig_markets/dealing_platform/sprint_market_position_methods_spec.rb +0 -39
  81. data/spec/ig_markets/dealing_platform/watchlist_methods_spec.rb +0 -89
  82. data/spec/ig_markets/dealing_platform/working_order_methods_spec.rb +0 -120
  83. data/spec/ig_markets/dealing_platform_spec.rb +0 -40
  84. data/spec/ig_markets/model_spec.rb +0 -127
  85. data/spec/ig_markets/password_encryptor_spec.rb +0 -23
  86. data/spec/ig_markets/payload_formatter_spec.rb +0 -19
  87. data/spec/ig_markets/position_spec.rb +0 -37
  88. data/spec/ig_markets/response_parser_spec.rb +0 -13
  89. data/spec/ig_markets/session_spec.rb +0 -134
  90. data/spec/spec_helper.rb +0 -14
  91. data/spec/support/factory_girl.rb +0 -7
  92. data/spec/support/random_test_order.rb +0 -3
@@ -0,0 +1,35 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets sentiment` command.
4
+ class Main
5
+ desc 'sentiment', 'Prints sentiment for the specified market'
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'
9
+
10
+ def sentiment
11
+ begin_session do
12
+ result = dealing_platform.client_sentiment[options[:market]]
13
+
14
+ print_sentiment result
15
+
16
+ if options[:related]
17
+ result.related_sentiments.each do |model|
18
+ print_sentiment model
19
+ end
20
+ end
21
+ end
22
+ 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
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets sprints` command.
4
+ class Main
5
+ desc 'sprints', 'Prints open sprint market positions'
6
+
7
+ def sprints
8
+ begin_session do
9
+ dealing_platform.sprint_market_positions.all.each do |sprint|
10
+ print_sprint_market_position sprint
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
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
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,56 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets transactions` command.
4
+ class Main
5
+ desc 'transactions', 'Prints recent transactions'
6
+
7
+ option :days, default: 3, type: :numeric, desc: 'The number of days to print recent transactions for'
8
+
9
+ def transactions
10
+ begin_session do
11
+ transactions = dealing_platform.account.recent_transactions(seconds).sort_by(&:date)
12
+
13
+ transactions.each do |transaction|
14
+ print_transaction transaction
15
+ end
16
+
17
+ print_transaction_totals transactions
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def transaction_totals(transactions)
24
+ transactions.each_with_object({}) do |transaction, hash|
25
+ profit_loss = transaction.profit_and_loss_amount
26
+
27
+ currency = (hash[transaction.currency] ||= Hash.new(0))
28
+
29
+ currency[:delta] += profit_loss
30
+ currency[:interest] += profit_loss if transaction.interest?
31
+ end
32
+ end
33
+
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
+ def print_transaction_totals(transactions)
45
+ transaction_totals(transactions).each do |currency, value|
46
+ print <<-END
47
+
48
+ Totals for currency '#{currency}':
49
+ Interest: #{Format.currency value[:interest], currency}
50
+ Profit/loss: #{Format.currency value[:delta], currency}
51
+ END
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,34 @@
1
+ module IGMarkets
2
+ module CLI
3
+ # Implements the `ig_markets watchlists` command.
4
+ class Main
5
+ desc 'watchlists', 'Prints all watchlists and their markets'
6
+
7
+ def watchlists
8
+ begin_session do
9
+ dealing_platform.watchlists.all.each do |watchlist|
10
+ print_watchlist watchlist
11
+
12
+ watchlist.markets.each do |market|
13
+ print ' - '
14
+ print_market_overview market
15
+ end
16
+ print "\n"
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
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
31
+ end
32
+ end
33
+ end
34
+ end
@@ -13,10 +13,10 @@ module IGMarkets
13
13
  attribute :deal_status, Symbol, allowed_values: [:accepted, :fund_account, :rejected]
14
14
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
15
15
  attribute :epic
16
- attribute :expiry, DateTime, nil_if: '-', format: '%d-%b-%y'
16
+ attribute :expiry, Date, nil_if: '-', format: '%d-%b-%y'
17
17
  attribute :guaranteed_stop, Boolean
18
18
  attribute :level, Float
19
- attribute :limit_distance, Float
19
+ attribute :limit_distance, Fixnum
20
20
  attribute :limit_level, Float
21
21
  attribute :reason, Symbol, allowed_values: [
22
22
  :account_not_enabled_to_trading, :attached_order_level_error, :attached_order_trailing_stop_error,
@@ -32,9 +32,9 @@ module IGMarkets
32
32
  :reject_spreadbet_order_on_cfd_account, :size_increment, :sprint_market_expiry_after_market_close,
33
33
  :stop_or_limit_not_allowed, :stop_required_error, :strike_level_tolerance, :success, :trailing_stop_not_allowed,
34
34
  :unknown, :wrong_side_of_market]
35
- attribute :size, Fixnum
35
+ attribute :size, Float
36
36
  attribute :status, Symbol, allowed_values: [:amended, :closed, :deleted, :open, :partially_closed]
37
- attribute :stop_distance, Float
37
+ attribute :stop_distance, Fixnum
38
38
  attribute :stop_level, Float
39
39
  attribute :trailing_stop, Boolean
40
40
  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 [DateTime] :expiry The expiry date of the instrument, if it has one. Optional.
39
+ # @option attributes [Time] :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`.
@@ -89,7 +89,7 @@ module IGMarkets
89
89
  attribute :currency_code, String, regex: Regex::CURRENCY
90
90
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
91
91
  attribute :epic, String, regex: Regex::EPIC
92
- attribute :expiry, DateTime, format: '%d-%b-%y'
92
+ attribute :expiry, Time, format: '%d-%b-%y'
93
93
  attribute :force_open, Boolean
94
94
  attribute :guaranteed_stop, Boolean
95
95
  attribute :level, Float
@@ -97,7 +97,7 @@ module IGMarkets
97
97
  attribute :limit_level, Float
98
98
  attribute :order_type, Symbol, allowed_values: [:limit, :market, :quote]
99
99
  attribute :quote_id
100
- attribute :size, Fixnum
100
+ attribute :size, Float
101
101
  attribute :stop_distance, Fixnum
102
102
  attribute :stop_level, Float
103
103
  attribute :time_in_force, Symbol, allowed_values: [:execute_and_eliminate, :fill_or_kill]
@@ -39,7 +39,7 @@ module IGMarkets
39
39
  attribute :epic, String, regex: Regex::EPIC
40
40
  attribute :expiry_period, Symbol, allowed_values: [
41
41
  :one_minute, :two_minutes, :five_minutes, :twenty_minutes, :sixty_minutes]
42
- attribute :size, Fixnum
42
+ attribute :size, Float
43
43
  end
44
44
  end
45
45
  end
@@ -33,9 +33,7 @@ module IGMarkets
33
33
  def create(name, *epics)
34
34
  result = @dealing_platform.session.post 'watchlists', { name: name, epics: epics.flatten }, API_V1
35
35
 
36
- all.detect do |watchlist|
37
- watchlist.id == result.fetch(:watchlist_id)
38
- end
36
+ self[result.fetch(:watchlist_id)]
39
37
  end
40
38
  end
41
39
  end
@@ -40,15 +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 [DateTime] :expiry The expiry date of the instrument (if applicable). Optional.
43
+ # @option attributes [Time] :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 [DateTime] :good_till_date The date that the working order will live till. Must be set if
45
+ # @option attributes [Time] :good_till_date The date that the working order will live till. Must be set if
46
46
  # `:time_in_force` is `:good_till_date`.
47
47
  # @option attributes [Boolean] :guaranteed_stop Whether a guaranteed stop is required. Defaults to `false`.
48
48
  # @option attributes [Float] :level The level at which the order will be triggered.
49
- # @option attributes [Float] :limit_distance The distance away in pips to place the limit. Optional.
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
- # @option attributes [Float] :stop_distance The distance away in pips to place the stop. Optional.
51
+ # @option attributes [Fixnum] :stop_distance The distance away in pips to place the stop. Optional.
52
52
  # @option attributes [:good_till_cancelled, :good_till_date] :time_in_force The lifespan of the working order.
53
53
  # `:good_till_cancelled` means the working order will live until it is explicitly cancelled.
54
54
  # `:good_till_date` means the working order will live until the date specified by
@@ -85,7 +85,7 @@ module IGMarkets
85
85
  raise ArgumentError, "#{attribute} attribute must be set" if attributes[attribute].nil?
86
86
  end
87
87
 
88
- if model.time_in_force == :good_till_date && !model.good_till_date.is_a?(DateTime)
88
+ if model.time_in_force == :good_till_date && !model.good_till_date.is_a?(Time)
89
89
  raise ArgumentError, 'good_till_date must be set when time_in_force is :good_till_date'
90
90
  end
91
91
 
@@ -97,14 +97,14 @@ module IGMarkets
97
97
  attribute :currency_code, String, regex: Regex::CURRENCY
98
98
  attribute :direction, Symbol, allowed_values: [:buy, :sell]
99
99
  attribute :epic, String, regex: Regex::EPIC
100
- attribute :expiry, DateTime, format: '%d-%b-%y'
100
+ attribute :expiry, Time, format: '%d-%b-%y'
101
101
  attribute :force_open, Boolean
102
- attribute :good_till_date, DateTime, format: '%Y-%m-%d %H:%M:%S'
102
+ attribute :good_till_date, Time, format: '%F %T'
103
103
  attribute :guaranteed_stop, Boolean
104
104
  attribute :level, Float
105
- attribute :limit_distance, Float
106
- attribute :size, Fixnum
107
- attribute :stop_distance, Float
105
+ attribute :limit_distance, Fixnum
106
+ attribute :size, Float
107
+ attribute :stop_distance, Fixnum
108
108
  attribute :time_in_force, Symbol, allowed_values: [:good_till_cancelled, :good_till_date]
109
109
  attribute :type, Symbol, allowed_values: [:limit, :stop]
110
110
  end
@@ -0,0 +1,36 @@
1
+ module IGMarkets
2
+ # This module contains shared methods for formatting different content types for display.
3
+ module Format
4
+ module_function
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.
8
+ #
9
+ # @param [Float, Fixnum] amount The currency amount to format.
10
+ # @param [String] symbol The currency symbol.
11
+ #
12
+ # @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}"
16
+ else
17
+ "#{symbol} #{format '%.2f', amount.to_f}"
18
+ end
19
+ end
20
+
21
+ # Returns a formatted string for the specified number of seconds in the format `[<hours>:]<minutes>:<seconds>`.
22
+ #
23
+ # @param [Fixnum] value The number of seconds to format.
24
+ #
25
+ # @return [String]
26
+ def seconds(value)
27
+ result = if value >= 60 * 60
28
+ "#{value / 60 / 60}:#{Kernel.format('%02i', (value / 60) % 60)}"
29
+ else
30
+ (value / 60).to_s
31
+ end
32
+
33
+ result + ':' + Kernel.format('%02i', value % 60)
34
+ end
35
+ end
36
+ end
@@ -23,7 +23,7 @@ module IGMarkets
23
23
  attribute :last_traded_volume, Float
24
24
  attribute :low_price, Price
25
25
  attribute :open_price, Price
26
- attribute :snapshot_time, DateTime, format: '%Y/%m/%d %H:%M:%S'
26
+ attribute :snapshot_time, Time, format: '%Y/%m/%d %T', time_zone: '+1000'
27
27
  end
28
28
 
29
29
  attribute :allowance, DataAllowance
@@ -12,7 +12,7 @@ module IGMarkets
12
12
 
13
13
  # Contains details on the expiry details of an instrument. Returned by {#expiry_details}.
14
14
  class ExpiryDetails < Model
15
- attribute :last_dealing_date, DateTime, format: '%Y/%m/%d %H:%M:%S'
15
+ attribute :last_dealing_date, Time, format: '%FT%R', time_zone: '+1000'
16
16
  attribute :settlement_info
17
17
  end
18
18
 
@@ -60,7 +60,7 @@ module IGMarkets
60
60
  attribute :country
61
61
  attribute :currencies, Currency
62
62
  attribute :epic, String, regex: Regex::EPIC
63
- attribute :expiry, DateTime, nil_if: '-', format: '%d-%b-%y'
63
+ attribute :expiry, Date, nil_if: '-', format: '%d-%b-%y'
64
64
  attribute :expiry_details, ExpiryDetails
65
65
  attribute :force_open_allowed, Boolean
66
66
  attribute :lot_size, Float
@@ -62,17 +62,17 @@ module IGMarkets
62
62
  #
63
63
  # @param [:minute, :minute_2, :minute_3, :minute_5, :minute_10, :minute_15, :minute_30, :hour, :hour_2, :hour_3,
64
64
  # :hour_4, :day, :week, :month] resolution The resolution of the historical prices to return.
65
- # @param [DateTime] start_date_time The start of the desired time period.
66
- # @param [DateTime] end_date_time The end of the desired time period.
65
+ # @param [Time] start_time The start of the desired time period.
66
+ # @param [Time] end_time The end of the desired time period.
67
67
  #
68
68
  # @return [HistoricalPriceResult]
69
- def prices_in_date_range(resolution, start_date_time, end_date_time)
69
+ def prices_in_date_range(resolution, start_time, end_time)
70
70
  validate_historical_price_resolution resolution
71
71
 
72
- start_date_time = format_date_time start_date_time
73
- end_date_time = format_date_time end_date_time
72
+ start_time = format_time start_time
73
+ end_time = format_time end_time
74
74
 
75
- url = "prices/#{instrument.epic}/#{resolution.to_s.upcase}/#{start_date_time}/#{end_date_time}"
75
+ url = "prices/#{instrument.epic}/#{resolution.to_s.upcase}/#{start_time}/#{end_time}"
76
76
 
77
77
  HistoricalPriceResult.from @dealing_platform.session.get(url, API_V2)
78
78
  end
@@ -89,11 +89,11 @@ module IGMarkets
89
89
  raise ArgumentError, 'resolution is invalid' unless resolutions.include? resolution
90
90
  end
91
91
 
92
- # Takes a `DateTime` and formats it for the historical prices API URLs.
92
+ # Takes a `Time` and formats it for the historical prices API URLs.
93
93
  #
94
- # @param [DateTime] date_time The `DateTime` to format.
95
- def format_date_time(date_time)
96
- date_time.strftime '%Y-%m-%dT%H:%M:%S'
94
+ # @param [Time] time The `Time` to format.
95
+ def format_time(time)
96
+ time.utc.strftime '%FT%T'
97
97
  end
98
98
  end
99
99
  end
@@ -19,9 +19,8 @@ module IGMarkets
19
19
 
20
20
  (attributes.keys - defined_attribute_names).map do |attribute|
21
21
  value = attributes[attribute]
22
- value = value.inspect unless value.is_a? DateTime
23
22
 
24
- raise ArgumentError, "Unknown attribute: #{self.class.name}##{attribute}, value: #{value}"
23
+ raise ArgumentError, "Unknown attribute: #{self.class.name}##{attribute}, value: #{inspect_value value}"
25
24
  end
26
25
  end
27
26
 
@@ -39,14 +38,25 @@ module IGMarkets
39
38
  # @return [String]
40
39
  def inspect
41
40
  formatted_attributes = self.class.defined_attribute_names.map do |attribute|
42
- value = send attribute
43
-
44
- "#{attribute}: #{value.is_a?(DateTime) ? value : value.inspect}"
41
+ "#{attribute}: #{inspect_value send(attribute)}"
45
42
  end
46
43
 
47
44
  "#<#{self.class.name} #{formatted_attributes.join ', '}>"
48
45
  end
49
46
 
47
+ private
48
+
49
+ # Returns the #inspect string for the given value.
50
+ def inspect_value(value)
51
+ if value.is_a? Time
52
+ value.utc.strftime '%F %T %z'
53
+ elsif value.is_a? Date
54
+ value.strftime '%F'
55
+ else
56
+ value.inspect
57
+ end
58
+ end
59
+
50
60
  class << self
51
61
  # @return [Hash] A hash containing details of all attributes that have been defined on this model.
52
62
  attr_accessor :defined_attributes
@@ -70,15 +80,17 @@ module IGMarkets
70
80
  # Defines setter and getter methods for a new attribute on this class.
71
81
  #
72
82
  # @param [Symbol] name The name of the new attribute.
73
- # @param [Boolean, String, DateTime, Fixnum, Float, Symbol, #from] type The attribute's type.
83
+ # @param [Boolean, String, Date, Time, Fixnum, Float, Symbol, #from] type The attribute's type.
74
84
  # @param [Hash] options The configuration options for the new attribute.
75
85
  # @option options [Array] :allowed_values The set of values that this attribute is allowed to be set to. An
76
86
  # attempt to set this attribute to a value not in this list will raise `ArgumentError`. Optional.
77
87
  # @option options [Array] :nil_if Values that, when set on the attribute, should be converted to `nil`.
78
88
  # @option options [Regexp] :regex When `type` is `String` only values matching this regex will be allowed.
79
89
  # Optional.
80
- # @option options [String] :format When `type` is `DateTime` this specifies the format for parsing String and
81
- # Fixnum instances assigned to this attribute. See `DateTime#strptime` for details.
90
+ # @option options [String] :format When `type` is `Date` or `Time` this specifies the format for parsing String
91
+ # and `Fixnum` instances assigned to this attribute.
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.
82
94
  #
83
95
  # @macro [attach] attribute
84
96
  # The $1 attribute.
@@ -87,7 +99,8 @@ module IGMarkets
87
99
  define_attribute_reader name
88
100
  define_attribute_writer name, type, options
89
101
 
90
- (self.defined_attributes ||= {})[name] = options.merge type: type
102
+ self.defined_attributes ||= {}
103
+ self.defined_attributes[name] = options.merge type: type
91
104
  end
92
105
 
93
106
  # Creates a new Model instance from the specified source, which can take a variety of different forms.
@@ -120,7 +133,7 @@ module IGMarkets
120
133
  define_method "#{name}=" do |value|
121
134
  value = nil if Array(options.fetch(:nil_if, [])).include? value
122
135
 
123
- value = typecaster.call(value, options)
136
+ value = typecaster.call value, options
124
137
 
125
138
  allowed_values = options[:allowed_values]
126
139
  if !value.nil? && allowed_values
@@ -130,56 +143,6 @@ module IGMarkets
130
143
  (@attributes ||= {})[name] = value
131
144
  end
132
145
  end
133
-
134
- def typecaster_for(type)
135
- if [Boolean, String, Fixnum, Float, Symbol, DateTime].include? type
136
- method "typecaster_#{type.to_s.gsub(/\AIGMarkets::/, '').downcase}"
137
- elsif type.respond_to? :from
138
- -> (value, _options) { type.from value }
139
- end
140
- end
141
-
142
- def typecaster_boolean(value, _options)
143
- return value if [nil, true, false].include? value
144
-
145
- raise ArgumentError, "#{self}: invalid boolean value: #{value}"
146
- end
147
-
148
- def typecaster_string(value, options)
149
- return nil if value.nil?
150
-
151
- if options.key? :regex
152
- raise ArgumentError, "#{self}: invalid string value: #{value}" unless options[:regex].match value.to_s
153
- end
154
-
155
- value.to_s
156
- end
157
-
158
- def typecaster_fixnum(value, _options)
159
- value.nil? ? nil : value.to_s.to_i
160
- end
161
-
162
- def typecaster_float(value, _options)
163
- value.nil? ? nil : Float(value)
164
- end
165
-
166
- def typecaster_symbol(value, _options)
167
- value.nil? ? nil : value.to_s.downcase.to_sym
168
- end
169
-
170
- def typecaster_datetime(value, options)
171
- raise ArgumentError, "#{self}: invalid or missing date time format" unless options[:format].is_a? String
172
-
173
- if value.is_a?(String) || value.is_a?(Fixnum)
174
- begin
175
- DateTime.strptime(value.to_s, options[:format])
176
- rescue ArgumentError
177
- raise ArgumentError, "#{self}: failed parsing date '#{value}' with format '#{options[:format]}'"
178
- end
179
- else
180
- value
181
- end
182
- end
183
146
  end
184
147
  end
185
148
  end