1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use std::rc::Rc;

use crate::broker_statement::BrokerStatement;
use crate::commissions::CommissionCalc;
use crate::config::{Config, PortfolioConfig};
use crate::core::{GenericResult, EmptyResult};
use crate::currency::converter::CurrencyConverter;
use crate::db;
use crate::localities;
use crate::quotes::Quotes;
use crate::types::Decimal;

use self::performance::PortfolioPerformanceAnalyser;

pub mod deposit_emulator;
mod performance;
mod sell_simulation;

pub fn analyse(config: &Config, portfolio_name: &str, show_closed_positions: bool) -> EmptyResult {
    let mut portfolios = Vec::new();

    if portfolio_name == "all" {
        if config.portfolios.is_empty() {
            return Err!("There is no any portfolio defined in the configuration file")
        }

        for portfolio in &config.portfolios {
            let statement = load_portfolio(config, portfolio, false)?;
            portfolios.push((portfolio, statement));
        }
    } else {
        let portfolio = config.get_portfolio(portfolio_name)?;
        let statement = load_portfolio(config, portfolio, false)?;
        portfolios.push((portfolio, statement));
    }

    let country = localities::russia();
    let (converter, quotes) = load_tools(config)?;

    for (_, statement) in &mut portfolios {
        statement.batch_quotes(&quotes);
    }

    for (portfolio, statement) in &mut portfolios {
        statement.check_date();

        let mut commission_calc = CommissionCalc::new(statement.broker.commission_spec.clone());

        for (symbol, quantity) in statement.open_positions.clone() {
            statement.emulate_sell(&symbol, quantity, quotes.get(&symbol)?, &mut commission_calc)?;
        }
        statement.process_trades()?;
        statement.emulate_commissions(commission_calc);

        statement.merge_symbols(&portfolio.merge_performance).map_err(|e| format!(
            "Invalid performance merging configuration: {}", e))?;
    }

    for &currency in &["USD", "RUB"] {
        let mut analyser = PortfolioPerformanceAnalyser::new(
            country, currency, &converter, show_closed_positions);

        for (portfolio, statement) in &mut portfolios {
            analyser.add(&portfolio, &statement)?;
        }

        analyser.analyse()?;
    }

    Ok(())
}

pub fn simulate_sell(config: &Config, portfolio_name: &str, positions: &[(String, Option<Decimal>)]) -> EmptyResult {
    let portfolio = config.get_portfolio(portfolio_name)?;
    let statement = load_portfolio(config, portfolio, true)?;
    let (converter, quotes) = load_tools(config)?;
    sell_simulation::simulate_sell(portfolio, statement, &converter, &quotes, positions)
}

fn load_portfolio(config: &Config, portfolio: &PortfolioConfig, strict_mode: bool) -> GenericResult<BrokerStatement> {
    let broker = portfolio.broker.get_info(config, portfolio.plan.as_ref())?;
    BrokerStatement::read(
        broker, &portfolio.statements, &portfolio.symbol_remapping, &portfolio.instrument_names,
        portfolio.get_tax_remapping()?, strict_mode)
}

fn load_tools(config: &Config) -> GenericResult<(CurrencyConverter, Rc<Quotes>)> {
    let database = db::connect(&config.db_path)?;
    let quotes = Rc::new(Quotes::new(&config, database.clone())?);
    let converter = CurrencyConverter::new(database, Some(quotes.clone()), false);
    Ok((converter, quotes))
}