1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use crate::FuturesType;

#[derive(Debug, Clone, Default)]
/// Describes the position information of the account
pub struct Position {
    /// The position size
    size: f64,
    /// The entry price of the position
    entry_price: f64,
    /// The current position leverage
    leverage: f64,
    /// The currently unrealized profit and loss
    unrealized_pnl: f64,
}

impl Position {
    /// Create a new position with a given leverage
    pub fn new(leverage: f64) -> Self {
      debug_assert!(leverage > 0.0);
        Position {
            size: 0.0,
            entry_price: 0.0,
            leverage,
            unrealized_pnl: 0.0,
        }
    }

    /// Create a new position with all fields custom.
    /// NOTE: only for advanced use cases
    pub fn new_all_fields(
        size: f64,
        entry_price: f64,
        leverage: f64,
        unrealized_pnl: f64,
    ) -> Self {
      debug_assert!(size.is_finite());
      debug_assert!(entry_price.is_finite());
      debug_assert!(leverage.is_finite());
      debug_assert!(unrealized_pnl.is_finite());
        Position {
            size,
            entry_price,
            leverage,
            unrealized_pnl,
        }
    }

    /// Change the position size by a given delta at a certain price
    pub(crate) fn change_size(&mut self, size_delta: f64, price: f64, futures_type: FuturesType) {
        if self.size > 0.0 {
            if self.size + size_delta < 0.0 {
                // counts as new position as all old position size is sold
                self.entry_price = price;
            } else if (self.size + size_delta).abs() > self.size {
                self.entry_price = ((self.size.abs() * self.entry_price)
                    + (size_delta.abs() * price))
                    / (self.size.abs() + size_delta.abs());
            }
        } else if self.size < 0.0 {
            if self.size + size_delta > 0.0 {
                self.entry_price = price;
            } else if self.size + size_delta < self.size {
                self.entry_price = ((self.size.abs() * self.entry_price)
                    + (size_delta.abs() * price))
                    / (self.size.abs() + size_delta.abs());
            }
        } else {
            self.entry_price = price;
        }
        self.size += size_delta;

        self.update_state(price, futures_type);
    }

    /// Update the state to reflect price changes
    pub(crate) fn update_state(&mut self, price: f64, futures_type: FuturesType) {
        self.unrealized_pnl = if self.size != 0.0 {
          futures_type.pnl(self.entry_price, price, self.size)
        } else {
            0.0
        };
    }

    /// Return the position size
    pub fn size(&self) -> f64 {
        self.size
    }

    /// Return the entry price of the position
    pub fn entry_price(&self) -> f64 {
        self.entry_price
    }

    /// Return the positions leverage
    pub fn leverage(&self) -> f64 {
        self.leverage
    }

    /// Return the positions unrealized profit and loss
    pub fn unrealized_pnl(&self) -> f64 {
        self.unrealized_pnl
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::round;

    #[test]
    fn position_change_size() {
        let mut pos = Position::new(1.0);
        let futures_type = FuturesType::Inverse;

        pos.change_size(100.0, 100.0, futures_type);
        assert_eq!(pos.size, 100.0);
        assert_eq!(pos.entry_price, 100.0);
        assert_eq!(pos.leverage, 1.0);
        assert_eq!(pos.unrealized_pnl, 0.0);

        pos.change_size(-50.0, 150.0, futures_type);
        assert_eq!(pos.size, 50.0);
        assert_eq!(pos.entry_price, 100.0);
        assert_eq!(pos.leverage, 1.0);
        assert_eq!(round(pos.unrealized_pnl, 2), 0.17);

        pos.change_size(50.0, 150.0, futures_type);
        assert_eq!(pos.size, 100.0);
        assert_eq!(pos.entry_price, 125.0);
        assert_eq!(pos.leverage, 1.0);
        assert_eq!(round(pos.unrealized_pnl, 2), 0.13);

        pos.change_size(-150.0, 150.0, futures_type);
        assert_eq!(pos.size, -50.0);
        assert_eq!(pos.entry_price, 150.0);
        assert_eq!(pos.leverage, 1.0);
        assert_eq!(pos.unrealized_pnl, 0.0);

        pos.change_size(50.0, 150.0, futures_type);
        assert_eq!(pos.size, 0.0);
        assert_eq!(pos.entry_price, 150.0);
        assert_eq!(pos.leverage, 1.0);
        assert_eq!(pos.unrealized_pnl, 0.0);
    }

    #[test]
    fn position_update_state() {
        let mut pos = Position::new_all_fields(100.0, 100.0, 1.0, 0.0);
        pos.update_state(110.0, FuturesType::Inverse);

        assert_eq!(round(pos.unrealized_pnl, 2), 0.09);
    }
}