use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ChartType {
Bars,
#[default]
Candles,
HollowCandles,
VolumeCandles,
Line,
LineWithMarkers,
StepLine,
Area,
HlcArea,
Baseline,
HighLow,
Range,
Renko,
Kagi,
LineBreak,
Heikin,
PointAndFigure,
VolumeFootprint,
TimePriceOpportunity,
SessionVolume,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChartTypeCategory {
Standard,
LineBased,
AreaBased,
Japanese,
RangeBased,
Advanced,
}
impl ChartTypeCategory {
pub fn name(&self) -> &'static str {
match self {
Self::Standard => "Standard",
Self::LineBased => "Line",
Self::AreaBased => "Area",
Self::Japanese => "Japanese",
Self::RangeBased => "Range",
Self::Advanced => "Advanced",
}
}
pub fn all() -> &'static [ChartTypeCategory] {
&[
Self::Standard,
Self::LineBased,
Self::AreaBased,
Self::Japanese,
Self::RangeBased,
Self::Advanced,
]
}
}
impl ChartType {
pub fn all() -> &'static [ChartType] {
&[
ChartType::Bars,
ChartType::Candles,
ChartType::HollowCandles,
ChartType::VolumeCandles,
ChartType::Line,
ChartType::LineWithMarkers,
ChartType::StepLine,
ChartType::Area,
ChartType::HlcArea,
ChartType::Baseline,
ChartType::HighLow,
ChartType::VolumeFootprint,
ChartType::TimePriceOpportunity,
ChartType::SessionVolume,
ChartType::LineBreak,
ChartType::Kagi,
ChartType::Range,
ChartType::PointAndFigure,
ChartType::Renko,
ChartType::Heikin,
]
}
pub fn name(&self) -> &'static str {
match self {
ChartType::Bars => "Bars",
ChartType::Candles => "Candles",
ChartType::HollowCandles => "Hollow candles",
ChartType::VolumeCandles => "Volume candles",
ChartType::Line => "Line",
ChartType::LineWithMarkers => "Line with markers",
ChartType::StepLine => "Step line",
ChartType::Area => "Area",
ChartType::HlcArea => "HLC area",
ChartType::Baseline => "Baseline",
ChartType::HighLow => "High-low",
ChartType::VolumeFootprint => "Volume footprint",
ChartType::TimePriceOpportunity => "Time Price Opportunity",
ChartType::SessionVolume => "Session volume",
ChartType::LineBreak => "Line break",
ChartType::Kagi => "Kagi",
ChartType::Range => "Range",
ChartType::PointAndFigure => "Point & Figure",
ChartType::Renko => "Renko",
ChartType::Heikin => "Heikin Ashi",
}
}
pub fn as_str(&self) -> &'static str {
self.name()
}
pub fn description(&self) -> &'static str {
match self {
ChartType::Bars => "OHLC bars with tick marks",
ChartType::Candles => "Japanese candlesticks",
ChartType::HollowCandles => "Hollow when close > open",
ChartType::VolumeCandles => "Width based on volume",
ChartType::Line => "Close price line",
ChartType::LineWithMarkers => "Line with data points",
ChartType::StepLine => "Stepped line chart",
ChartType::Area => "Filled area chart",
ChartType::HlcArea => "High-Low-Close area",
ChartType::Baseline => "Baseline comparison",
ChartType::HighLow => "High-Low range",
ChartType::VolumeFootprint => "Order flow analysis",
ChartType::TimePriceOpportunity => "TPO / Market Profile",
ChartType::SessionVolume => "Volume by session",
ChartType::LineBreak => "Three line break",
ChartType::Kagi => "Kagi chart",
ChartType::Range => "Range bars",
ChartType::PointAndFigure => "X and O chart",
ChartType::Renko => "Renko bricks",
ChartType::Heikin => "Heikin-Ashi candles",
}
}
pub fn desc(&self) -> &'static str {
self.description()
}
pub fn category(&self) -> ChartTypeCategory {
match self {
ChartType::Bars
| ChartType::Candles
| ChartType::HollowCandles
| ChartType::VolumeCandles => ChartTypeCategory::Standard,
ChartType::Line | ChartType::LineWithMarkers | ChartType::StepLine => {
ChartTypeCategory::LineBased
}
ChartType::Area | ChartType::HlcArea | ChartType::Baseline => {
ChartTypeCategory::AreaBased
}
ChartType::Renko
| ChartType::Kagi
| ChartType::LineBreak
| ChartType::Heikin
| ChartType::PointAndFigure => ChartTypeCategory::Japanese,
ChartType::HighLow | ChartType::Range => ChartTypeCategory::RangeBased,
ChartType::VolumeFootprint
| ChartType::TimePriceOpportunity
| ChartType::SessionVolume => ChartTypeCategory::Advanced,
}
}
pub fn in_category(category: ChartTypeCategory) -> Vec<ChartType> {
Self::all()
.iter()
.copied()
.filter(|ct| ct.category() == category)
.collect()
}
pub fn uses_ohlc(&self) -> bool {
match self {
ChartType::Bars
| ChartType::Candles
| ChartType::HollowCandles
| ChartType::VolumeCandles
| ChartType::HlcArea
| ChartType::HighLow
| ChartType::Renko
| ChartType::Kagi
| ChartType::LineBreak
| ChartType::Heikin
| ChartType::Range
| ChartType::PointAndFigure
| ChartType::VolumeFootprint
| ChartType::TimePriceOpportunity
| ChartType::SessionVolume => true,
ChartType::Line
| ChartType::LineWithMarkers
| ChartType::StepLine
| ChartType::Area
| ChartType::Baseline => false,
}
}
pub fn supports_volume(&self) -> bool {
match self {
ChartType::Renko
| ChartType::Kagi
| ChartType::PointAndFigure
| ChartType::Range
| ChartType::LineBreak => false,
ChartType::VolumeFootprint | ChartType::SessionVolume => false,
_ => true,
}
}
pub fn requires_parameters(&self) -> bool {
matches!(
self,
ChartType::Renko
| ChartType::Kagi
| ChartType::Range
| ChartType::PointAndFigure
| ChartType::LineBreak
| ChartType::Baseline
)
}
pub fn is_time_independent(&self) -> bool {
matches!(
self,
ChartType::Renko
| ChartType::Kagi
| ChartType::Range
| ChartType::PointAndFigure
| ChartType::LineBreak
)
}
pub fn transforms_data(&self) -> bool {
matches!(
self,
ChartType::Renko
| ChartType::Kagi
| ChartType::Range
| ChartType::PointAndFigure
| ChartType::LineBreak
| ChartType::Heikin
)
}
}
impl fmt::Display for ChartType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
use crate::model::enums::ChartStyle;
impl From<ChartStyle> for ChartType {
fn from(style: ChartStyle) -> Self {
match style {
ChartStyle::Bar => ChartType::Bars,
ChartStyle::Candle => ChartType::Candles,
ChartStyle::Line => ChartType::Line,
ChartStyle::Area => ChartType::Area,
ChartStyle::Renko => ChartType::Renko,
ChartStyle::Kagi => ChartType::Kagi,
ChartStyle::PnF => ChartType::PointAndFigure,
ChartStyle::LineBreak => ChartType::LineBreak,
ChartStyle::HeikinAshi => ChartType::Heikin,
ChartStyle::HollowCandle => ChartType::HollowCandles,
ChartStyle::Baseline => ChartType::Baseline,
ChartStyle::HighLow => ChartType::HighLow,
ChartStyle::Column => ChartType::Bars, ChartStyle::LineWithMarkers => ChartType::LineWithMarkers,
ChartStyle::Stepline => ChartType::StepLine,
ChartStyle::HLCArea => ChartType::HlcArea,
ChartStyle::VolCandle => ChartType::VolumeCandles,
ChartStyle::HLCBars => ChartType::HighLow, }
}
}
impl ChartType {
pub fn to_chart_style(&self) -> Option<ChartStyle> {
match self {
ChartType::Bars => Some(ChartStyle::Bar),
ChartType::Candles => Some(ChartStyle::Candle),
ChartType::HollowCandles => Some(ChartStyle::HollowCandle),
ChartType::VolumeCandles => Some(ChartStyle::VolCandle),
ChartType::Line => Some(ChartStyle::Line),
ChartType::LineWithMarkers => Some(ChartStyle::LineWithMarkers),
ChartType::StepLine => Some(ChartStyle::Stepline),
ChartType::Area => Some(ChartStyle::Area),
ChartType::HlcArea => Some(ChartStyle::HLCArea),
ChartType::Baseline => Some(ChartStyle::Baseline),
ChartType::HighLow => Some(ChartStyle::HighLow),
ChartType::Renko => Some(ChartStyle::Renko),
ChartType::Kagi => Some(ChartStyle::Kagi),
ChartType::LineBreak => Some(ChartStyle::LineBreak),
ChartType::Heikin => Some(ChartStyle::HeikinAshi),
ChartType::PointAndFigure => Some(ChartStyle::PnF),
ChartType::Range
| ChartType::VolumeFootprint
| ChartType::TimePriceOpportunity
| ChartType::SessionVolume => None,
}
}
}
#[derive(Debug, Clone)]
pub struct ChartTypeParams {
pub renko_brick_size: f64,
pub kagi_reversal: f64,
pub range_size: f64,
pub pnf_box_size: f64,
pub pnf_reversal: u32,
pub line_break_count: usize,
pub baseline_price: f64,
}
impl Default for ChartTypeParams {
fn default() -> Self {
Self {
renko_brick_size: 1.0,
kagi_reversal: 4.0,
range_size: 10.0,
pnf_box_size: 1.0,
pnf_reversal: 3,
line_break_count: 3,
baseline_price: 0.0, }
}
}
impl ChartTypeParams {
pub fn with_atr_renko(atr: f64, multiplier: f64) -> Self {
Self {
renko_brick_size: atr * multiplier,
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_all_chart_types_count() {
assert_eq!(ChartType::all().len(), 20);
}
#[test]
fn test_default_is_candles() {
assert_eq!(ChartType::default(), ChartType::Candles);
}
#[test]
fn test_category_grouping() {
let standard = ChartType::in_category(ChartTypeCategory::Standard);
assert!(standard.contains(&ChartType::Candles));
assert!(standard.contains(&ChartType::Bars));
assert!(!standard.contains(&ChartType::Line));
}
#[test]
fn test_ohlc_vs_single_value() {
assert!(ChartType::Candles.uses_ohlc());
assert!(!ChartType::Line.uses_ohlc());
}
#[test]
fn test_volume_support() {
assert!(ChartType::Candles.supports_volume());
assert!(!ChartType::Renko.supports_volume());
}
#[test]
fn test_time_independence() {
assert!(ChartType::Renko.is_time_independent());
assert!(!ChartType::Candles.is_time_independent());
}
#[test]
fn test_display_trait() {
assert_eq!(format!("{}", ChartType::Candles), "Candles");
assert_eq!(format!("{}", ChartType::Heikin), "Heikin Ashi");
}
}