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
154
155
156
157
158
159
160
161
162
#[derive(Debug, Clone, Default)]
/// Describes the position information of the account
pub struct Position {
    /// The position size denoted in QUOTE currency
    size: f64,
    /// The value of the position, denoted in BASE currency
    value: f64,
    /// The entry price of the position
    entry_price: f64,
    /// The current position leverage
    leverage: f64,
    /// The currently unrealized profit and loss, denoted in BASE currency
    unrealized_pnl: f64,
}

impl Position {
    /// Create a new position with a given leverage
    pub fn new(leverage: f64) -> Self {
        Position {
            size: 0.0,
            value: 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,
        value: f64,
        entry_price: f64,
        leverage: f64,
        unrealized_pnl: f64,
    ) -> Self {
        Position {
            size,
            value,
            entry_price,
            leverage,
            unrealized_pnl,
        }
    }

    /// Change the position size by a given delta, denoted in QUOTE currency at a given price
    pub(crate) fn change_size(&mut self, size_delta: f64, price: f64) {
        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);
    }

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

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

    /// Return the position value denoted in BASE currency
    pub fn value(&self) -> f64 {
        self.value
    }

    /// 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, denoted in BASE currency
    pub fn unrealized_pnl(&self) -> f64 {
        self.unrealized_pnl
    }
}

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

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

        pos.change_size(100.0, 100.0);
        assert_eq!(pos.size, 100.0);
        assert_eq!(pos.value, 1.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);
        assert_eq!(pos.size, 50.0);
        assert_eq!(round(pos.value, 2), 0.33);
        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);
        assert_eq!(pos.size, 100.0);
        assert_eq!(round(pos.value, 2), 0.67);
        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);
        assert_eq!(pos.size, -50.0);
        assert_eq!(round(pos.value, 2), 0.33);
        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);
        assert_eq!(pos.size, 0.0);
        assert_eq!(pos.value, 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, 1.0, 100.0, 1.0, 0.0);
        pos.update_state(110.0);

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