@@ -55,6 +55,10 @@ struct Args {
5555 #[ arg( long, default_value_t = 10.0 ) ]
5656 max_div_yield : f64 ,
5757
58+ /// Target Dividend Yield[%]
59+ #[ arg( long, default_value_t = 4.0 ) ]
60+ target_yield : f64 ,
61+
5862 /// Minimum accepted Dividend Growth rate[%]
5963 #[ arg( long, default_value_t = 10.0 ) ]
6064 min_div_growth_rate : f64 ,
@@ -68,6 +72,29 @@ struct Args {
6872 sp500_divy : f64 ,
6973}
7074
75+ /// Calculate target share price when its div yield will reach our expected yield
76+ /// divy [%]
77+ /// target_divy [%]
78+ fn calculate_target_price_and_distance (
79+ share_price : f64 ,
80+ maybe_divy : Option < f64 > ,
81+ target_divy : f64 ,
82+ ) -> ( Option < f64 > , Option < f64 > ) {
83+ // if there is no dividend yield given (no data avaialble)
84+ // then we just return Nones
85+ match maybe_divy {
86+ Some ( divy) => {
87+ let ratio_divy = target_divy / divy;
88+ let target_share_price = share_price / ratio_divy;
89+ // if target is 50.0 and share price is 100.0 then 50.0/100.0 -100.0% is -50%
90+ //if target is 150.0 and share price is 100.0 then 150.0/100.0 - 100.0% is 50%
91+ let distance = ( target_share_price / share_price - 1.0 ) * 100.0 ;
92+ ( Some ( target_share_price) , Some ( distance) )
93+ }
94+ None => ( None , None ) ,
95+ }
96+ }
97+
7198fn analyze_div_yield (
7299 df : & DataFrame ,
73100 sp500_divy : f64 ,
@@ -189,7 +216,7 @@ fn print_summary(df: &DataFrame, company: Option<&str>) -> Result<(), &'static s
189216fn configure_dataframes_format ( ) {
190217 // Make sure to show all columns
191218 if std:: env:: var ( "POLARS_FMT_MAX_COLS" ) . is_err ( ) {
192- std:: env:: set_var ( "POLARS_FMT_MAX_COLS" , "13 " )
219+ std:: env:: set_var ( "POLARS_FMT_MAX_COLS" , "15 " )
193220 }
194221 // Make sure to show all raws
195222 if std:: env:: var ( "POLARS_FMT_MAX_ROWS" ) . is_err ( ) {
@@ -201,10 +228,15 @@ fn configure_dataframes_format() {
201228 }
202229}
203230
204- fn get_companies_data ( companies : & [ String ] , database : Option < String > ) -> Result < ( ) , & ' static str > {
231+ fn get_companies_data (
232+ companies : & [ String ] ,
233+ database : Option < String > ,
234+ target_yield : f64 ,
235+ ) -> Result < ( ) , & ' static str > {
205236 let mut symbols: Vec < & str > = vec ! [ ] ;
206237 let mut share_prices: Vec < f64 > = vec ! [ ] ;
207238 let mut curr_divs: Vec < Option < f64 > > = vec ! [ ] ;
239+ let mut target_prices: Vec < Option < f64 > > = vec ! [ ] ;
208240 let mut divys: Vec < Option < f64 > > = vec ! [ ] ;
209241 let mut freqs: Vec < Option < i64 > > = vec ! [ ] ;
210242 let mut dgrs: Vec < Option < f64 > > = vec ! [ ] ;
@@ -213,22 +245,28 @@ fn get_companies_data(companies: &[String], database: Option<String>) -> Result<
213245 let mut dgr1ys: Vec < Option < f64 > > = vec ! [ ] ;
214246 let mut dgr1y_ttms: Vec < Option < f64 > > = vec ! [ ] ;
215247 let mut years_growth: Vec < Option < i64 > > = vec ! [ ] ;
248+ let mut distances: Vec < Option < f64 > > = vec ! [ ] ;
216249 let mut payout_ratios: Vec < Option < f64 > > = vec ! [ ] ;
217250 let mut sectors: Vec < Option < String > > = vec ! [ ] ;
218251
219252 let s1 = Series :: new ( "Symbol" , & symbols) ;
220253 let s2 = Series :: new ( "Share Price" , share_prices. clone ( ) ) ;
221254 let s3 = Series :: new ( "Recent Div" , curr_divs. clone ( ) ) ;
222- let s4 = Series :: new ( "Annual Frequency" , freqs. clone ( ) ) ;
223- let s5 = Series :: new ( "Div Yield[%]" , divys. clone ( ) ) ;
224- let s6 = Series :: new ( "DGR 1Y TTM[%]" , dgr1y_ttms. clone ( ) ) ;
225- let s7 = Series :: new ( "DGR 1Y[%]" , dgr1ys. clone ( ) ) ;
226- let s8 = Series :: new ( "DGR 3Y[%]" , dgrs. clone ( ) ) ;
227- let s9 = Series :: new ( "DGR 5Y[%]" , dgrs. clone ( ) ) ;
228- let s10 = Series :: new ( "DGR 10Y[%]" , dgrs. clone ( ) ) ;
229- let s11 = Series :: new ( "Years of consecutive Div growth" , years_growth. clone ( ) ) ;
230- let s12 = Series :: new ( "Payout ratio[%]" , payout_ratios. clone ( ) ) ;
231- let s13 = Series :: new ( "Industry Desc" , sectors. clone ( ) ) ;
255+ let s4 = Series :: new (
256+ & format ! ( "Target Price\n (Div yield {target_yield}%)" ) ,
257+ target_prices. clone ( ) ,
258+ ) ;
259+ let s5 = Series :: new ( "Annual Frequency" , freqs. clone ( ) ) ;
260+ let s6 = Series :: new ( "Div Yield[%]" , divys. clone ( ) ) ;
261+ let s7 = Series :: new ( "DGR 1Y TTM[%]" , dgr1y_ttms. clone ( ) ) ;
262+ let s8 = Series :: new ( "DGR 1Y[%]" , dgr1ys. clone ( ) ) ;
263+ let s9 = Series :: new ( "DGR 3Y[%]" , dgrs. clone ( ) ) ;
264+ let s10 = Series :: new ( "DGR 5Y[%]" , dgrs. clone ( ) ) ;
265+ let s11 = Series :: new ( "DGR 10Y[%]" , dgrs. clone ( ) ) ;
266+ let s12 = Series :: new ( "Years of\n consecutive Div growth" , years_growth. clone ( ) ) ;
267+ let s13 = Series :: new ( "Distance\n to Target[%]" , distances. clone ( ) ) ;
268+ let s14 = Series :: new ( "Payout ratio[%]" , payout_ratios. clone ( ) ) ;
269+ let s15 = Series :: new ( "Industry Desc" , sectors. clone ( ) ) ;
232270 let df: DataFrame = DataFrame :: new ( vec ! [
233271 s1. clone( ) ,
234272 s2. clone( ) ,
@@ -243,6 +281,8 @@ fn get_companies_data(companies: &[String], database: Option<String>) -> Result<
243281 s11. clone( ) ,
244282 s12. clone( ) ,
245283 s13. clone( ) ,
284+ s14. clone( ) ,
285+ s15. clone( ) ,
246286 ] )
247287 . unwrap ( ) ;
248288
@@ -291,7 +331,11 @@ fn get_companies_data(companies: &[String], database: Option<String>) -> Result<
291331 . expect ( "Error: unable to get Data from yahoo finance for forecasting" )
292332 } ;
293333
334+ let ( target_price, distance) =
335+ calculate_target_price_and_distance ( share_price, divy, target_yield) ;
336+
294337 share_prices. push ( share_price) ;
338+ target_prices. push ( target_price) ;
295339 curr_divs. push ( curr_div) ;
296340 divys. push ( divy) ;
297341 freqs. push ( frequency) ;
@@ -301,24 +345,30 @@ fn get_companies_data(companies: &[String], database: Option<String>) -> Result<
301345 dgr1y_ttms. push ( dgr1y_ttm) ;
302346 dgrs. push ( dgr) ;
303347 years_growth. push ( years_of_growth) ;
348+ distances. push ( distance) ;
304349 payout_ratios. push ( payout_ratio) ;
305- symbols. push ( & symbol) ;
350+ symbols. push ( symbol) ;
306351 sectors. push ( sector_desc) ;
307352
308353 if let Some ( database) = database. clone ( ) {
309354 let s1 = Series :: new ( "Symbol" , & symbols) ;
310355 let s2 = Series :: new ( "Share Price" , share_prices. clone ( ) ) ;
311356 let s3 = Series :: new ( "Recent Div" , curr_divs. clone ( ) ) ;
312- let s4 = Series :: new ( "Annual Frequency" , freqs. clone ( ) ) ;
313- let s5 = Series :: new ( "Div Yield[%]" , divys. clone ( ) ) ;
314- let s6 = Series :: new ( "DGR 1Y TTM[%]" , dgr1y_ttms. clone ( ) ) ;
315- let s7 = Series :: new ( "DGR 1Y[%]" , dgr1ys. clone ( ) ) ;
316- let s8 = Series :: new ( "DGR 3Y[%]" , dgr3ys. clone ( ) ) ;
317- let s9 = Series :: new ( "DGR 5Y[%]" , dgr5ys. clone ( ) ) ;
318- let s10 = Series :: new ( "DGR 10Y[%]" , dgrs. clone ( ) ) ;
319- let s11 = Series :: new ( "Years of consecutive Div growth" , years_growth. clone ( ) ) ;
320- let s12 = Series :: new ( "Payout ratio[%]" , payout_ratios. clone ( ) ) ;
321- let s13 = Series :: new ( "Industry Desc" , sectors. clone ( ) ) ;
357+ let s4 = Series :: new (
358+ & format ! ( "Target Price\n (Div yield {target_yield}%)" ) ,
359+ target_prices. clone ( ) ,
360+ ) ;
361+ let s5 = Series :: new ( "Annual Frequency" , freqs. clone ( ) ) ;
362+ let s6 = Series :: new ( "Div Yield[%]" , divys. clone ( ) ) ;
363+ let s7 = Series :: new ( "DGR 1Y TTM[%]" , dgr1y_ttms. clone ( ) ) ;
364+ let s8 = Series :: new ( "DGR 1Y[%]" , dgr1ys. clone ( ) ) ;
365+ let s9 = Series :: new ( "DGR 3Y[%]" , dgr3ys. clone ( ) ) ;
366+ let s10 = Series :: new ( "DGR 5Y[%]" , dgr5ys. clone ( ) ) ;
367+ let s11 = Series :: new ( "DGR 10Y[%]" , dgrs. clone ( ) ) ;
368+ let s12 = Series :: new ( "Years of\n consecutive Div growth" , years_growth. clone ( ) ) ;
369+ let s13 = Series :: new ( "Distance\n to Target[%]" , distances. clone ( ) ) ;
370+ let s14 = Series :: new ( "Payout ratio[%]" , payout_ratios. clone ( ) ) ;
371+ let s15 = Series :: new ( "Industry Desc" , sectors. clone ( ) ) ;
322372
323373 let df: DataFrame = DataFrame :: new ( vec ! [
324374 s1. clone( ) ,
@@ -334,6 +384,8 @@ fn get_companies_data(companies: &[String], database: Option<String>) -> Result<
334384 s11. clone( ) ,
335385 s12. clone( ) ,
336386 s13. clone( ) ,
387+ s14. clone( ) ,
388+ s15. clone( ) ,
337389 ] )
338390 . unwrap ( ) ;
339391
@@ -345,7 +397,15 @@ fn get_companies_data(companies: &[String], database: Option<String>) -> Result<
345397 // .. the same results we sort according to dividend yield..
346398 // .. and then lastly according the DGR 3Y
347399 let mut df = df
348- . sort ( [ "Years of consecutive Div growth" , "Div Yield[%]" , "DGR 3Y[%]" ] , vec ! [ true , true , true ] , false )
400+ . sort (
401+ [
402+ "Years of\n consecutive Div growth" ,
403+ "Div Yield[%]" ,
404+ "DGR 3Y[%]" ,
405+ ] ,
406+ vec ! [ true , true , true ] ,
407+ false ,
408+ )
349409 . unwrap ( ) ;
350410
351411 let mut file =
@@ -368,16 +428,21 @@ fn get_companies_data(companies: &[String], database: Option<String>) -> Result<
368428 let s1 = Series :: new ( "Symbol" , & symbols) ;
369429 let s2 = Series :: new ( "Share Price" , share_prices. clone ( ) ) ;
370430 let s3 = Series :: new ( "Recent Div" , curr_divs. clone ( ) ) ;
371- let s4 = Series :: new ( "Annual Frequency" , freqs. clone ( ) ) ;
372- let s5 = Series :: new ( "Div Yield[%]" , divys. clone ( ) ) ;
373- let s6 = Series :: new ( "DGR 1Y TTM[%]" , dgr1y_ttms. clone ( ) ) ;
374- let s7 = Series :: new ( "DGR 1Y[%]" , dgr1ys. clone ( ) ) ;
375- let s8 = Series :: new ( "DGR 3Y[%]" , dgr3ys. clone ( ) ) ;
376- let s9 = Series :: new ( "DGR 5Y[%]" , dgr5ys. clone ( ) ) ;
377- let s10 = Series :: new ( "DGR 10Y[%]" , dgrs. clone ( ) ) ;
378- let s11 = Series :: new ( "Years of consecutive Div growth" , years_growth. clone ( ) ) ;
379- let s12 = Series :: new ( "Payout ratio[%]" , payout_ratios. clone ( ) ) ;
380- let s13 = Series :: new ( "Industry Desc" , sectors. clone ( ) ) ;
431+ let s4 = Series :: new (
432+ & format ! ( "Target Price\n (Div yield {target_yield}%)" ) ,
433+ target_prices. clone ( ) ,
434+ ) ;
435+ let s5 = Series :: new ( "Annual Frequency" , freqs. clone ( ) ) ;
436+ let s6 = Series :: new ( "Div Yield[%]" , divys. clone ( ) ) ;
437+ let s7 = Series :: new ( "DGR 1Y TTM[%]" , dgr1y_ttms. clone ( ) ) ;
438+ let s8 = Series :: new ( "DGR 1Y[%]" , dgr1ys. clone ( ) ) ;
439+ let s9 = Series :: new ( "DGR 3Y[%]" , dgr3ys. clone ( ) ) ;
440+ let s10 = Series :: new ( "DGR 5Y[%]" , dgr5ys. clone ( ) ) ;
441+ let s11 = Series :: new ( "DGR 10Y[%]" , dgrs. clone ( ) ) ;
442+ let s12 = Series :: new ( "Years of\n consecutive Div growth" , years_growth. clone ( ) ) ;
443+ let s13 = Series :: new ( "Distance\n to Target[%]" , distances. clone ( ) ) ;
444+ let s14 = Series :: new ( "Payout ratio[%]" , payout_ratios. clone ( ) ) ;
445+ let s15 = Series :: new ( "Industry Desc" , sectors. clone ( ) ) ;
381446
382447 let df: DataFrame = DataFrame :: new ( vec ! [
383448 s1. clone( ) ,
@@ -393,15 +458,26 @@ fn get_companies_data(companies: &[String], database: Option<String>) -> Result<
393458 s11. clone( ) ,
394459 s12. clone( ) ,
395460 s13. clone( ) ,
461+ s14. clone( ) ,
462+ s15. clone( ) ,
396463 ] )
397464 . unwrap ( ) ;
398465
399- let df = start_df
400- . vstack ( & df)
401- . map_err ( |_| "Unable to combine data frames" ) ?;
466+ let df = start_df. vstack ( & df) . map_err ( |e| {
467+ println ! ( "Error during combining data frames: {e}" ) ;
468+ "Unable to combine data frames"
469+ } ) ?;
402470
403471 let df = df
404- . sort ( [ "Years of consecutive Div growth" , "Div Yield[%]" , "DGR 3Y[%]" ] , vec ! [ true , true , true ] , false )
472+ . sort (
473+ [
474+ "Years of\n consecutive Div growth" ,
475+ "Div Yield[%]" ,
476+ "DGR 3Y[%]" ,
477+ ] ,
478+ vec ! [ true , true , true ] ,
479+ false ,
480+ )
405481 . unwrap ( ) ;
406482
407483 println ! ( "{df}" ) ;
@@ -488,7 +564,7 @@ fn main() -> Result<(), &'static str> {
488564 companies. into_iter ( ) . for_each ( |( s, _) | {
489565 symbols. push ( s) ;
490566 } ) ;
491- get_companies_data ( & symbols, args. database ) ?;
567+ get_companies_data ( & symbols, args. database , args . target_yield ) ?;
492568 }
493569 }
494570 }
@@ -521,7 +597,7 @@ fn main() -> Result<(), &'static str> {
521597 } else {
522598 companies
523599 } ;
524- get_companies_data ( & companies, args. database ) ?;
600+ get_companies_data ( & companies, args. database , args . target_yield ) ?;
525601 }
526602 }
527603 }
@@ -623,6 +699,41 @@ mod tests {
623699 Ok ( ( ) )
624700 }
625701
702+ #[ test]
703+ fn test_target_price_and_distance ( ) -> Result < ( ) , String > {
704+ let share_price = 100.0 ;
705+ let divy = 2.0 ;
706+
707+ let target_divy = 4.0 ;
708+ let expected_target_price = Some ( 50.0 ) ;
709+ let expected_distance = Some ( -50.0 ) ;
710+ let ( target_price, distance) =
711+ calculate_target_price_and_distance ( share_price, Some ( divy) , target_divy) ;
712+ // given 2.0% of 100.0 , 4% will be at 50.0USD e.g. x => 100/2.0 = 50.0
713+ assert_eq ! ( target_price, expected_target_price) ;
714+ // distance: 50.0/100.0 * 100% - 100.0% = 50.0%
715+ assert_eq ! ( distance, expected_distance) ;
716+
717+ let target_divy = 1.0 ;
718+ let expected_target_price = Some ( 200.0 ) ;
719+ let expected_distance = Some ( 100.0 ) ;
720+ let ( target_price, distance) =
721+ calculate_target_price_and_distance ( share_price, Some ( divy) , target_divy) ;
722+ // given 2.0% of 100.0 , 1% will be at 200.0USD e.g. x => 100*2.0 = 200.0
723+ assert_eq ! ( target_price, expected_target_price) ;
724+ // distance: 200.0/100.0 * 100% - 100.0% = 100.0%
725+ assert_eq ! ( distance, expected_distance) ;
726+
727+ let expected_target_price = None ;
728+ let expected_distance = None ;
729+ let ( target_price, distance) =
730+ calculate_target_price_and_distance ( share_price, None , target_divy) ;
731+ // No divy avaialbe then no target price and distance
732+ assert_eq ! ( target_price, expected_target_price) ;
733+ assert_eq ! ( distance, expected_distance) ;
734+ Ok ( ( ) )
735+ }
736+
626737 #[ test]
627738 fn test_analyze_div_growth ( ) -> Result < ( ) , String > {
628739 let min_growth_rate = 7.0 ;
0 commit comments