use pine_core::Bar;
mod broker;
mod fill;
pub use broker::BarBroker;
pub use fill::{FillModel, PineFills};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Long,
Short,
}
impl Direction {
pub fn sign(self) -> f64 {
match self {
Direction::Long => 1.0,
Direction::Short => -1.0,
}
}
}
impl From<&str> for Direction {
fn from(tag: &str) -> Self {
if tag == "short" {
Direction::Short
} else {
Direction::Long
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OrderKind {
Market,
Limit(f64),
Stop(f64),
StopLimit { stop: f64, limit: f64 },
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Commission {
Percent(f64),
CashPerContract(f64),
CashPerOrder(f64),
}
impl Commission {
fn charge(self, qty: f64, price: f64) -> f64 {
match self {
Commission::Percent(pct) => qty.abs() * price * pct / 100.0,
Commission::CashPerContract(cash) => qty.abs() * cash,
Commission::CashPerOrder(cash) => cash,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Sizing {
Contracts(f64),
Cash(f64),
PercentOfEquity(f64),
}
impl Sizing {
fn contracts(self, price: f64, equity: f64) -> f64 {
match self {
Sizing::Contracts(c) => c,
Sizing::Cash(cash) if price > 0.0 => cash / price,
Sizing::PercentOfEquity(pct) if price > 0.0 => (pct / 100.0 * equity) / price,
_ => 0.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OcaType {
#[default]
None,
Cancel,
Reduce,
}
impl From<&str> for OcaType {
fn from(tag: &str) -> Self {
match tag {
"cancel" => OcaType::Cancel,
"reduce" => OcaType::Reduce,
_ => OcaType::None,
}
}
}
#[derive(Debug, Clone)]
pub struct Order {
pub id: String,
pub direction: Direction,
pub qty: Option<f64>,
pub qty_percent: Option<f64>,
pub sizing_price: Option<f64>,
pub kind: OrderKind,
pub reduce_only: bool,
pub reverses: bool,
pub close_target: Option<String>,
pub oca_name: Option<String>,
pub oca_type: OcaType,
pub comment: String,
}
impl Order {
pub fn market(id: impl Into<String>, direction: Direction, qty: Option<f64>) -> Self {
Self {
id: id.into(),
direction,
qty,
qty_percent: None,
sizing_price: None,
kind: OrderKind::Market,
reduce_only: false,
reverses: true,
close_target: None,
oca_name: None,
oca_type: OcaType::None,
comment: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct Exit {
pub id: String,
pub from_entry: Option<String>,
pub qty: Option<f64>,
pub qty_percent: Option<f64>,
pub limit: Option<f64>,
pub profit_ticks: Option<f64>,
pub stop: Option<f64>,
pub loss_ticks: Option<f64>,
pub trail_price: Option<f64>,
pub trail_points: Option<f64>,
pub trail_offset: Option<f64>,
pub activated: bool,
pub peak: Option<f64>,
}
impl Exit {
pub fn resting(
id: impl Into<String>,
from_entry: Option<String>,
qty: Option<f64>,
qty_percent: Option<f64>,
) -> Self {
Self {
id: id.into(),
from_entry,
qty,
qty_percent,
limit: None,
profit_ticks: None,
stop: None,
loss_ticks: None,
trail_price: None,
trail_points: None,
trail_offset: None,
activated: false,
peak: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Trade {
pub entry_id: String,
pub size: f64,
pub entry_price: f64,
pub entry_bar: u64,
pub exit_price: Option<f64>,
pub exit_bar: Option<u64>,
pub commission: f64,
}
impl Trade {
pub fn profit(&self, price: f64) -> f64 {
let exit = self.exit_price.unwrap_or(price);
(exit - self.entry_price) * self.size - self.commission
}
pub fn is_open(&self) -> bool {
self.exit_price.is_none()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Position {
pub size: f64,
pub avg_price: f64,
}
impl Position {
pub fn is_flat(&self) -> bool {
self.size == 0.0
}
}
pub trait Broker {
fn submit(&mut self, order: Order);
fn submit_exit(&mut self, exit: Exit);
fn cancel(&mut self, id: &str);
fn cancel_all(&mut self);
fn advance(&mut self, bar: &Bar);
fn position(&self) -> Position;
fn initial_capital(&self) -> f64;
fn equity(&self, price: f64) -> f64;
fn open_trades(&self) -> Vec<&Trade>;
fn closed_trades(&self) -> &[Trade];
}