use crate::is_valid_price;
#[derive(Debug, Clone)]
pub struct Position {
pub value: f64,
pub entry_price: f64,
pub stop_entry_price: f64,
pub max_price: f64,
pub last_market_value: f64,
pub cr: f64,
pub maxcr: f64,
pub previous_price: f64,
}
impl Position {
pub fn new(value: f64, price: f64) -> Self {
Self {
value,
entry_price: price,
stop_entry_price: price,
max_price: price,
last_market_value: value,
cr: 1.0,
maxcr: 1.0,
previous_price: price,
}
}
pub fn new_with_nan_price(value: f64) -> Self {
Self {
value,
entry_price: 0.0, stop_entry_price: 0.0,
max_price: 0.0,
last_market_value: value, cr: 1.0,
maxcr: 1.0,
previous_price: 0.0,
}
}
pub fn update_with_return(&mut self, current_price: f64) {
if !is_valid_price(current_price) {
return;
}
if current_price > self.max_price {
self.max_price = current_price;
}
if self.previous_price > 0.0 {
let r = current_price / self.previous_price;
self.cr *= r;
self.last_market_value *= r; }
self.maxcr = self.maxcr.max(self.cr);
}
pub fn update_previous_price(&mut self, price: f64) {
if is_valid_price(price) {
self.previous_price = price;
}
}
pub fn is_long(&self) -> bool {
self.last_market_value >= 0.0
}
pub fn reset_stop_tracking(&mut self, price: f64) {
self.stop_entry_price = price;
self.max_price = price;
self.cr = 1.0;
self.maxcr = 1.0;
self.previous_price = price;
}
pub fn new_from_snapshot(new_value: f64, current_price: f64, snapshot: &PositionSnapshot) -> Self {
Self {
value: new_value,
entry_price: current_price,
stop_entry_price: snapshot.stop_entry_price,
max_price: snapshot.max_price,
last_market_value: new_value,
cr: snapshot.cr,
maxcr: snapshot.maxcr,
previous_price: snapshot.previous_price,
}
}
}
impl Default for Position {
fn default() -> Self {
Self {
value: 0.0,
entry_price: 0.0,
stop_entry_price: 0.0,
max_price: 0.0,
last_market_value: 0.0,
cr: 1.0,
maxcr: 1.0,
previous_price: 0.0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PositionSnapshot {
pub cost_basis: f64,
pub market_value: f64,
pub stop_entry_price: f64,
pub max_price: f64,
pub cr: f64,
pub maxcr: f64,
pub previous_price: f64,
}
impl From<&Position> for PositionSnapshot {
fn from(pos: &Position) -> Self {
Self {
cost_basis: pos.value,
market_value: pos.last_market_value,
stop_entry_price: pos.stop_entry_price,
max_price: pos.max_price,
cr: pos.cr,
maxcr: pos.maxcr,
previous_price: pos.previous_price,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_position() {
let pos = Position::new(1000.0, 100.0);
assert!((pos.value - 1000.0).abs() < 1e-10);
assert!((pos.entry_price - 100.0).abs() < 1e-10);
assert!((pos.stop_entry_price - 100.0).abs() < 1e-10);
assert!((pos.max_price - 100.0).abs() < 1e-10);
assert!((pos.cr - 1.0).abs() < 1e-10);
assert!((pos.maxcr - 1.0).abs() < 1e-10);
}
#[test]
fn test_update_with_return() {
let mut pos = Position::new(1000.0, 100.0);
pos.update_with_return(110.0);
assert!((pos.cr - 1.1).abs() < 1e-10);
assert!((pos.maxcr - 1.1).abs() < 1e-10);
assert!((pos.last_market_value - 1100.0).abs() < 1e-10);
assert!((pos.max_price - 110.0).abs() < 1e-10);
}
#[test]
fn test_update_with_return_down() {
let mut pos = Position::new(1000.0, 100.0);
pos.update_with_return(110.0);
pos.update_previous_price(110.0);
pos.update_with_return(99.0);
assert!((pos.cr - 0.99).abs() < 1e-10);
assert!((pos.maxcr - 1.1).abs() < 1e-10);
assert!((pos.max_price - 110.0).abs() < 1e-10);
}
#[test]
fn test_is_long() {
let pos = Position::new(1000.0, 100.0);
assert!(pos.is_long());
let short_pos = Position {
last_market_value: -1000.0,
..Default::default()
};
assert!(!short_pos.is_long());
}
#[test]
fn test_reset_stop_tracking() {
let mut pos = Position::new(1000.0, 100.0);
pos.update_with_return(120.0);
pos.update_previous_price(120.0);
pos.reset_stop_tracking(115.0);
assert!((pos.stop_entry_price - 115.0).abs() < 1e-10);
assert!((pos.max_price - 115.0).abs() < 1e-10);
assert!((pos.cr - 1.0).abs() < 1e-10);
assert!((pos.maxcr - 1.0).abs() < 1e-10);
assert!((pos.previous_price - 115.0).abs() < 1e-10);
}
}