Skip to content

Commit 07d9e1b

Browse files
authored
Merge pull request #9 from Julien-R44/feat/volume-pane
Feat/volume pane + basic documentation
2 parents 1390f2c + fad0476 commit 07d9e1b

11 files changed

Lines changed: 223 additions & 36 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ fn main() {
6060
// Set customs colors
6161
chart.set_bear_color(1, 205, 254);
6262
chart.set_bull_color(255, 107, 153);
63+
chart.set_vol_bull_color(1, 205, 254);
64+
chart.set_vol_bear_color(255, 107, 153);
6365

66+
chart.set_volume_pane_height(6);
67+
chart.set_volume_pane_enabled(false);
68+
6469
chart.draw();
6570
}
6671
```

examples/fetch-from-binance.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct BinanceKlinesItem {
2121

2222
fn main() -> Result<(), Box<dyn Error>> {
2323
let candles =
24-
reqwest::blocking::get("https://api.binance.com/api/v1/klines?symbol=BTCUSDT&interval=1h")?
24+
reqwest::blocking::get("https://api.binance.com/api/v1/klines?symbol=CHZUSDT&interval=1h")?
2525
.json::<Vec<BinanceKlinesItem>>()?
2626
.iter()
2727
.map(|candle| {
@@ -30,18 +30,22 @@ fn main() -> Result<(), Box<dyn Error>> {
3030
candle.high.parse::<f64>().unwrap(),
3131
candle.low.parse::<f64>().unwrap(),
3232
candle.close.parse::<f64>().unwrap(),
33+
Some(candle.volume.parse::<f64>().unwrap()),
34+
Some(candle.open_time as i64),
3335
)
3436
})
3537
.collect::<Vec<Candle>>();
3638

3739
let mut chart = Chart::new(&candles);
3840

39-
// Set the chart title
40-
chart.set_name(String::from("BTC/USDT"));
41-
42-
// Set customs colors
43-
chart.set_bear_color(1, 205, 254);
44-
chart.set_bull_color(255, 107, 153);
41+
chart.set_name(String::from("CHZ/USDT"));
42+
chart.set_bull_color(1, 205, 254);
43+
chart.set_bear_color(255, 107, 153);
44+
chart.set_vol_bull_color(1, 205, 254);
45+
chart.set_vol_bear_color(255, 107, 153);
46+
chart.set_volume_pane_height(4);
47+
chart.set_volume_pane_enabled(true);
48+
// chart.set_volume_pane_unicode_fill(true);
4549

4650
chart.draw();
4751

src/bin/cli/yahoo_api.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ pub fn get_yahoo_klines(symbol: &str, interval: &str) -> Vec<Candle> {
3434
let client = reqwest::blocking::get(url.as_str()).unwrap();
3535
let value: serde_json::Value = serde_json::from_str(client.text().unwrap().as_str()).unwrap();
3636

37-
let nb_candles = value["chart"]["result"][0]["timestamp"]
37+
let timestamps = value["chart"]["result"][0]["timestamp"]
3838
.as_array()
39-
.expect("Ticker seems to be invalid.")
40-
.len();
39+
.expect("Tickers seems to be invalid.");
40+
41+
let nb_candles = timestamps.len();
4142

4243
let mut candles: Vec<Candle> = Vec::new();
4344
for i in 0..nb_candles {
@@ -47,12 +48,16 @@ pub fn get_yahoo_klines(symbol: &str, interval: &str) -> Vec<Candle> {
4748
let high = base["high"][i].as_f64().unwrap();
4849
let low = base["low"][i].as_f64().unwrap();
4950
let close = base["close"][i].as_f64().unwrap();
51+
let volume = base["volume"][i].as_f64().unwrap();
52+
let timestamp = timestamps[i].as_i64().unwrap();
5053

5154
let candle = Candle::new(
5255
open.to_owned(),
5356
high.to_owned(),
5457
low.to_owned(),
5558
close.to_owned(),
59+
Some(volume.to_owned()),
60+
Some(timestamp.to_owned()),
5661
);
5762
candles.push(candle);
5863
}

src/candle_set.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,31 @@ use crate::Candle;
33
#[derive(Debug, Clone)]
44
pub struct CandleSet {
55
pub candles: Vec<Candle>,
6-
pub min_value: f64,
7-
pub max_value: f64,
6+
7+
pub min_price: f64,
8+
pub max_price: f64,
9+
10+
pub min_volume: f64,
11+
pub max_volume: f64,
12+
813
pub variation: f64,
914
pub average: f64,
1015
pub last_price: f64,
16+
pub cumulative_volume: f64,
1117
}
1218

1319
impl CandleSet {
1420
pub fn new(candles: Vec<Candle>) -> CandleSet {
1521
let mut cs = CandleSet {
1622
candles: Vec::new(),
17-
min_value: 0.0,
18-
max_value: 0.0,
23+
min_price: 0.0,
24+
max_price: 0.0,
25+
min_volume: 0.0,
26+
max_volume: 0.0,
1927
variation: 0.0,
2028
average: 0.0,
2129
last_price: 0.0,
30+
cumulative_volume: 0.0,
2231
};
2332

2433
cs.set_candles(candles);
@@ -37,6 +46,14 @@ impl CandleSet {
3746
self.compute_variation();
3847
self.compute_average();
3948
self.compute_last_price();
49+
self.compute_cumulative_volume();
50+
}
51+
52+
fn compute_cumulative_volume(&mut self) {
53+
self.cumulative_volume = self
54+
.candles
55+
.iter()
56+
.fold(0.0, |acc, candle| acc + candle.volume.unwrap_or_default());
4057
}
4158

4259
fn compute_last_price(&mut self) {
@@ -57,11 +74,20 @@ impl CandleSet {
5774
}
5875

5976
fn compute_min_and_max_values(&mut self) {
60-
self.max_value = self
77+
self.max_price = self
6178
.candles
6279
.iter()
6380
.fold(f64::NEG_INFINITY, |a, b| a.max(b.high));
6481

65-
self.min_value = self.candles.iter().fold(f64::INFINITY, |a, b| a.min(b.low));
82+
self.min_price = self.candles.iter().fold(f64::INFINITY, |a, b| a.min(b.low));
83+
84+
self.max_volume = self.candles.iter().fold(f64::NEG_INFINITY, |a, b| {
85+
a.max(b.volume.unwrap_or_default())
86+
});
87+
88+
self.min_volume = self
89+
.candles
90+
.iter()
91+
.fold(f64::INFINITY, |a, b| a.min(b.volume.unwrap_or_default()));
6692
}
6793
}

src/chart.rs

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use std::{cell::RefCell, rc::Rc};
2-
31
use crate::{
4-
chart_data::ChartData, chart_renderer::ChartRenderer, info_bar::InfoBar, y_axis::YAxis,
2+
chart_data::ChartData, chart_renderer::ChartRenderer, info_bar::InfoBar,
3+
volume_pane::VolumePane, y_axis::YAxis,
54
};
5+
use std::cell::RefCell;
6+
use std::rc::Rc;
67

78
#[derive(Debug, Clone)]
89
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
@@ -11,25 +12,36 @@ pub struct Candle {
1112
pub high: f64,
1213
pub low: f64,
1314
pub close: f64,
15+
pub volume: Option<f64>,
16+
pub timestamp: Option<i64>,
1417
}
1518

16-
pub enum CandleType {
19+
pub(crate) enum CandleType {
1720
Bearish,
1821
Bullish,
1922
}
2023

2124
impl Candle {
2225
#[allow(dead_code)]
23-
pub fn new(open: f64, high: f64, low: f64, close: f64) -> Candle {
26+
pub fn new(
27+
open: f64,
28+
high: f64,
29+
low: f64,
30+
close: f64,
31+
volume: Option<f64>,
32+
timestamp: Option<i64>,
33+
) -> Candle {
2434
Candle {
2535
open,
2636
high,
2737
low,
2838
close,
39+
volume,
40+
timestamp,
2941
}
3042
}
3143

32-
pub fn get_type(&self) -> CandleType {
44+
pub(crate) fn get_type(&self) -> CandleType {
3345
match self.open < self.close {
3446
true => CandleType::Bullish,
3547
false => CandleType::Bearish,
@@ -42,6 +54,7 @@ pub struct Chart {
4254
pub(crate) y_axis: YAxis,
4355
pub(crate) chart_data: Rc<RefCell<ChartData>>,
4456
pub(crate) info_bar: InfoBar,
57+
pub(crate) volume_pane: VolumePane,
4558
}
4659

4760
impl Chart {
@@ -51,27 +64,69 @@ impl Chart {
5164
let y_axis = YAxis::new(chart_data.clone());
5265
let info_bar = InfoBar::new("APPLE".to_string(), chart_data.clone());
5366

67+
let volume_pane = VolumePane::new(
68+
chart_data.clone(),
69+
(chart_data.borrow().terminal_size.1 / 6) as i64,
70+
);
71+
72+
chart_data.borrow_mut().compute_height(&volume_pane);
73+
5474
Chart {
5575
renderer,
5676
y_axis,
5777
chart_data,
5878
info_bar,
79+
volume_pane,
5980
}
6081
}
6182

83+
/// Draws the chart by outputting multiples strings in the terminal.
6284
pub fn draw(&self) {
6385
self.renderer.render(self);
6486
}
6587

88+
/// Set the name of the chart in the info bar.
6689
pub fn set_name(&mut self, name: String) {
6790
self.info_bar.name = name;
6891
}
6992

93+
/// Set the color of the bearish candle
94+
/// The default color is (234, 74, 90).
7095
pub fn set_bear_color(&mut self, r: u8, g: u8, b: u8) {
7196
self.renderer.bearish_color = (r, g, b);
7297
}
7398

99+
/// Set the color of the bullish candle
100+
/// The default color is (52, 208, 88).
74101
pub fn set_bull_color(&mut self, r: u8, g: u8, b: u8) {
75102
self.renderer.bullish_color = (r, g, b);
76103
}
104+
105+
/// Sets the color of the volume when the candle is bearish.
106+
/// The default color is (234, 74, 90).
107+
pub fn set_vol_bear_color(&mut self, r: u8, g: u8, b: u8) {
108+
self.volume_pane.bearish_color = (r, g, b);
109+
}
110+
111+
/// Sets the color of the volume when the candle is bullish.
112+
/// The default color is (52, 208, 88).
113+
pub fn set_vol_bull_color(&mut self, r: u8, g: u8, b: u8) {
114+
self.volume_pane.bullish_color = (r, g, b);
115+
}
116+
117+
/// Hide or show the volume pane.
118+
pub fn set_volume_pane_enabled(&mut self, enabled: bool) {
119+
self.volume_pane.enabled = enabled;
120+
}
121+
122+
/// Set the character for drawing the volume bars.
123+
pub fn set_volume_pane_unicode_fill(&mut self, unicode_fill: char) {
124+
self.volume_pane.unicode_fill = unicode_fill;
125+
}
126+
127+
/// Set the volume pane height.
128+
/// Default is 1/6 of the terminal height.
129+
pub fn set_volume_pane_height(&mut self, height: i64) {
130+
self.volume_pane.height = height;
131+
}
77132
}

src/chart_data.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
candle_set::CandleSet, chart::Candle, chart_renderer::ChartRenderer, info_bar::InfoBar,
3-
y_axis::YAxis,
3+
volume_pane::VolumePane, y_axis::YAxis,
44
};
55
use terminal_size::terminal_size;
66

@@ -20,13 +20,26 @@ impl ChartData {
2020
main_candle_set: CandleSet::new(candles),
2121
visible_candle_set: CandleSet::new(Vec::new()),
2222
terminal_size: (w.0, h.0),
23-
height: h.0 as i64 - ChartRenderer::MARGIN_TOP - InfoBar::HEIGHT,
23+
height: h.0 as i64,
2424
};
2525

2626
chart_data.compute_visible_candles();
2727
chart_data
2828
}
2929

30+
pub fn compute_height(&mut self, volume_pane: &VolumePane) {
31+
let volume_pane_height = if volume_pane.enabled {
32+
volume_pane.height
33+
} else {
34+
0
35+
};
36+
37+
self.height = self.terminal_size.1 as i64
38+
- ChartRenderer::MARGIN_TOP
39+
- InfoBar::HEIGHT
40+
- volume_pane_height;
41+
}
42+
3043
pub fn compute_visible_candles(&mut self) {
3144
let term_width = self.terminal_size.0 as usize as i64;
3245
let nb_candles = self.main_candle_set.candles.len();

src/chart_renderer.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ impl ChartRenderer {
2424
control::set_virtual_terminal(true).unwrap();
2525

2626
ChartRenderer {
27-
bearish_color: (52, 208, 88),
28-
bullish_color: (234, 74, 90),
27+
bullish_color: (52, 208, 88),
28+
bearish_color: (234, 74, 90),
2929
}
3030
}
3131

@@ -88,6 +88,10 @@ impl ChartRenderer {
8888
pub fn render(&self, chart: &Chart) {
8989
let mut output_str = "".to_owned();
9090

91+
let mut chart_data = chart.chart_data.borrow_mut();
92+
chart_data.compute_height(&chart.volume_pane);
93+
drop(chart_data);
94+
9195
let chart_data = chart.chart_data.borrow();
9296

9397
for y in (1..chart_data.height as u16).rev() {
@@ -100,6 +104,18 @@ impl ChartRenderer {
100104
}
101105
}
102106

107+
if chart.volume_pane.enabled {
108+
for y in (1..chart.volume_pane.height + 1).rev() {
109+
output_str += "\n";
110+
111+
output_str += &chart.y_axis.render_empty();
112+
113+
for candle in chart_data.visible_candle_set.candles.iter() {
114+
output_str += &chart.volume_pane.render(candle, y);
115+
}
116+
}
117+
}
118+
103119
output_str += &chart.info_bar.render();
104120

105121
println!("{}", output_str)

src/info_bar.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,22 @@ impl InfoBar {
3636
}
3737
.to_string();
3838

39+
let variation_output = if main_set.variation > 0.0 {
40+
("↖", "green")
41+
} else {
42+
("↙", "red")
43+
};
44+
3945
output_str += &format!(
40-
"{:>width$} | Price: {price} | Highest: {high} | Lowest: {low} | Var.: {var} | Avg.: {avg} │",
46+
"{:>width$} | Price: {price} | Highest: {high} | Lowest: {low} | Var.: {var} | Avg.: {avg} │ Cum. Vol: {vol}",
4147
&self.name,
4248
width = YAxis::WIDTH as usize + 3,
43-
high = format!("{:.2}", main_set.max_value).green().bold(),
44-
low = format!("{:.2}", main_set.min_value).red().bold(),
45-
var = format!(" {:>+.2}%", main_set.variation).green().bold(),
49+
high = format!("{:.2}", main_set.max_price).green().bold(),
50+
low = format!("{:.2}", main_set.min_price).red().bold(),
51+
var = format!("{} {:>+.2}%", variation_output.0, main_set.variation).color(variation_output.1).bold(),
4652
avg = avg_format,
4753
price = format!("{:.2}", main_set.last_price).green().bold(),
54+
vol = format!("{:.0}", main_set.cumulative_volume).green().bold(),
4855
);
4956

5057
output_str

0 commit comments

Comments
 (0)