use super::output::Output;
use crate::dsl::ast::Portfolio as PortfolioStatement;
use crate::dsl::ast::{Define, Details, Program, Record, Statement};
use crate::evaluator::output::RecordOutput;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Asset {
pub symbol: String,
pub alias: Option<String>,
pub target_return: Option<f64>,
}
impl Asset {
pub fn new(symbol: String, alias: Option<String>, target_return: Option<f64>) -> Self {
Self {
symbol,
alias,
target_return,
}
}
pub fn get_symbol(&self) -> &String {
&self.symbol
}
pub fn get_alias(&self) -> &Option<String> {
&self.alias
}
pub fn get_target_return(&self) -> &Option<f64> {
&self.target_return
}
}
#[derive(Clone, Debug)]
pub struct Portfolio {
pub name: String,
pub assets: Vec<Asset>,
pub target_return: f64,
}
pub struct Plan {
pub name: String,
}
pub struct AnalysisReport {
pub assets: Vec<Asset>,
pub portfolios: Vec<Portfolio>,
pub daily_snapshot: HashMap<String, Vec<DailySnapshot>>,
}
impl AnalysisReport {
pub fn new() -> Self {
Self {
assets: Vec::new(),
portfolios: Vec::new(),
daily_snapshot: HashMap::new(),
}
}
}
#[derive(Debug)]
pub struct EngineError {}
impl EngineError {
pub fn new() -> Self {
Self {}
}
}
#[derive(Debug)]
pub struct Snapshot {
pub symbol: String,
pub date: String,
pub statement: Record,
pub total_purchase: f64,
pub total_sale: f64,
pub value: f64,
pub profit: f64,
}
impl Snapshot {
fn from_output(output: &RecordOutput) -> Self {
Self {
symbol: output.program.details.get_symbol().to_string(),
date: output.program.date.clone(),
statement: output.program.clone(),
total_purchase: output.total_purchase,
total_sale: output.total_sale,
value: output.value,
profit: output.profit,
}
}
}
#[derive(Debug)]
pub struct DailySnapshot {
pub symbol: String,
pub date: String,
pub snapshots: Vec<Snapshot>,
}
impl DailySnapshot {
fn new(symbol: String, date: String, snapshots: Vec<Snapshot>) -> Self {
Self {
symbol,
date,
snapshots,
}
}
}
struct EngineState {
portfolios: Vec<Portfolio>,
plans: Vec<Plan>,
assets: HashMap<String, Asset>,
record_outputs: Vec<RecordOutput>,
snapshots: HashMap<String, RecordOutput>,
}
#[derive(Debug, Clone)]
struct AssetMetric {
total_purchase: f64,
total_sale: f64,
value: f64,
}
impl AssetMetric {
fn new_zero() -> Self {
Self {
total_purchase: 0.0,
total_sale: 0.0,
value: 0.0,
}
}
fn from_record(output: &RecordOutput) -> Self {
Self {
total_purchase: output.total_purchase,
total_sale: output.total_sale,
value: output.value,
}
}
pub fn get_profit(&self) -> f64 {
self.value - self.total_purchase + self.total_sale
}
}
impl EngineState {
fn new() -> Self {
Self {
portfolios: Vec::new(),
plans: Vec::new(),
assets: HashMap::new(),
record_outputs: Vec::new(),
snapshots: HashMap::new(),
}
}
fn upsert_asset(&mut self, args: UpsertAssetArgs) {
use std::collections::hash_map::Entry;
match self.assets.entry(args.symbol.to_string()) {
Entry::Occupied(mut entry) => {
let asset = entry.get_mut();
if let Some(name) = args.name {
asset.alias = Some(name);
}
if let Some(target_return) = args.target_return {
asset.target_return = Some(target_return);
};
let asset = asset.clone();
self.update_portfolio_assets(asset);
}
Entry::Vacant(entry) => {
let asset = Asset::new(args.symbol.to_string(), args.name, args.target_return);
entry.insert(asset);
}
}
}
fn calc_asset(&mut self, record: &Record) -> Result<Output, EngineError> {
let symbol = record.details.get_symbol().to_string();
let last = match self.snapshots.get(&symbol) {
None => AssetMetric::new_zero(),
Some(shot) => AssetMetric::from_record(shot),
};
let details = &record.details;
let mut new_snapshot = last.clone();
match details {
Details::Trade(trade) => {
let value = trade.signed_amount.value;
if trade.buy() {
new_snapshot.total_purchase = last.total_purchase + value;
} else {
new_snapshot.total_sale = last.total_sale + value;
}
new_snapshot.value = last.value + trade.signed_amount.to_f64();
}
Details::Mark(mark) => {
new_snapshot.value = mark.value;
new_snapshot.total_purchase = last.total_purchase;
new_snapshot.total_sale = last.total_sale;
}
}
let output = RecordOutput::from_record_with_metric(&record, new_snapshot);
self.record_outputs.push(output.clone());
self.snapshots.insert(symbol, output.clone());
Ok(Output::Record(output))
}
fn update_portfolio(&mut self, statement: PortfolioStatement) -> Result<(), EngineError> {
let portfolio = Portfolio {
name: statement.name.clone(),
assets: statement
.assets
.iter()
.map(|x| {
let symbol = x.to_string();
let asset = self
.assets
.get(&symbol)
.cloned()
.unwrap_or_else(|| Asset::new(symbol, None, None));
asset
})
.collect(),
target_return: statement.target_return.unwrap_or(0.0),
};
self.portfolios.push(portfolio);
Ok(())
}
fn update_portfolio_assets(&mut self, asset: Asset) {
for portfolio in self.portfolios.iter_mut() {
for (_i, a) in portfolio.assets.iter_mut().enumerate() {
if a.get_symbol() == asset.get_symbol() {
*a = asset.clone();
}
}
}
}
}
impl RecordOutput {
fn from_record_with_metric(record: &Record, metric: AssetMetric) -> Self {
Self {
program: record.clone(),
total_purchase: metric.total_purchase,
total_sale: metric.total_sale,
value: metric.value,
profit: metric.get_profit(),
}
}
}
struct UpsertAssetArgs {
symbol: String,
name: Option<String>,
target_return: Option<f64>,
}
struct DefineStatementResult {}
pub struct Engine {
state: EngineState,
}
impl Engine {
pub fn new() -> Self {
Self {
state: EngineState {
portfolios: Vec::new(),
plans: Vec::new(),
assets: HashMap::new(),
record_outputs: Vec::new(),
snapshots: HashMap::new(),
},
}
}
pub fn evaluate(&mut self, program: Program) -> Result<AnalysisReport, EngineError> {
let mut record_statements = Vec::new();
for statement in program.statements.iter() {
match statement {
Statement::Record(rec) => record_statements.push(rec),
Statement::Plan(_) => {}
Statement::Define(define) => self.evaluate_define(&define)?,
Statement::Portfolio(statement) => self.evaluate_portfolio(statement)?,
}
}
record_statements.sort_by(|a, b| a.date.cmp(&b.date));
record_statements
.iter()
.try_for_each(|rec| self.evaluate_record(rec).map(|_| ()))?;
let mut result = AnalysisReport::new();
result.assets = self.state.assets.values().cloned().collect();
for output in self.state.record_outputs.iter() {
let symbol = output.program.details.get_symbol().to_string();
let date = output.program.date.clone();
let by_symbol = result
.daily_snapshot
.entry(symbol.clone())
.or_insert(Vec::new());
match by_symbol.iter_mut().find(|x| x.date == date) {
Some(by_date) => {
by_date.snapshots.push(Snapshot::from_output(output));
}
None => {
by_symbol.push(DailySnapshot::new(
symbol.clone(),
date.clone(),
vec![Snapshot::from_output(output)],
));
}
}
}
result.portfolios = self.state.portfolios.clone();
Ok(result)
}
fn evaluate_record(&mut self, record: &Record) -> Result<Output, EngineError> {
let details = &record.details;
let symbol = details.get_symbol().to_string();
let args = UpsertAssetArgs {
symbol,
name: None,
target_return: None,
};
self.state.upsert_asset(args);
let output = self.state.calc_asset(record)?;
Ok(output)
}
fn evaluate_plan(&self, _plan: Plan) -> Result<(), EngineError> {
Ok(())
}
fn evaluate_define(&mut self, define: &Define) -> Result<(), EngineError> {
self.state.upsert_asset(UpsertAssetArgs {
symbol: define.symbol.to_string(),
name: define.alias.clone(),
target_return: define.target_return.clone(),
});
Ok(())
}
fn evaluate_portfolio(&mut self, statement: &PortfolioStatement) -> Result<(), EngineError> {
self.state.update_portfolio(statement.clone())?;
Ok(())
}
}