Skip to content

Commit 5151364

Browse files
committed
- Added target price functionality
1 parent 887c274 commit 5151364

2 files changed

Lines changed: 152 additions & 40 deletions

File tree

src/divanalysis/main.rs

Lines changed: 151 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
7198
fn 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
189216
fn 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\nconsecutive Div growth", years_growth.clone());
267+
let s13 = Series::new("Distance\nto 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\nconsecutive Div growth", years_growth.clone());
369+
let s13 = Series::new("Distance\nto 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\nconsecutive 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\nconsecutive Div growth", years_growth.clone());
443+
let s13 = Series::new("Distance\nto 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\nconsecutive 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;

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ pub fn get_yahoo_data(
605605
),
606606
&'static str,
607607
> {
608+
log::info!("Yahoo: Getting Ticker: {}", company);
608609
// create provider
609610
let mut provider =
610611
yahoo::YahooConnector::new().map_err(|_| "Could not create Yahoo provider")?;

0 commit comments

Comments
 (0)