use std::path::Path;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use avin_analyse::TrendAnalytic;
use avin_core::{Chart, ExtremumIndicator, TimeFrame};
use avin_utils::{AvinError, CFG, Cmd};
pub trait Filter {
fn name(&self) -> &'static str;
fn apply(&self, chart: &Chart) -> bool;
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum MarkerShape {
Circle,
Square,
Diamond,
Plus,
Cross,
Asterisk,
Up,
Down,
Left,
Right,
}
impl std::fmt::Display for MarkerShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Circle => write!(f, "Circle"),
Self::Square => write!(f, "Square"),
Self::Diamond => write!(f, "Diamond"),
Self::Plus => write!(f, "Plus"),
Self::Cross => write!(f, "Cross"),
Self::Asterisk => write!(f, "Asterisk"),
Self::Up => write!(f, "Up"),
Self::Down => write!(f, "Down"),
Self::Left => write!(f, "Left"),
Self::Right => write!(f, "Right"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum MarkerColor {
Red,
Orange,
Yellow,
Green,
Cyan,
Blue,
Violet,
White,
Grey,
Black,
}
impl std::fmt::Display for MarkerColor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Red => write!(f, "#FF0000"),
Self::Orange => write!(f, "#FFA500"),
Self::Yellow => write!(f, "#FFFF00"),
Self::Green => write!(f, "#008000"),
Self::Cyan => write!(f, "#00FFFF"),
Self::Blue => write!(f, "#0000FF"),
Self::Violet => write!(f, "#EE82EE"),
Self::White => write!(f, "#FFFFFF"),
Self::Grey => write!(f, "#808080"),
Self::Black => write!(f, "#000000"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum MarkerSize {
S = 3,
M = 5,
L = 8,
}
impl std::fmt::Display for MarkerSize {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::S => write!(f, "S"),
Self::M => write!(f, "M"),
Self::L => write!(f, "L"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Marker {
pub shape: MarkerShape,
pub color: MarkerColor,
pub size: MarkerSize,
}
impl Marker {
pub fn new(
shape: MarkerShape,
color: MarkerColor,
size: MarkerSize,
) -> Self {
Self { shape, color, size }
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ScannerResult {
scan_name: String,
iid_name: String,
tf: TimeFrame,
marker: Marker,
points: Vec<i64>,
}
impl ScannerResult {
pub fn new(
chart: &Chart,
filter: impl Filter,
marker: Marker,
points: Vec<i64>,
) -> Self {
Self {
scan_name: format!("{}_{}", filter.name(), chart.ticker()),
iid_name: chart.iid().to_string(),
tf: chart.tf(),
marker,
points,
}
}
pub fn save(result: &ScannerResult) -> Result<(), AvinError> {
let text = toml::to_string_pretty(result).unwrap();
let mut path = CFG.dir.scan();
path.push(format!("{}.toml", result.scan_name));
Cmd::write(&text, &path).unwrap();
Ok(())
}
pub fn load(path: &Path) -> Result<Self, AvinError> {
let text = Cmd::read(path).unwrap();
let r: ScannerResult = toml::from_str(&text).unwrap();
Ok(r)
}
pub fn scan_name(&self) -> &String {
&self.scan_name
}
pub fn iid_name(&self) -> &String {
&self.iid_name
}
pub fn tf(&self) -> TimeFrame {
self.tf
}
pub fn marker(&self) -> &Marker {
&self.marker
}
pub fn points(&self) -> &Vec<i64> {
&self.points
}
pub fn begin(&self) -> DateTime<Utc> {
let ts = self.points.first().unwrap();
DateTime::from_timestamp_nanos(
*ts - 24 * 60 * 60 * 1_000_000_000, )
}
pub fn end(&self) -> DateTime<Utc> {
let ts = self.points.last().unwrap();
DateTime::from_timestamp_nanos(
*ts + 24 * 60 * 60 * 1_000_000_000, )
}
}
pub struct ScannerResultList {
scanner_results: Vec<ScannerResult>,
}
impl Default for ScannerResultList {
fn default() -> Self {
ScannerResultList::new()
}
}
impl ScannerResultList {
pub fn new() -> Self {
Self {
scanner_results: Vec::new(),
}
}
pub fn save(list: &ScannerResultList) -> Result<(), AvinError> {
for result in list.scanner_results.iter() {
ScannerResult::save(result).unwrap();
}
Ok(())
}
pub fn load_name(name: &str) -> Result<ScannerResultList, AvinError> {
let mut scan_list = ScannerResultList::new();
let mut dir_path = CFG.dir.scan();
dir_path.push(name);
let files = Cmd::get_files(&dir_path).unwrap();
for file in files {
let scan = ScannerResult::load(&file).unwrap();
scan_list.add(scan);
}
Ok(scan_list)
}
pub fn load_dir(path: &Path) -> Result<ScannerResultList, AvinError> {
let mut test_list = ScannerResultList::new();
let files = Cmd::get_files(path).unwrap();
for file in files {
let scan = ScannerResult::load(&file).unwrap();
test_list.add(scan);
}
Ok(test_list)
}
pub fn is_empty(&self) -> bool {
self.scanner_results.is_empty()
}
pub fn len(&self) -> usize {
self.scanner_results.len()
}
pub fn results(&self) -> &Vec<ScannerResult> {
&self.scanner_results
}
pub fn add(&mut self, result: ScannerResult) {
self.scanner_results.push(result);
}
pub fn get(&self, index: usize) -> Option<&ScannerResult> {
self.scanner_results.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut ScannerResult> {
self.scanner_results.get_mut(index)
}
pub fn clear(&mut self) {
self.scanner_results.clear();
}
}
pub struct Scanner {}
impl Scanner {
pub fn scan(
chart: &Chart,
filter: impl Filter,
marker: Marker,
) -> Result<(), AvinError> {
let mut points = Vec::new();
let mut new_chart = Chart::empty(chart.iid(), chart.tf());
ExtremumIndicator::init(&mut new_chart);
TrendAnalytic::init(&mut new_chart);
let bars = chart.bars();
for bar in bars.iter() {
new_chart.add_bar(*bar);
let result = filter.apply(&new_chart);
if result {
let point = new_chart.now().unwrap().ts_nanos;
points.push(point);
}
}
let scan_result = ScannerResult::new(chart, filter, marker, points);
ScannerResult::save(&scan_result)
}
}