barter_execution/model/
balance.rs

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
use barter_integration::model::instrument::symbol::Symbol;
use serde::{Deserialize, Serialize};

/// [`Balance`] associated with a [`Symbol`].
#[derive(Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
pub struct SymbolBalance {
    pub symbol: Symbol,
    pub balance: Balance,
}

impl SymbolBalance {
    /// Construct a new [`SymbolBalance`] from a [`Symbol`] and it's associated [`Balance`].
    pub fn new<S>(symbol: S, balance: Balance) -> Self
    where
        S: Into<Symbol>,
    {
        Self {
            symbol: symbol.into(),
            balance,
        }
    }
}

/// Total and available balance values.
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
pub struct Balance {
    pub total: f64,
    pub available: f64,
}

impl Balance {
    /// Construct a new [`Balance`].
    pub fn new(total: f64, available: f64) -> Self {
        Self { total, available }
    }

    /// Calculate the used (`total` - `available`) balance.
    pub fn used(&self) -> f64 {
        self.total - self.available
    }

    /// Apply a [`BalanceDelta`] to this [`Balance`].
    pub fn apply(&mut self, delta: BalanceDelta) {
        self.total += delta.total;
        self.available += delta.available;
    }
}

/// Communicates a change to be applied to a [`Balance`];
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Deserialize, Serialize)]
pub struct BalanceDelta {
    pub total: f64,
    pub available: f64,
}

impl BalanceDelta {
    /// Construct a new [`BalanceDelta`].
    pub fn new(total: f64, available: f64) -> Self {
        Self { total, available }
    }
}

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

    #[test]
    fn test_balance_used() {
        // No Balance is used
        let balance = Balance::new(10.0, 10.0);
        assert_eq!(balance.used(), 0.0);

        // All Balance is used
        let balance = Balance::new(10.0, 0.0);
        assert_eq!(balance.used(), balance.total);

        // Half Balance is used
        let balance = Balance::new(10.0, 5.0);
        assert_eq!(balance.used(), balance.available);
    }

    #[test]
    fn test_balance_apply_balance_delta() {
        struct TestCase {
            balance: Balance,
            input_delta: BalanceDelta,
            expected: Balance,
        }

        let tests = vec![
            TestCase {
                // TC0: Delta applies a negative total delta only
                balance: Balance::new(10.0, 0.0),
                input_delta: BalanceDelta::new(-10.0, 0.0),
                expected: Balance::new(0.0, 0.0),
            },
            TestCase {
                // TC1: Delta applies a negative available delta only
                balance: Balance::new(10.0, 10.0),
                input_delta: BalanceDelta::new(0.0, -10.0),
                expected: Balance::new(10.0, 0.0),
            },
            TestCase {
                // TC2: Delta applies a positive available delta only
                balance: Balance::new(10.0, 10.0),
                input_delta: BalanceDelta::new(0.0, 10.0),
                expected: Balance::new(10.0, 20.0),
            },
            TestCase {
                // TC3: Delta applies a positive available delta only
                balance: Balance::new(10.0, 10.0),
                input_delta: BalanceDelta::new(0.0, 10.0),
                expected: Balance::new(10.0, 20.0),
            },
            TestCase {
                // TC4: Delta applies a positive total & available delta
                balance: Balance::new(10.0, 10.0),
                input_delta: BalanceDelta::new(10.0, 10.0),
                expected: Balance::new(20.0, 20.0),
            },
            TestCase {
                // TC5: Delta applies a negative total & available delta
                balance: Balance::new(10.0, 10.0),
                input_delta: BalanceDelta::new(-10.0, -10.0),
                expected: Balance::new(0.0, 0.0),
            },
        ];

        for (index, mut test) in tests.into_iter().enumerate() {
            test.balance.apply(test.input_delta);
            assert_eq!(test.balance, test.expected, "TC{} failed", index);
        }
    }
}