Files
foxhunt/testing/test-common/src/builders/position_builder.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

150 lines
3.4 KiB
Rust

//! Position builder for testing
use chrono::{DateTime, Utc};
/// Mock position for testing
#[derive(Clone, Debug)]
pub struct MockPosition {
pub symbol: String,
pub quantity: f64,
pub entry_price: f64,
pub current_price: f64,
pub timestamp: DateTime<Utc>,
}
impl MockPosition {
pub fn pnl(&self) -> f64 {
(self.current_price - self.entry_price) * self.quantity
}
pub fn pnl_percent(&self) -> f64 {
((self.current_price - self.entry_price) / self.entry_price) * 100.0
}
}
/// Builder for creating mock positions
pub struct PositionBuilder {
symbol: String,
quantity: f64,
entry_price: f64,
current_price: f64,
timestamp: DateTime<Utc>,
}
impl PositionBuilder {
pub fn new() -> Self {
Self {
symbol: "AAPL".to_string(),
quantity: 100.0,
entry_price: 150.0,
current_price: 150.0,
timestamp: Utc::now(),
}
}
pub fn symbol(mut self, symbol: &str) -> Self {
self.symbol = symbol.to_string();
self
}
pub fn long(mut self, quantity: f64) -> Self {
self.quantity = quantity.abs();
self
}
pub fn short(mut self, quantity: f64) -> Self {
self.quantity = -quantity.abs();
self
}
pub fn entry_price(mut self, price: f64) -> Self {
self.entry_price = price;
self
}
pub fn current_price(mut self, price: f64) -> Self {
self.current_price = price;
self
}
pub fn profitable(mut self, percent: f64) -> Self {
self.current_price = self.entry_price * (1.0 + percent / 100.0);
self
}
pub fn losing(mut self, percent: f64) -> Self {
self.current_price = self.entry_price * (1.0 - percent / 100.0);
self
}
pub fn build(self) -> MockPosition {
MockPosition {
symbol: self.symbol,
quantity: self.quantity,
entry_price: self.entry_price,
current_price: self.current_price,
timestamp: self.timestamp,
}
}
}
impl Default for PositionBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_builder() {
let pos = PositionBuilder::new()
.symbol("TSLA")
.long(50.0)
.entry_price(200.0)
.current_price(220.0)
.build();
assert_eq!(pos.symbol, "TSLA");
assert_eq!(pos.quantity, 50.0);
assert_eq!(pos.pnl(), 1000.0); // (220 - 200) * 50
assert_eq!(pos.pnl_percent(), 10.0);
}
#[test]
fn test_short_position() {
let pos = PositionBuilder::new()
.short(100.0)
.entry_price(150.0)
.current_price(140.0)
.build();
assert!(pos.quantity < 0.0);
assert_eq!(pos.pnl(), -1000.0); // (140 - 150) * -100
}
#[test]
fn test_profitable_position() {
let pos = PositionBuilder::new()
.entry_price(100.0)
.profitable(10.0)
.build();
assert_eq!(pos.current_price, 110.0);
assert_eq!(pos.pnl_percent(), 10.0);
}
#[test]
fn test_losing_position() {
let pos = PositionBuilder::new()
.entry_price(100.0)
.losing(5.0)
.build();
assert_eq!(pos.current_price, 95.0);
assert_eq!(pos.pnl_percent(), -5.0);
}
}