@@ -23,9 +23,10 @@ use std::str::FromStr;
2323use anyhow:: Context ;
2424use nautilus_core:: nanos:: UnixNanos ;
2525use nautilus_model:: {
26- data:: { Bar , BarType , TradeTick } ,
26+ data:: { Bar , BarSpecification , BarType , TradeTick } ,
2727 enums:: {
28- AggressorSide , LiquiditySide , OrderSide , OrderStatus , OrderType , TimeInForce , TriggerType ,
28+ AggressorSide , BarAggregation , LiquiditySide , OrderSide , OrderStatus , OrderType ,
29+ TimeInForce , TriggerType ,
2930 } ,
3031 identifiers:: {
3132 AccountId , ClientOrderId , InstrumentId , OrderListId , Symbol , TradeId , Venue , VenueOrderId ,
@@ -42,7 +43,7 @@ use serde_json::Value;
4243
4344use crate :: {
4445 common:: {
45- enums:: BinanceContractStatus ,
46+ enums:: { BinanceContractStatus , BinanceKlineInterval } ,
4647 fixed:: { mantissa_to_price, mantissa_to_quantity} ,
4748 sbe:: spot:: {
4849 order_side:: OrderSide as SbeOrderSide , order_status:: OrderStatus as SbeOrderStatus ,
@@ -875,6 +876,56 @@ pub fn parse_klines_to_bars(
875876 Ok ( bars)
876877}
877878
879+ /// Converts a Nautilus bar specification to a Binance kline interval.
880+ ///
881+ /// # Errors
882+ ///
883+ /// Returns an error if the bar specification does not map to a supported
884+ /// Binance kline interval.
885+ pub fn bar_spec_to_binance_interval (
886+ bar_spec : BarSpecification ,
887+ ) -> anyhow:: Result < BinanceKlineInterval > {
888+ let step = bar_spec. step . get ( ) ;
889+ let interval = match bar_spec. aggregation {
890+ BarAggregation :: Second => {
891+ anyhow:: bail!( "Binance Spot does not support second-level kline intervals" )
892+ }
893+ BarAggregation :: Minute => match step {
894+ 1 => BinanceKlineInterval :: Minute1 ,
895+ 3 => BinanceKlineInterval :: Minute3 ,
896+ 5 => BinanceKlineInterval :: Minute5 ,
897+ 15 => BinanceKlineInterval :: Minute15 ,
898+ 30 => BinanceKlineInterval :: Minute30 ,
899+ _ => anyhow:: bail!( "Unsupported minute interval: {step}m" ) ,
900+ } ,
901+ BarAggregation :: Hour => match step {
902+ 1 => BinanceKlineInterval :: Hour1 ,
903+ 2 => BinanceKlineInterval :: Hour2 ,
904+ 4 => BinanceKlineInterval :: Hour4 ,
905+ 6 => BinanceKlineInterval :: Hour6 ,
906+ 8 => BinanceKlineInterval :: Hour8 ,
907+ 12 => BinanceKlineInterval :: Hour12 ,
908+ _ => anyhow:: bail!( "Unsupported hour interval: {step}h" ) ,
909+ } ,
910+ BarAggregation :: Day => match step {
911+ 1 => BinanceKlineInterval :: Day1 ,
912+ 3 => BinanceKlineInterval :: Day3 ,
913+ _ => anyhow:: bail!( "Unsupported day interval: {step}d" ) ,
914+ } ,
915+ BarAggregation :: Week => match step {
916+ 1 => BinanceKlineInterval :: Week1 ,
917+ _ => anyhow:: bail!( "Unsupported week interval: {step}w" ) ,
918+ } ,
919+ BarAggregation :: Month => match step {
920+ 1 => BinanceKlineInterval :: Month1 ,
921+ _ => anyhow:: bail!( "Unsupported month interval: {step}M" ) ,
922+ } ,
923+ agg => anyhow:: bail!( "Unsupported bar aggregation for Binance: {agg:?}" ) ,
924+ } ;
925+
926+ Ok ( interval)
927+ }
928+
878929#[ cfg( test) ]
879930mod tests {
880931 use rstest:: rstest;
@@ -986,4 +1037,89 @@ mod tests {
9861037 . contains( "Missing PRICE_FILTER" )
9871038 ) ;
9881039 }
1040+
1041+ mod bar_spec_tests {
1042+ use std:: num:: NonZeroUsize ;
1043+
1044+ use nautilus_model:: {
1045+ data:: BarSpecification ,
1046+ enums:: { BarAggregation , PriceType } ,
1047+ } ;
1048+
1049+ use super :: * ;
1050+ use crate :: common:: enums:: BinanceKlineInterval ;
1051+
1052+ fn make_bar_spec ( step : usize , aggregation : BarAggregation ) -> BarSpecification {
1053+ BarSpecification {
1054+ step : NonZeroUsize :: new ( step) . unwrap ( ) ,
1055+ aggregation,
1056+ price_type : PriceType :: Last ,
1057+ }
1058+ }
1059+
1060+ #[ rstest]
1061+ #[ case( 1 , BarAggregation :: Minute , BinanceKlineInterval :: Minute1 ) ]
1062+ #[ case( 3 , BarAggregation :: Minute , BinanceKlineInterval :: Minute3 ) ]
1063+ #[ case( 5 , BarAggregation :: Minute , BinanceKlineInterval :: Minute5 ) ]
1064+ #[ case( 15 , BarAggregation :: Minute , BinanceKlineInterval :: Minute15 ) ]
1065+ #[ case( 30 , BarAggregation :: Minute , BinanceKlineInterval :: Minute30 ) ]
1066+ #[ case( 1 , BarAggregation :: Hour , BinanceKlineInterval :: Hour1 ) ]
1067+ #[ case( 2 , BarAggregation :: Hour , BinanceKlineInterval :: Hour2 ) ]
1068+ #[ case( 4 , BarAggregation :: Hour , BinanceKlineInterval :: Hour4 ) ]
1069+ #[ case( 6 , BarAggregation :: Hour , BinanceKlineInterval :: Hour6 ) ]
1070+ #[ case( 8 , BarAggregation :: Hour , BinanceKlineInterval :: Hour8 ) ]
1071+ #[ case( 12 , BarAggregation :: Hour , BinanceKlineInterval :: Hour12 ) ]
1072+ #[ case( 1 , BarAggregation :: Day , BinanceKlineInterval :: Day1 ) ]
1073+ #[ case( 3 , BarAggregation :: Day , BinanceKlineInterval :: Day3 ) ]
1074+ #[ case( 1 , BarAggregation :: Week , BinanceKlineInterval :: Week1 ) ]
1075+ #[ case( 1 , BarAggregation :: Month , BinanceKlineInterval :: Month1 ) ]
1076+ fn test_bar_spec_to_binance_interval (
1077+ #[ case] step : usize ,
1078+ #[ case] aggregation : BarAggregation ,
1079+ #[ case] expected : BinanceKlineInterval ,
1080+ ) {
1081+ let bar_spec = make_bar_spec ( step, aggregation) ;
1082+ let result = bar_spec_to_binance_interval ( bar_spec) . unwrap ( ) ;
1083+ assert_eq ! ( result, expected) ;
1084+ }
1085+
1086+ #[ rstest]
1087+ fn test_unsupported_second_interval ( ) {
1088+ let bar_spec = make_bar_spec ( 1 , BarAggregation :: Second ) ;
1089+ let result = bar_spec_to_binance_interval ( bar_spec) ;
1090+ assert ! ( result. is_err( ) ) ;
1091+ assert ! (
1092+ result
1093+ . unwrap_err( )
1094+ . to_string( )
1095+ . contains( "does not support second-level" )
1096+ ) ;
1097+ }
1098+
1099+ #[ rstest]
1100+ fn test_unsupported_minute_interval ( ) {
1101+ let bar_spec = make_bar_spec ( 7 , BarAggregation :: Minute ) ;
1102+ let result = bar_spec_to_binance_interval ( bar_spec) ;
1103+ assert ! ( result. is_err( ) ) ;
1104+ assert ! (
1105+ result
1106+ . unwrap_err( )
1107+ . to_string( )
1108+ . contains( "Unsupported minute interval" )
1109+ ) ;
1110+ }
1111+
1112+ #[ rstest]
1113+ fn test_unsupported_aggregation ( ) {
1114+ let bar_spec = make_bar_spec ( 100 , BarAggregation :: Tick ) ;
1115+ let result = bar_spec_to_binance_interval ( bar_spec) ;
1116+ assert ! ( result. is_err( ) ) ;
1117+ assert ! (
1118+ result
1119+ . unwrap_err( )
1120+ . to_string( )
1121+ . contains( "Unsupported bar aggregation" )
1122+ ) ;
1123+ }
1124+ }
9891125}
0 commit comments