diff --git a/app/models/account/market_data_importer.rb b/app/models/account/market_data_importer.rb index af5383140..d00c22347 100644 --- a/app/models/account/market_data_importer.rb +++ b/app/models/account/market_data_importer.rb @@ -24,12 +24,18 @@ class Account::MarketDataImporter .each do |source_currency, date| key = [ source_currency, account.currency ] pair_dates[key] = [ pair_dates[key], date ].compact.min + + inverse_key = [ account.currency, source_currency ] + pair_dates[inverse_key] = [ pair_dates[inverse_key], date ].compact.min end # 2. ACCOUNT-BASED PAIR – convert the account currency to the family currency (if different) if foreign_account? key = [ account.currency, account.family.currency ] pair_dates[key] = [ pair_dates[key], account.start_date ].compact.min + + inverse_key = [ account.family.currency, account.currency ] + pair_dates[inverse_key] = [ pair_dates[inverse_key], account.start_date ].compact.min end pair_dates.each do |(source, target), start_date| diff --git a/app/models/market_data_importer.rb b/app/models/market_data_importer.rb index 86c3c2351..d1ac7d7d7 100644 --- a/app/models/market_data_importer.rb +++ b/app/models/market_data_importer.rb @@ -76,6 +76,9 @@ class MarketDataImporter .each do |(source, target), date| key = [ source, target ] pair_dates[key] = [ pair_dates[key], date ].compact.min + + inverse_key = [ target, source ] + pair_dates[inverse_key] = [ pair_dates[inverse_key], date ].compact.min end # 2. ACCOUNT-BASED PAIRS – use the account's oldest entry date @@ -91,6 +94,9 @@ class MarketDataImporter key = [ account.source, account.target ] pair_dates[key] = [ pair_dates[key], chosen_date ].compact.min + + inverse_key = [ account.target, account.source ] + pair_dates[inverse_key] = [ pair_dates[inverse_key], chosen_date ].compact.min end # Convert to array of hashes for ease of use diff --git a/test/models/account/market_data_importer_test.rb b/test/models/account/market_data_importer_test.rb index b74401997..4a305e94f 100644 --- a/test/models/account/market_data_importer_test.rb +++ b/test/models/account/market_data_importer_test.rb @@ -35,6 +35,7 @@ class Account::MarketDataImporterTest < ActiveSupport::TestCase # Seed a rate for the first required day so that the importer only needs the next day forward existing_date = account.start_date ExchangeRate.create!(from_currency: "CAD", to_currency: "USD", date: existing_date, rate: 2.0) + ExchangeRate.create!(from_currency: "USD", to_currency: "CAD", date: existing_date, rate: 0.5) expected_start_date = (existing_date + 1.day) - PROVIDER_BUFFER end_date = Date.current.in_time_zone("America/New_York").to_date @@ -48,11 +49,20 @@ class Account::MarketDataImporterTest < ActiveSupport::TestCase OpenStruct.new(from: "CAD", to: "USD", date: existing_date, rate: 1.5) ])) + @provider.expects(:fetch_exchange_rates) + .with(from: "USD", + to: "CAD", + start_date: expected_start_date, + end_date: end_date) + .returns(provider_success_response([ + OpenStruct.new(from: "USD", to: "CAD", date: existing_date, rate: 0.67) + ])) + before = ExchangeRate.count Account::MarketDataImporter.new(account).import_all after = ExchangeRate.count - assert_operator after, :>, before, "Should insert at least one new exchange-rate row" + assert_operator after, :>, before + 1, "Should insert at least two new exchange-rate rows" end test "syncs security prices for securities traded by the account" do @@ -169,6 +179,7 @@ class Account::MarketDataImporterTest < ActiveSupport::TestCase # Seed a rate for the first required day existing_date = account.start_date ExchangeRate.create!(from_currency: "CAD", to_currency: "USD", date: existing_date, rate: 2.0) + ExchangeRate.create!(from_currency: "USD", to_currency: "CAD", date: existing_date, rate: 0.5) expected_start_date = (existing_date + 1.day) - PROVIDER_BUFFER end_date = Date.current.in_time_zone("America/New_York").to_date @@ -183,6 +194,15 @@ class Account::MarketDataImporterTest < ActiveSupport::TestCase Provider::TwelveData::Error.new("Rate limit exceeded", details: { code: 429, message: "Rate limit exceeded" }) )) + @provider.expects(:fetch_exchange_rates) + .with(from: "USD", + to: "CAD", + start_date: expected_start_date, + end_date: end_date) + .returns(provider_error_response( + Provider::TwelveData::Error.new("Rate limit exceeded", details: { code: 429, message: "Rate limit exceeded" }) + )) + before = ExchangeRate.count # Should not raise an error, just log and continue diff --git a/test/models/market_data_importer_test.rb b/test/models/market_data_importer_test.rb index b5d6854ef..2a9ff3460 100644 --- a/test/models/market_data_importer_test.rb +++ b/test/models/market_data_importer_test.rb @@ -34,6 +34,11 @@ class MarketDataImporterTest < ActiveSupport::TestCase date: SNAPSHOT_START_DATE, rate: 2.0) + ExchangeRate.create!(from_currency: "USD", + to_currency: "CAD", + date: SNAPSHOT_START_DATE, + rate: 0.5) + expected_start_date = (SNAPSHOT_START_DATE + 1.day) - PROVIDER_BUFFER end_date = Date.current.in_time_zone("America/New_York").to_date @@ -46,11 +51,20 @@ class MarketDataImporterTest < ActiveSupport::TestCase OpenStruct.new(from: "CAD", to: "USD", date: SNAPSHOT_START_DATE, rate: 1.5) ])) + @provider.expects(:fetch_exchange_rates) + .with(from: "USD", + to: "CAD", + start_date: expected_start_date, + end_date: end_date) + .returns(provider_success_response([ + OpenStruct.new(from: "USD", to: "CAD", date: SNAPSHOT_START_DATE, rate: 0.67) + ])) + before = ExchangeRate.count MarketDataImporter.new(mode: :snapshot).import_exchange_rates after = ExchangeRate.count - assert_operator after, :>, before, "Should insert at least one new exchange-rate row" + assert_operator after, :>, before + 1, "Should insert at least two new exchange-rate rows" end test "syncs security prices" do