@@ -32,6 +32,7 @@ import com.davidcrespo.onewallet.domain.cache.CachePolicy
3232import com.davidcrespo.onewallet.domain.di.DispatcherProvider
3333import com.davidcrespo.onewallet.domain.logging.Telemetry
3434import com.davidcrespo.onewallet.domain.model.investment.Currency
35+ import com.davidcrespo.onewallet.domain.model.investment.DataSource
3536import com.davidcrespo.onewallet.domain.model.investment.EUR
3637import com.davidcrespo.onewallet.domain.model.investment.Investment
3738import com.davidcrespo.onewallet.domain.model.investment.InvestmentType
@@ -68,14 +69,15 @@ class FinancialRepositoryImpl(
6869 name : String ,
6970 selectedCurrency : Currency ? ,
7071 marketType : MarketType ? ,
71- investmentCurrency : Currency ?
72+ investmentCurrency : Currency ? ,
73+ preferredApi : DataSource ?
7274 ): Result <Investment > {
7375 return withContext(dispatcher.io) {
7476 when (type) {
75- InvestmentType .STOCK -> getStockPrice(symbol, name, marketType, investmentCurrency)
77+ InvestmentType .STOCK -> getStockPrice(symbol, name, marketType, investmentCurrency, preferredApi )
7678 InvestmentType .CRYPTO -> getCryptoPrice(symbol)
77- InvestmentType .FUND -> getFundPrice(symbol)
78- InvestmentType .ETF -> getEtfPrice(symbol, selectedCurrency)
79+ InvestmentType .FUND -> getFundPrice(symbol, preferredApi )
80+ InvestmentType .ETF -> getEtfPrice(symbol, selectedCurrency, preferredApi )
7981 else -> Result .failure(IllegalArgumentException (" Invalid investment type: $type " ))
8082 }
8183 }
@@ -87,64 +89,104 @@ class FinancialRepositoryImpl(
8789 ?.toDomain()
8890 ?.let { return @runCatching it }
8991
90- val dto = binanceDataSource.getCryptoPrice(symbol)
91- val valid = dto.takeIf { it.isValidPrice() }
92+ tryFetch(symbol, DataSource .BINANCE ) { binanceDataSource.getCryptoPrice(symbol) }
9293 ? : throw IllegalStateException (" No se pudo obtener el precio de $symbol " )
93-
94- telemetry.log(" (Binance) get $symbol from remote ${valid.price} ${valid.currency} " )
95- symbolCache.setCachedInvestment(valid.toEntity())
96- valid.toDomain()
9794 }
9895 }
9996
100- private suspend fun getStockPrice (symbol : String , name : String , marketType : MarketType ? , currency : Currency ? ): Result <Investment > =
97+ private suspend fun getStockPrice (symbol : String , name : String , marketType : MarketType ? , currency : Currency ? , preferredApi : DataSource ? ): Result <Investment > =
10198 runCatching {
102- symbolCache.getCachedInvestmentIfValid(symbol, cachePolicy.stockHours)
103- ?.toDomain()
104- ?.let { return @runCatching it }
99+ val cachedValid = symbolCache.getCachedInvestmentIfValid(symbol, cachePolicy.stockHours)
100+ cachedValid?.toDomain()?.let { return @runCatching it }
101+
102+ val apiToTryFirst = preferredApi ? : symbolCache.getCachedInvestment(symbol)?.preferredApi
105103
106104 val country = marketType ? : MarketType .GLOBAL
107105
108106 when (country) {
109107 MarketType .US -> {
110- tryFetch(symbol, " Finnhub " ) { finnhubDataSource.getStockPrice(symbol, name) }
108+ tryFetch(symbol, DataSource . FINNHUB ) { finnhubDataSource.getStockPrice(symbol, name) }
111109 ? : throw IllegalStateException (" No se pudo obtener el precio del fondo" )
112110 }
113111 MarketType .GLOBAL -> {
114- tryFetch(symbol, " Yahoo Finance" ) { yahooFinanceDataSource.getStockPrice(symbol, name) }
115- ? : tryFetch(symbol, " Finnhub" ) { finnhubDataSource.getStockPrice(symbol, name) }
116- ? : tryFetch(symbol, " Alpha Vantage" ) { alphaVantageDataSource.getStockPrice(symbol, name, currency?.toDto() ? : CurrencyDto (USD )) }
117- ? : tryFetch(symbol, " Marketstack" ) { marketstackDataSource.getStockPrice(symbol, name) }
112+ val apis = listOf<Pair <DataSource , suspend () - > InvestmentDto ? >> (
113+ DataSource .YAHOO_FINANCE to { yahooFinanceDataSource.getStockPrice(symbol, name) },
114+ DataSource .FINNHUB to { finnhubDataSource.getStockPrice(symbol, name) },
115+ DataSource .ALPHA_VANTAGE to { alphaVantageDataSource.getStockPrice(symbol, name, currency?.toDto() ? : CurrencyDto (USD )) },
116+ DataSource .MARKETSTACK to { marketstackDataSource.getStockPrice(symbol, name) }
117+ )
118+ tryFetchFromSequence(symbol, apis, apiToTryFirst)
118119 ? : throw IllegalStateException (" No se pudo obtener el precio del fondo" )
119120 }
120121 }
121122 }
122123
123- private suspend fun getFundPrice (isin : String ): Result <Investment > =
124+ private suspend fun getFundPrice (isin : String , preferredApi : DataSource ? ): Result <Investment > =
124125 runCatching {
125- symbolCache.getCachedInvestmentIfValid(isin, cachePolicy.fundHours)
126- ?.toDomain()
127- ?.let { return @runCatching it }
126+ val cachedValid = symbolCache.getCachedInvestmentIfValid(isin, cachePolicy.fundHours)
127+ cachedValid?.toDomain()?.let { return @runCatching it }
128+
129+ val apiToTryFirst = preferredApi ? : symbolCache.getCachedInvestment(isin)?.preferredApi
128130
129- tryFetch(isin, " Investing.com" ) { investingDataSource.getFundPrice(isin) }
130- ? : tryFetch(isin, " QueFondos.com" ) { queFondosDataSource.getFundPrice(isin, InvestmentType .FUND ) }
131+ val apis = listOf<Pair <DataSource , suspend () - > InvestmentDto ? >> (
132+ DataSource .INVESTING_COM to { investingDataSource.getFundPrice(isin) },
133+ DataSource .QUE_FONDOS to { queFondosDataSource.getFundPrice(isin, InvestmentType .FUND ) }
134+ )
135+
136+ tryFetchFromSequence(isin, apis, apiToTryFirst)
131137 ? : throw IllegalStateException (" No se pudo obtener el precio del fondo" )
132138 }
133139
134- private suspend fun getEtfPrice (isin : String , selectedCurrency : Currency ? ): Result <Investment > {
140+ private suspend fun getEtfPrice (isin : String , selectedCurrency : Currency ? , preferredApi : DataSource ? ): Result <Investment > {
135141 return runCatching {
136- symbolCache.getCachedInvestmentIfValid(isin, cachePolicy.etfHours)
137- ?.toDomain()
138- ?.let { return @runCatching it }
142+ val cachedValid = symbolCache.getCachedInvestmentIfValid(isin, cachePolicy.etfHours)
143+ cachedValid?.toDomain()?.let { return @runCatching it }
144+
145+ val apiToTryFirst = preferredApi ? : symbolCache.getCachedInvestment(isin)?.preferredApi
139146
140147 val currency = selectedCurrency?.toDto() ? : CurrencyDto (EUR )
141148
142- tryFetch(isin, " JustETF.com (detail)" ) { justEtfDataSource.getEtfDetail(isin, currency) }
143- ? : tryFetch(isin, " ExtraETF.com" ) { extraEtfDataSource.getEtfPrice(isin) }
144- ? : tryFetch(isin, " QueFondos.com" ) { queFondosDataSource.getFundPrice(isin, InvestmentType .ETF ) }
145- ? : tryFetch(isin, " JustETF.com (price)" , false ) { justEtfDataSource.getEtfPrice(isin, currency) }
146- ? : throw IllegalStateException (" No se pudo obtener el precio del ETF" )
149+ val apis = listOf<Triple <DataSource , Boolean , suspend () - > InvestmentDto ? >> (
150+ Triple (DataSource .JUST_ETF_DETAIL , true ) { justEtfDataSource.getEtfDetail(isin, currency) },
151+ Triple (DataSource .EXTRA_ETF , true ) { extraEtfDataSource.getEtfPrice(isin) },
152+ Triple (DataSource .QUE_FONDOS , true ) { queFondosDataSource.getFundPrice(isin, InvestmentType .ETF ) },
153+ Triple (DataSource .JUST_ETF_PRICE , false ) { justEtfDataSource.getEtfPrice(isin, currency) }
154+ )
155+
156+ if (apiToTryFirst != null ) {
157+ val preferred = apis.find { it.first == apiToTryFirst }
158+ if (preferred != null ) {
159+ tryFetch(isin, preferred.first, preferred.second, preferred.third)?.let { return @runCatching it }
160+ }
161+ }
162+
163+ for (api in apis) {
164+ if (api.first == apiToTryFirst) continue
165+ tryFetch(isin, api.first, api.second, api.third)?.let { return @runCatching it }
166+ }
167+
168+ throw IllegalStateException (" No se pudo obtener el precio del ETF" )
169+ }
170+ }
171+
172+ private suspend fun tryFetchFromSequence (
173+ isin : String ,
174+ apis : List <Pair <DataSource , suspend () - > InvestmentDto ? >>,
175+ preferredApi : DataSource ?
176+ ): Investment ? {
177+ if (preferredApi != null ) {
178+ val preferred = apis.find { it.first == preferredApi }
179+ if (preferred != null ) {
180+ tryFetch(isin, preferred.first, fetch = preferred.second)?.let { return it }
181+ }
147182 }
183+
184+ for ((source, fetch) in apis) {
185+ if (source == preferredApi) continue
186+ tryFetch(isin, source, fetch = fetch)?.let { return it }
187+ }
188+
189+ return null
148190 }
149191
150192 override suspend fun getStocksSymbols (exchange : String ): Result <List <MarketAsset >> =
@@ -251,15 +293,16 @@ class FinancialRepositoryImpl(
251293
252294 private suspend fun tryFetch (
253295 isin : String ,
254- source : String ,
296+ source : DataSource ,
255297 validateName : Boolean = true,
256298 fetch : suspend () -> InvestmentDto ?
257299 ): Investment ? {
258300 return runCatching {
259301 val inv = fetch()
260302 val valid = inv ?.takeIf { if (validateName) { it.isValidName() && it.isValidPrice() } else { it.isValidPrice()} }
303+ ?.copy(preferredApi = source)
261304 if (valid != null ) {
262- telemetry.log(" $source get $isin succeed ${valid.price} ${valid.currency.code} " )
305+ telemetry.log(" ${ source.value} get $isin succeed ${valid.price} ${valid.currency.code} " )
263306 symbolCache.setCachedInvestment(valid.toEntity())
264307 }
265308 valid?.toDomain()
0 commit comments