use std::collections::HashMap;
use std::hash::Hash;
use std::path::PathBuf;
use chrono::prelude::*;
use avin_utils::{AvinError, CFG, Cmd};
use crate::{
BarEvent, Chart, Footprint, Iid, Manager, MarketData, Tic, TicEvent,
TimeFrame,
};
pub struct Share {
iid: Iid,
tics: Vec<Tic>,
charts: HashMap<TimeFrame, Chart>,
footprints: HashMap<TimeFrame, Footprint>,
}
impl Share {
pub fn new(s: &str) -> Result<Share, AvinError> {
let iid = Manager::find_iid(s)?;
let share = Share::from_iid(iid);
Ok(share)
}
pub fn from_iid(iid: Iid) -> Share {
assert!(iid.category() == "SHARE");
Self {
iid,
tics: Vec::new(),
charts: HashMap::new(),
footprints: HashMap::new(),
}
}
pub fn from_info(info: HashMap<String, String>) -> Share {
let iid = Iid::new(info);
Share::from_iid(iid)
}
pub fn all() -> Vec<Share> {
let mut shares: Vec<Share> = Vec::new();
let mut dir_path = CFG.dir.data();
dir_path.push("MOEX");
dir_path.push("SHARE");
let dirs = Cmd::get_dirs(&dir_path).unwrap();
if dirs.is_empty() {
log::warn!("Shares not found! Dir empty: {}", dir_path.display());
return shares;
}
for dir in dirs.iter() {
let ticker = Cmd::name(dir).unwrap();
let s = format!("MOEX_SHARE_{ticker}");
let share = Share::new(&s).unwrap();
shares.push(share);
}
shares
}
pub fn iid(&self) -> &Iid {
&self.iid
}
pub fn exchange(&self) -> &String {
self.iid.exchange()
}
pub fn category(&self) -> &String {
self.iid.category()
}
pub fn ticker(&self) -> &String {
self.iid.ticker()
}
pub fn figi(&self) -> &String {
self.iid.figi()
}
pub fn name(&self) -> &String {
self.iid.name()
}
pub fn info(&self) -> &HashMap<String, String> {
self.iid.info()
}
pub fn path(&self) -> PathBuf {
self.iid.path()
}
pub fn chart(&self, tf: TimeFrame) -> Option<&Chart> {
self.charts.get(&tf)
}
pub fn chart_mut(&mut self, tf: TimeFrame) -> Option<&mut Chart> {
self.charts.get_mut(&tf)
}
pub fn load_chart(&mut self, tf: TimeFrame) -> Result<&Chart, AvinError> {
let end = Utc::now();
let begin = end - tf.timedelta() * CFG.core.default_bars_count as i32;
self.load_chart_period(tf, begin, end)
}
pub fn load_chart_mut(
&mut self,
tf: TimeFrame,
) -> Result<&mut Chart, AvinError> {
let end = Utc::now();
let begin = end - tf.timedelta() * CFG.core.default_bars_count as i32;
self.load_chart_period(tf, begin, end).unwrap();
Ok(self.charts.get_mut(&tf).unwrap())
}
pub fn load_chart_period(
&mut self,
tf: TimeFrame,
begin: DateTime<Utc>,
end: DateTime<Utc>,
) -> Result<&Chart, AvinError> {
let chart = Chart::load(&self.iid, tf, begin, end)?;
self.charts.insert(tf, chart);
Ok(self.charts[&tf].as_ref())
}
pub fn load_chart_empty(&mut self, tf: TimeFrame) -> &Chart {
let chart = Chart::empty(&self.iid, tf);
self.charts.insert(tf, chart);
self.charts[&tf].as_ref()
}
pub fn tics(&self) -> Option<&Vec<Tic>> {
if !self.tics.is_empty() {
return Some(&self.tics);
}
None
}
pub fn footprint(&self, tf: TimeFrame) -> Option<&Footprint> {
self.footprints.get(&tf)
}
pub fn footprint_mut(
&mut self,
tf: &TimeFrame,
) -> Option<&mut Footprint> {
self.footprints.get_mut(tf)
}
pub fn load_tics(&mut self) -> Result<(), AvinError> {
let begin = Utc::now().with_time(NaiveTime::MIN).unwrap();
let end = Utc::now();
match Manager::load(&self.iid, MarketData::TIC, begin, end) {
Ok(df) => {
self.tics = Tic::from_df(&df).unwrap();
Ok(())
}
Err(AvinError::NotFound(_)) => {
self.tics = Vec::new();
Ok(())
}
Err(err) => {
log::error!("{err}");
panic!();
}
}
}
pub fn build_footprint(
&mut self,
tf: TimeFrame,
) -> Result<(), AvinError> {
let footprint = Footprint::from_tics(self.iid(), tf, &self.tics);
self.footprints.insert(tf, footprint);
Ok(())
}
pub fn delta_month(&self) -> Option<f64> {
let chart = self.charts.get(&TimeFrame::Month)?;
let bar = chart.now()?;
let delta = bar.body().delta_p();
Some(delta)
}
pub fn delta_week(&self) -> Option<f64> {
let chart = self.charts.get(&TimeFrame::Week)?;
let bar = chart.now()?;
let delta = bar.body().delta_p();
Some(delta)
}
pub fn delta_day(&self) -> Option<f64> {
let chart = self.charts.get(&TimeFrame::Day)?;
let bar = chart.now()?;
let delta = bar.body().delta_p();
Some(delta)
}
pub fn delta_1h(&self) -> Option<f64> {
let chart = self.charts.get(&TimeFrame::H1)?;
let bar = chart.now()?;
let delta = bar.body().delta_p();
Some(delta)
}
pub fn delta_10m(&self) -> Option<f64> {
let chart = self.charts.get(&TimeFrame::M10)?;
let bar = chart.now()?;
let delta = bar.body().delta_p();
Some(delta)
}
pub fn delta_1m(&self) -> Option<f64> {
let chart = self.charts.get(&TimeFrame::M1)?;
let bar = chart.now()?;
let delta = bar.body().delta_p();
Some(delta)
}
pub fn bar_event(&mut self, e: BarEvent) {
assert!(e.tf == TimeFrame::M1);
for (_tf, chart) in self.charts.iter_mut() {
chart.add_bar(e.bar);
}
}
pub fn tic_event(&mut self, _e: TicEvent) {
todo!();
}
pub fn clear(&mut self) {
self.charts.clear();
self.tics.clear();
self.footprints.clear();
}
}
impl std::fmt::Display for Share {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Share={} {}", self.exchange(), self.ticker())
}
}
impl Hash for Share {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.figi().hash(state);
}
}
impl PartialEq for Share {
fn eq(&self, other: &Self) -> bool {
self.figi() == other.figi()
}
}
impl Eq for Share {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn share_new() {
let share = Share::new("moex_share_sber").unwrap();
assert_eq!(share.exchange(), "MOEX");
assert_eq!(share.category(), "SHARE");
assert_eq!(share.ticker(), "SBER");
assert_eq!(share.figi(), "BBG004730N88");
}
#[test]
fn load_chart() {
let mut share = Share::new("moex_share_sber").unwrap();
let tf = TimeFrame::H1;
let begin = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
let end = Utc.with_ymd_and_hms(2025, 2, 1, 0, 0, 0).unwrap();
let chart = share.load_chart_period(tf, begin, end).unwrap();
assert_eq!(chart.tf(), tf);
assert_eq!(
chart.first().unwrap().dt(),
Utc.with_ymd_and_hms(2025, 1, 3, 6, 0, 0).unwrap()
);
assert_eq!(
chart.last().unwrap().dt(),
Utc.with_ymd_and_hms(2025, 1, 31, 19, 0, 0).unwrap()
);
assert_eq!(
chart.now().unwrap().dt(),
Utc.with_ymd_and_hms(2025, 1, 31, 20, 0, 0).unwrap()
);
}
#[test]
fn load_chart_no_args() {
let mut share = Share::new("moex_share_sber").unwrap();
let tf = TimeFrame::Day;
let chart = share.load_chart(tf).unwrap();
assert_eq!(chart.tf(), tf);
assert!(!chart.bars().is_empty());
assert!(chart.bars().len() <= CFG.core.default_bars_count);
}
}