mod api;
pub(crate) mod core;
pub mod error;
pub mod interop;
pub mod macros;
pub mod market;
pub mod models;
pub mod storage;
pub mod time;
pub use api::{
download, download_incremental, download_incremental_with_client,
download_incremental_with_concurrency, download_range, download_range_with_client,
download_range_with_concurrency, download_with_client, download_with_concurrency, Period,
Ticker, DEFAULT_DOWNLOAD_CONCURRENCY,
};
pub use core::catalog::{AssetClass, InstrumentCatalog, InstrumentDefinition};
pub use error::DukascopyError;
pub use interop::{flatten_row, flatten_rows, FlatExchangeRow};
pub use models::{CurrencyExchange, CurrencyPair, RateRequest, RequestParseMode};
pub use storage::checkpoint::{CheckpointStore, FileCheckpointStore};
#[cfg(feature = "sinks-parquet")]
pub use storage::sink::ParquetSink;
pub use storage::sink::{CsvSink, DataSink, NoopSink};
pub type Error = DukascopyError;
pub type Result<T> = std::result::Result<T, Error>;
use chrono::{DateTime, Duration, Utc};
#[inline]
pub async fn get_rate(from: &str, to: &str, timestamp: DateTime<Utc>) -> Result<CurrencyExchange> {
let pair = CurrencyPair::try_new(from, to)?;
core::client::DukascopyClient::get_exchange_rate(&pair, timestamp).await
}
#[inline]
pub async fn get_rate_for_request(
request: &RateRequest,
timestamp: DateTime<Utc>,
) -> Result<CurrencyExchange> {
core::client::DukascopyClient::get_exchange_rate_for_request(request, timestamp).await
}
#[inline]
pub async fn get_rate_for_input(input: &str, timestamp: DateTime<Utc>) -> Result<CurrencyExchange> {
let request: RateRequest = input.parse()?;
get_rate_for_request(&request, timestamp).await
}
#[inline]
pub async fn get_rate_for_input_with_mode(
input: &str,
mode: RequestParseMode,
timestamp: DateTime<Utc>,
) -> Result<CurrencyExchange> {
let request = RateRequest::parse_with_mode(input, mode)?;
get_rate_for_request(&request, timestamp).await
}
#[inline]
pub async fn get_rate_for_pair(
pair: &CurrencyPair,
timestamp: DateTime<Utc>,
) -> Result<CurrencyExchange> {
core::client::DukascopyClient::get_exchange_rate(pair, timestamp).await
}
#[inline]
pub async fn get_rates_range(
from: &str,
to: &str,
start: DateTime<Utc>,
end: DateTime<Utc>,
interval: Duration,
) -> Result<Vec<CurrencyExchange>> {
let pair = CurrencyPair::try_new(from, to)?;
core::client::DukascopyClient::get_exchange_rates_range(&pair, start, end, interval).await
}
#[inline]
pub async fn get_rates_range_for_pair(
pair: &CurrencyPair,
start: DateTime<Utc>,
end: DateTime<Utc>,
interval: Duration,
) -> Result<Vec<CurrencyExchange>> {
core::client::DukascopyClient::get_exchange_rates_range(pair, start, end, interval).await
}
#[inline]
pub async fn get_rate_for_symbol(
symbol: &str,
timestamp: DateTime<Utc>,
) -> Result<CurrencyExchange> {
core::client::DukascopyClient::get_exchange_rate_for_symbol(symbol, timestamp).await
}
#[inline]
pub async fn get_rate_in_quote(
symbol: &str,
quote: &str,
timestamp: DateTime<Utc>,
) -> Result<CurrencyExchange> {
core::client::DukascopyClient::get_exchange_rate_in_quote(symbol, quote, timestamp).await
}
pub use market::{get_market_status, is_market_open, is_weekend, MarketStatus};
pub mod advanced {
pub use crate::core::catalog::{AssetClass, InstrumentCatalog, InstrumentDefinition};
pub use crate::core::client::{
ClientConfig, ConfiguredClient, ConversionMode, ConversionPathType, DukascopyClient,
DukascopyClientBuilder, PairResolutionMode, ResolvedExchange, DEFAULT_CACHE_SIZE,
DEFAULT_MAX_AT_OR_BEFORE_BACKTRACK_HOURS, DEFAULT_MAX_DOWNLOAD_CONCURRENCY,
DEFAULT_MAX_IDLE_CONNECTIONS, DEFAULT_MAX_IN_FLIGHT_REQUESTS, DEFAULT_MAX_RETRIES,
DEFAULT_RETRY_BASE_DELAY_MS, DEFAULT_TIMEOUT_SECS, DUKASCOPY_BASE_URL,
GLOBAL_DEFAULT_QUOTE_CURRENCY,
};
pub use crate::core::instrument::{
resolve_instrument_config, CurrencyCategory, DefaultInstrumentProvider,
HasInstrumentConfig, InstrumentConfig, InstrumentProvider, OverrideInstrumentProvider,
DIVISOR_2_DECIMALS, DIVISOR_3_DECIMALS, DIVISOR_5_DECIMALS,
};
pub use crate::core::parser::{DukascopyParser, ParsedTick, TICK_SIZE_BYTES};
pub use crate::market::last_available_tick_time;
pub use crate::models::{RateRequest, RequestParseMode};
pub use crate::storage::checkpoint::{CheckpointStore, FileCheckpointStore};
#[cfg(feature = "sinks-parquet")]
pub use crate::storage::sink::ParquetSink;
pub use crate::storage::sink::{CsvSink, DataSink, NoopSink};
}
pub mod prelude {
pub use crate::api::{
download, download_incremental, download_incremental_with_client,
download_incremental_with_concurrency, download_range, download_range_with_client,
download_range_with_concurrency, download_with_client, download_with_concurrency, Period,
Ticker, DEFAULT_DOWNLOAD_CONCURRENCY,
};
pub use crate::core::catalog::{AssetClass, InstrumentCatalog, InstrumentDefinition};
pub use crate::error::DukascopyError;
pub use crate::market::{is_market_open, is_weekend, MarketStatus};
pub use crate::models::{CurrencyExchange, CurrencyPair, RateRequest, RequestParseMode};
pub use crate::storage::checkpoint::{CheckpointStore, FileCheckpointStore};
#[cfg(feature = "sinks-parquet")]
pub use crate::storage::sink::ParquetSink;
pub use crate::storage::sink::{CsvSink, DataSink, NoopSink};
pub use crate::time::{
date, datetime, days_ago, hours_ago, now, try_datetime_utc, weeks_ago, DateTime, Duration,
Utc,
};
pub use crate::{datetime, ticker, try_datetime, try_ticker};
pub use crate::{
get_rate, get_rate_for_input, get_rate_for_input_with_mode, get_rate_for_pair,
get_rate_for_request, get_rate_for_symbol, get_rate_in_quote, get_rates_range,
get_rates_range_for_pair,
};
pub use crate::{Error, Result};
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Utc;
#[tokio::test]
async fn test_get_rate_for_input_rejects_empty_request() {
let err = get_rate_for_input(" ", Utc::now()).await.unwrap_err();
assert!(matches!(err, DukascopyError::InvalidRequest(_)));
}
#[tokio::test]
async fn test_get_rate_for_input_rejects_invalid_symbol() {
let err = get_rate_for_input("BAD$", Utc::now()).await.unwrap_err();
assert!(matches!(
err,
DukascopyError::InvalidCurrencyCode { code, .. } if code == "BAD$"
));
}
#[tokio::test]
async fn test_get_rate_for_input_rejects_invalid_pair() {
let err = get_rate_for_input("EUR/US$", Utc::now()).await.unwrap_err();
assert!(matches!(
err,
DukascopyError::InvalidCurrencyCode { code, .. } if code == "US$"
));
}
#[tokio::test]
async fn test_get_rate_for_request_rejects_invalid_pair_before_network() {
let request = RateRequest::pair("BAD$", "USD");
let err = get_rate_for_request(&request, Utc::now())
.await
.unwrap_err();
assert!(matches!(
err,
DukascopyError::InvalidCurrencyCode { code, .. } if code == "BAD$"
));
}
#[tokio::test]
async fn test_get_rate_for_request_rejects_invalid_symbol_before_network() {
let request = RateRequest::Symbol("BAD$".to_string());
let err = get_rate_for_request(&request, Utc::now())
.await
.unwrap_err();
assert!(matches!(
err,
DukascopyError::InvalidCurrencyCode { code, .. } if code == "BAD$"
));
}
}