use crate::error::Result;
use crate::models::{OptionChain, OptionContract};
use std::fs;
use std::path::Path;
use yf_common::output::JsonWriter;
pub fn write_to_file(
chain: &OptionChain,
output_dir: &str,
format: &str,
pretty: bool,
) -> Result<String> {
match format {
"json" => {
let writer = JsonWriter::new(output_dir, pretty);
let filename = format!("{}_options.json", chain.symbol);
let path = writer.write(chain, &filename)?;
Ok(path.to_string_lossy().to_string())
}
"csv" => {
let filename = format!("{}_options.csv", chain.symbol);
let output_path = Path::new(output_dir).join(&filename);
write_csv(chain, &output_path)?;
Ok(output_path.to_string_lossy().to_string())
}
_ => unreachable!("Invalid format validated by clap"),
}
}
pub fn write_combined_to_file(
chains: &[OptionChain],
output_dir: &str,
format: &str,
pretty: bool,
combined_filename: &str,
) -> Result<String> {
match format {
"json" => {
let writer = JsonWriter::new(output_dir, pretty);
let filename = if combined_filename.ends_with(".json") {
combined_filename.to_string()
} else {
format!("{}.json", combined_filename)
};
let chains_vec: Vec<&OptionChain> = chains.iter().collect();
let path = writer.write(&chains_vec, &filename)?;
Ok(path.to_string_lossy().to_string())
}
"csv" => {
let filename = if combined_filename.ends_with(".csv") {
combined_filename.to_string()
} else {
format!("{}.csv", combined_filename)
};
let output_path = Path::new(output_dir).join(&filename);
let mut csv_content = String::new();
csv_content.push_str(CSV_HEADER);
for chain in chains {
write_chain_csv_rows(&mut csv_content, chain);
}
fs::write(&output_path, csv_content)?;
Ok(output_path.to_string_lossy().to_string())
}
_ => unreachable!("Invalid format validated by clap"),
}
}
const CSV_HEADER: &str = "symbol,underlying_price,expiration_date,option_type,contract_symbol,strike,last_price,bid,ask,volume,open_interest,implied_volatility,in_the_money,delta,gamma,theta,vega,rho\n";
fn write_csv(chain: &OptionChain, path: &Path) -> Result<()> {
let mut csv_content = String::new();
csv_content.push_str(CSV_HEADER);
write_chain_csv_rows(&mut csv_content, chain);
fs::write(path, csv_content)?;
Ok(())
}
fn write_chain_csv_rows(buf: &mut String, chain: &OptionChain) {
for expiration_data in &chain.options {
let exp_date = expiration_data.expiration_date;
for call in &expiration_data.calls {
buf.push_str(&format_option_row(
&chain.symbol,
chain.underlying_price,
exp_date,
"call",
call,
));
}
for put in &expiration_data.puts {
buf.push_str(&format_option_row(
&chain.symbol,
chain.underlying_price,
exp_date,
"put",
put,
));
}
}
}
fn format_option_row(
symbol: &str,
underlying_price: f64,
expiration: i64,
option_type: &str,
contract: &OptionContract,
) -> String {
let mut row = format!(
"{},{},{},{},{},{},{},{},{},{},{},{},{}",
symbol,
underlying_price,
expiration,
option_type,
contract.contract_symbol,
contract.strike,
contract.last_price,
contract.bid.map_or(String::new(), |v| v.to_string()),
contract.ask.map_or(String::new(), |v| v.to_string()),
contract.volume.map_or(String::new(), |v| v.to_string()),
contract
.open_interest
.map_or(String::new(), |v| v.to_string()),
contract.implied_volatility,
contract.in_the_money,
);
if let Some(greeks) = &contract.greeks {
row.push_str(&format!(
",{},{},{},{},{}",
greeks.delta, greeks.gamma, greeks.theta, greeks.vega, greeks.rho
));
} else {
row.push_str(",,,,,");
}
row.push('\n');
row
}