2026/1/18 微修正版 ------------------------------ se chrono::{Datelike, NaiveDate, Utc}; use plotters::{ chart::ChartBuilder, prelude::{BindKeyPoints, BitMapBackend, IntoDrawingArea}, series::LineSeries, style::{BLUE, Color, IntoFont, WHITE}, }; use polars::{ df, frame::DataFrame, prelude::{DataType, IntoLazy, JoinArgs, col, lit}, }; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use time::{OffsetDateTime, macros::datetime}; use yahoo_finance_api::{self, YResponse}; async fn load_vt_price() -> Result> { let provider = yahoo_finance_api::YahooConnector::new()?; let ticker = "VT"; let start = datetime!(2008-6-26 0:00:00.00 UTC); let end = OffsetDateTime::now_utc(); let resp: YResponse = provider.get_quote_history(ticker, start, end).await?; let quotes = resp.quotes()?; let mut ts_vec = Vec::with_capacity(quotes.len()); let mut close_vec = Vec::with_capacity(quotes.len()); for q in quotes { ts_vec.push(q.timestamp); close_vec.push(q.close); } let col_name_ts = "ts"; let col_name_close = "vt_close_usd"; let col_name_date = "date"; let ts_offset = 0; let price = df![ col_name_ts => ts_vec, col_name_close => close_vec ]?; let price = price .lazy() .with_column( ((col(col_name_ts) + lit(ts_offset)) * lit(1000)) .cast(DataType::Datetime( polars::prelude::TimeUnit::Milliseconds, None, )) .cast(DataType::Date) .alias(col_name_date), ) .select([col(col_name_date), col(col_name_close)]) .collect()?; Ok(price) } async fn load_jpyx_price() -> Result> { let provider = yahoo_finance_api::YahooConnector::new()?; let ticker = "JPY=X"; let start = datetime!(1996-10-30 0:00:00.00 UTC); let end = OffsetDateTime::now_utc(); let resp: YResponse = provider.get_quote_history(ticker, start, end).await?; let quotes = resp.quotes()?; let mut ts_vec = Vec::with_capacity(quotes.len()); let mut close_vec = Vec::with_capacity(quotes.len()); for q in quotes { ts_vec.push(q.timestamp); close_vec.push(q.close); } let col_name_ts = "ts"; let col_name_close = "jpyx_close_usd"; let col_name_date = "date"; let ts_offset = 52200; let price = df![ col_name_ts => ts_vec, col_name_close => close_vec ]?; let price = price .lazy() .with_column( ((col(col_name_ts) + lit(ts_offset)) * lit(1000)) .cast(DataType::Datetime( polars::prelude::TimeUnit::Milliseconds, None, )) .cast(DataType::Date) .alias(col_name_date), ) .select([col(col_name_date), col(col_name_close)]) .collect()?; Ok(price) } async fn load_price() -> Result> { let vt_price = load_vt_price().await?; let jpyx_price = load_jpyx_price().await?; let price = vt_price.lazy().join( jpyx_price.lazy(), [col("date")], [col("date")], JoinArgs::new(polars::prelude::JoinType::Inner), ); let price = price .with_column((col("vt_close_usd") * col("jpyx_close_usd")).alias("vt_close_yen")) .collect()?; Ok(price) } fn output_graph(price: DataFrame) -> Result<(), Box> { let dates: Vec = price .column("date")? .date()? .as_date_iter() .flatten() .collect(); let closes: Vec = price .column("vt_close_usd")? .f64()? .into_iter() .flatten() .collect(); let closes_yen: Vec = price .column("vt_close_yen")? .f64()? .into_iter() .flatten() .collect(); let n = dates.len(); let today = Utc::now().format("%Y%m%d"); (1..n).into_par_iter().for_each(|i| { let png_filename = format!("image/VT価格推移_{}_{:04}.png", today, i); let root = BitMapBackend::new(&png_filename, (3840, 3840)).into_drawing_area(); root.fill(&WHITE).unwrap(); let areas = root.split_evenly((2, 1)); let x_min = NaiveDate::from_ymd_opt(2008, 1, 1).unwrap(); let x_max = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); let x_ticks: Vec = (2008..=2027) .map(|y| NaiveDate::from_ymd_opt(y, 1, 1).unwrap()) .collect(); let y_min: f64 = 0.0; let y_max: f64 = 160.0; let y_min_yen: f64 = 0.0; let y_max_yen: f64 = 25000.0; let mut chart = ChartBuilder::on(&areas[0]) .caption("VT価格推移 - テスト中", ("Yu Gothic", 80)) .margin(100) .x_label_area_size(100) .y_label_area_size(150) .build_cartesian_2d( (x_min..x_max).with_key_points(x_ticks.clone()), y_min..y_max, ) .unwrap(); chart .configure_mesh() .label_style(("Yu Gothic", 50).into_font()) .x_label_formatter(&|x| format!("{}", x.year())) .y_desc("価格(USD)") .y_label_formatter(&|y| format!("{:.0}", y)) .draw() .unwrap(); chart .draw_series(LineSeries::new( dates[0..i] .iter() .cloned() .zip(closes[0..i].iter().cloned()), BLUE.stroke_width(1), )) .unwrap(); let mut chart2 = ChartBuilder::on(&areas[1]) .caption("円建VT価格推移 - テスト中", ("Yu Gothic", 80)) .margin(100) .x_label_area_size(100) .y_label_area_size(150) .build_cartesian_2d( (x_min..x_max).with_key_points(x_ticks.clone()), y_min_yen..y_max_yen, ) .unwrap(); chart2 .configure_mesh() .label_style(("Yu Gothic", 50).into_font()) .x_label_formatter(&|x| format!("{}", x.year())) .y_desc("価格(円)") .y_label_formatter(&|y| format!("{:.0}", y)) .draw() .unwrap(); chart2 .draw_series(LineSeries::new( dates[0..i] .iter() .cloned() .zip(closes_yen[0..i].iter().cloned()), BLUE.stroke_width(1), )) .unwrap(); }); Ok(()) } #[tokio::main] async fn main() -> Result<(), Box> { let start = std::time::Instant::now(); let price = load_price().await?; println!("{:?}", price); output_graph(price)?; let elapsed = start.elapsed(); println!("処理時間(sec) = {:.3}", elapsed.as_secs_f64()); Ok(()) } ------------------------------