commit a7079454a2819620d7f22a02635c75bd5c20ca28 Author: Griffiths Lott Date: Sat Nov 26 17:58:20 2022 -0500 Framed inital data module: equity, crypto, options, price_history. Created traits for data service. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..17614f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +to-do.txt \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..792e136 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "RustyTrader" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = "0.4.23" +regex = "1.7.0" +polars = "0.25.1" \ No newline at end of file diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..26712dc --- /dev/null +++ b/src/data.rs @@ -0,0 +1,5 @@ +mod equity; +mod crypto; +mod options; +mod price_history; +mod data_service; \ No newline at end of file diff --git a/src/data/crypto.rs b/src/data/crypto.rs new file mode 100644 index 0000000..5f6fae9 --- /dev/null +++ b/src/data/crypto.rs @@ -0,0 +1,49 @@ +use std::fmt::Display; +use crate::DynResult; + +#[derive(Debug)] +pub struct CryptoError { + _error: String +} +impl CryptoError { + pub fn new(error_msg: &str) -> Self { + CryptoError { _error: error_msg.to_owned() } + } +} +impl Display for CryptoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Crypto Error Placeholder") + } +} +impl std::error::Error for CryptoError {} + +#[derive(Debug)] +pub struct Crypto { + pub symbol: String, + pub exchange: Option, + pub base_crypto: String, // being purchased (top) + pub quote_crypto: String, + pub bid: f64, + pub mid: f64, + pub ask: f64, + pub last: Option, + pub bid_size: Option, + pub ask_size: Option, + pub volume: Option, + pub high: Option, + pub open: Option, + pub low: Option, + pub close: Option, + pub marginable: Option, + pub quote_time: i64 +} +impl Crypto { + +} + + + + +pub trait ToCrypto { + fn to_crypto(&self) -> DynResult; +} \ No newline at end of file diff --git a/src/data/data_service.rs b/src/data/data_service.rs new file mode 100644 index 0000000..c7718b4 --- /dev/null +++ b/src/data/data_service.rs @@ -0,0 +1,58 @@ +use crate::data::*; +use crate::DynResult; + +/* +A data service must implement at lease one of the following traits + + + + + + + + +Important to note that Paramaters can be typed as Option

during implemenation +and that the actual strucut/type passed into these is flexable. This should allow +each services library to implement a paramates trait that works for them, or +leave paramaters as 'None' + +This way you can lock down what paramaters are required for each indiviudual service +while still allowing the RustyTrade framework to reliably obtain the expected struct +from each trait interface. + +struct ExamplePriceHistoryParams { + start_date: i64, + end_date: i64 +} + +struct ExampleDataService {}; +impl GetPriceHistory for ExampleDataService { + type Paramaters = ExamplePriceHistoryParmas; + fn get_price_history(&mut self, paramaters: Self::Paramaters)-> DynResult { + + } +} + +*/ +pub trait GetEquity { + type Paramaters; + fn get_equity(&mut self, paramaters: Self::Paramaters) -> DynResult; +} +pub trait GetCrypto { + type Paramaters; + fn get_crypto(&mut self, paramaters: Self::Paramaters) -> DynResult; +} +pub trait GetOptionContract { + type Paramaters; + fn get_option_contract(&mut self, paramaters: Self::Paramaters) -> DynResult; +} +pub trait GetOptionChain { + type Paramaters; + fn get_options_chain_vec(&mut self, paramaters: Self::Paramaters) -> DynResult>; + fn get_options_chain_dataframe(&mut self, paramaters: Self::Paramaters) -> DynResult; +} +pub trait GetPriceHistory{ + type Paramaters; + fn get_price_history(&mut self, paramaters: Self::Paramaters)-> DynResult; +} + diff --git a/src/data/equity.rs b/src/data/equity.rs new file mode 100644 index 0000000..38f7520 --- /dev/null +++ b/src/data/equity.rs @@ -0,0 +1,51 @@ +use std::fmt::Display; +use crate::DynResult; + +#[derive(Debug)] +pub struct EquityError { + error: String +} +impl EquityError { + pub fn new(error_msg: &str) -> Self { + EquityError { error: error_msg.to_owned() } + } +} +impl Display for EquityError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Equity Error Placeholder") + } +} +impl std::error::Error for EquityError {} + +#[derive(Debug)] +pub struct Equity { + pub symbol: String, + pub cusip : Option, + pub exchange: Option, + pub bid: f64, + pub mid: f64, + pub ask: f64, + pub last: Option, + pub bid_size: Option, + pub ask_size: Option, + pub volume: Option, + pub high: Option, + pub open: Option, + pub low: Option, + pub close: Option, + pub shortable: Option, + pub marginable: Option, + pub quote_time: i64 +} +impl Equity { + +} + + + + + + +pub trait ToEquity { + fn to_equity(&self) -> DynResult; +} \ No newline at end of file diff --git a/src/data/options.rs b/src/data/options.rs new file mode 100644 index 0000000..986d299 --- /dev/null +++ b/src/data/options.rs @@ -0,0 +1,193 @@ +use std::fmt::Display; +use chrono::{TimeZone, LocalResult, DateTime}; +use polars::{prelude::DataFrame, df, prelude::{NamedFrom}}; +use regex::Regex; + + +use crate::DynResult; + +#[derive(Debug)] +pub struct OptionContractError { + error: String +} +impl OptionContractError { + pub fn new(error_msg: &str) -> Self { + OptionContractError { error: error_msg.to_owned() } + } +} +impl Display for OptionContractError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "OptionContract Error Placeholder") + } +} +impl std::error::Error for OptionContractError {} + +pub enum OptionKind { + Call, + Put +} +impl OptionKind { + fn from_str(option_kind: &str) -> Result{ + let call = Regex::new("(?i)call|(?i)c").expect("call regex failed:\noptions.rs:\t(?i)call|(?i)c"); + let put = Regex::new("(?i)put|(?i)p").expect("put regex failed:\noptions.rs:\t(?i)put|(?i)p"); + + if call.find(option_kind).is_some() { + return Ok(OptionKind::Call) + } else if put.find(option_kind).is_some() { + return Ok(OptionKind::Put)} + + return Err(OptionContractError::new(&format!("Could not convert {option_kind} to an OptionKind (Call/Put)"))) + } +} +impl ToString for OptionKind { + fn to_string(&self) -> String { + match &self { + Self::Call => "Call".to_string(), + Self::Put => "Put".to_string() + } + } +} + + +pub struct OptionContract { + pub symbol: String, + pub underlying: String, + pub exchange: Option, + pub kind: OptionKind, + pub strike_price: f64, + pub expiration: i64, // mls since epoch + pub dte: u32, // days to expiration time of quote + pub bid: f64, + pub mid: f64, + pub ask: f64, + pub last: Option, + pub bid_size: Option, + pub ask_size: Option, + pub volume: Option, + pub high: Option, + pub open: Option, + pub low: Option, + pub close: Option, + pub greeks: Option, + pub volatility: Option +} +impl OptionContract { + pub fn expiration_as_date(&self) -> LocalResult> { + chrono::Utc.timestamp_millis_opt(self.expiration) + } +} + + +pub trait ToOptionContract { + fn to_option_contract(&self) -> DynResult; +} + +pub struct Greeks { + delta: Option, + gamma: Option, + theta: Option, + vega: Option, + rho: Option, +} + + + +pub type OptionChainDataFrame = DataFrame; +pub fn create_optionchain_dataframe(options: Vec) -> DynResult { + let mut symbols = vec![]; + let mut underlyings = vec![] ; + let mut exchanges = vec![] ; + let mut kinds = vec![] ; + let mut strike_prices = vec![] ; + let mut expirations = vec![] ; + let mut dtes = vec![] ; + let mut bids = vec![] ; + let mut mids = vec![] ; + let mut asks = vec![] ; + let mut lasts = vec![] ; + let mut bid_sizes = vec![] ; + let mut ask_sizes = vec![] ; + let mut volumes = vec![] ; + let mut highs = vec![] ; + let mut opens = vec![] ; + let mut lows = vec![] ; + let mut closes = vec![] ; + let mut deltas = vec![]; + let mut gammas = vec![] ; + let mut thetas = vec![] ; + let mut vegas = vec![] ; + let mut rhos = vec![] ; + let mut volatilitys = vec![] ; + for op in options { + symbols.push(op.symbol); + underlyings.push(op.underlying); + exchanges.push(op.exchange); + kinds.push(op.kind.to_string()); + strike_prices.push(op.strike_price); + expirations.push(op.expiration); + dtes.push(op.dte); + bids.push(op.bid); + mids.push(op.mid); + asks.push(op.ask); + lasts.push(op.last); + bid_sizes.push(op.bid_size); + ask_sizes.push(op.ask_size); + volumes.push(op.volume); + highs.push(op.high); + opens.push(op.open); + lows.push(op.low); + closes.push(op.close); + deltas.push(match &op.greeks { + Some(greeks) => greeks.delta, + None => None + }); + deltas.push(match &op.greeks { + Some(greeks) => greeks.delta, + None => None + }); + gammas.push(match &op.greeks { + Some(greeks) => greeks.gamma, + None => None + }); + thetas.push(match &op.greeks { + Some(greeks) => greeks.theta, + None => None + }); + vegas.push(match &op.greeks { + Some(greeks) => greeks.vega, + None => None + }); + rhos.push(match &op.greeks { + Some(greeks) => greeks.rho, + None => None + }); + volatilitys.push(op.volatility); + } + Ok(df!( + "Symbol" => &symbols, + "Underlying" => &underlyings, + "Exchange" => &exchanges, + "Kind" => &kinds, + "StrikePrice" => &strike_prices, + "Expiration" => &expirations, + "DaysToExpiry" => &dtes, + "Bid" => &bids, + "Mid" => &mids, + "Ask" => &asks, + "Last" => &lasts, + "BidSize" => &bid_sizes, + "AskSize" => &ask_sizes, + "Volume" => &volumes, + "High" => &highs, + "Open" => &opens, + "Low" => &lows, + "Close" => &closes, + "Delta" => &deltas, + "Gamma" => &gammas, + "Theta" => &thetas, + "Vegas" => &vegas, + "Rho" => &rhos, + "Volatility" => &volatilitys, + + )?) +} \ No newline at end of file diff --git a/src/data/price_history.rs b/src/data/price_history.rs new file mode 100644 index 0000000..7f6ebf3 --- /dev/null +++ b/src/data/price_history.rs @@ -0,0 +1,56 @@ +use polars::{prelude::DataFrame, df, prelude::{NamedFrom}}; + +use crate::DynResult; + + + +pub struct Candle { + pub high: f64, + pub open: f64, + pub low: f64, + pub close: f64, + pub volume: Option, + pub timestamp: i64 // mls since epoch +} +impl Candle { + pub fn new(high: f64, open: f64, low: f64, close: f64, volume: Option, timestamp: i64) -> Self { + Candle { high, open, low, close, volume, timestamp } + } +} + +pub struct PriceHistory { + symbol: String, + candles: Vec +} + +pub trait ToPriceHistory { + fn to_pricehistorty(&self) -> DynResult; +} + +pub fn create_timeseries_dataframe(timeseries_data: &Vec) -> DynResult { + let mut high = vec![]; + let mut open = vec![]; + let mut low = vec![]; + let mut close = vec![]; + let mut volume = vec![]; + let mut timestamp = vec![]; + + for candle in timeseries_data { + high.push(candle.high); + open.push(candle.open); + low.push(candle.low); + close.push(candle.close); + volume.push(candle.volume); + timestamp.push(candle.timestamp); + } + + Ok(df!( + "High" => &high, + "Open" => &open, + "Low" => &low, + "Close"=> &close, + "Volume"=> &volume, + "TimeStamp" => ×tamp + )?) + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8bcc6f0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +mod data; + +pub type DynResult = Result>; + + +#[cfg(test)] +mod tests { + use super::*; + + +}