positions/
position.rs

1use crate::{
2    instrument::{Instrument, Symbol},
3    tree::PositionTree,
4    Asset, HashMap, IntoNaivePosition, NaivePosition, PositionNum, Reversed,
5};
6use alloc::fmt;
7use core::ops::{Add, AddAssign, Deref, Neg, SubAssign};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// Position.
13#[derive(Debug, Clone)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15pub struct Position<T> {
16    instrument: Instrument,
17    #[cfg_attr(feature = "serde", serde(flatten))]
18    naive: NaivePosition<T>,
19}
20
21impl<T> Position<T> {
22    /// Get the instrument.
23    pub fn instrument(&self) -> &Instrument {
24        &self.instrument
25    }
26
27    /// Convert to a [`NaivePosition`]
28    pub fn as_naive(&self) -> &NaivePosition<T> {
29        &self.naive
30    }
31
32    /// Get the value of the position.
33    pub fn value(&self) -> &T {
34        &self.naive.value
35    }
36}
37
38impl<T> Position<T>
39where
40    T: PositionNum,
41{
42    /// Create a new position.
43    pub fn new(instrument: Instrument, position: impl IntoNaivePosition<T>) -> Self {
44        Self {
45            instrument,
46            naive: position.into_naive(),
47        }
48    }
49
50    /// Return the value when the position is closed at the given price.
51    /// # Warning
52    /// This method will respect the reversed-preference,
53    /// so if you want to close a position of a "reversed instrument",
54    /// you should provide the price with "reversed form".
55    pub fn closed(&self, price: &T) -> T {
56        let mut p = self.naive.clone();
57        if self.instrument.is_prefer_reversed() {
58            p -= Reversed((price.clone(), self.size()));
59        } else {
60            p -= (price.clone(), self.size());
61        }
62        p.value
63    }
64
65    /// Get the average price of the position,
66    /// respecting the reversed preference of its instrument.
67    pub fn price(&self) -> Option<T> {
68        if self.instrument.is_prefer_reversed() {
69            if self.naive.price.is_zero() {
70                None
71            } else {
72                Some({
73                    let mut v = T::one();
74                    v /= &self.naive.price;
75                    v
76                })
77            }
78        } else {
79            Some(self.naive.price.clone())
80        }
81    }
82
83    /// Get the size of the position,
84    /// respecting the reversed preference of its instrument.
85    pub fn size(&self) -> T {
86        if self.instrument.is_prefer_reversed() {
87            self.naive.size.clone().neg()
88        } else {
89            self.naive.size.clone()
90        }
91    }
92
93    /// Calculate the notional value of the position.
94    /// Note that the notional value of a short position will be negative.
95    pub fn notional_value(&self) -> T {
96        let mut value = self.naive.price.clone();
97        value *= &self.naive.size;
98        value
99    }
100
101    /// Merge with the other position.
102    /// After merging, the `other` will be the default ("zero") position.
103    /// # Warning
104    /// No-OP if the other position has different `instrument`.
105    pub fn merge(&mut self, other: &mut Self) {
106        if other.instrument == self.instrument {
107            let rhs = core::mem::take(&mut other.naive);
108            self.naive += rhs;
109            debug_assert!(other.is_zero());
110        }
111    }
112
113    /// Take the value of the position.
114    #[inline]
115    pub fn take(&mut self) -> T {
116        self.naive.take()
117    }
118
119    /// Convert the price to the given.
120    /// # Warning
121    /// The `to` price is treated to be in the reversed-form
122    /// if the `instrument` is reversed-prefering.
123    /// # Panic
124    /// Panic if `to` is in the reversed-form and is zero.
125    pub fn convert(&mut self, to: T) {
126        let to = if self.instrument.is_prefer_reversed() {
127            if to.is_zero() {
128                panic!("the price in reversed-form cannot be zero");
129            }
130            T::one() / to
131        } else {
132            to
133        };
134        self.naive.convert(to);
135    }
136
137    /// Is this a zero position whose `size` and `value` are both zero.
138    pub fn is_zero(&self) -> bool {
139        self.naive.size.is_zero() && self.naive.value.is_zero()
140    }
141
142    /// Convert to a position tree.
143    pub fn as_tree(&self) -> PositionTree<'_, T> {
144        PositionTree {
145            asset: self.instrument.quote(),
146            value: T::zero(),
147            positions: HashMap::from([(&self.instrument, self)]),
148            children: HashMap::default(),
149        }
150    }
151}
152
153impl<'a, T: PositionNum> IntoNaivePosition<T> for &'a Position<T> {
154    fn into_naive(self) -> NaivePosition<T> {
155        self.naive.clone()
156    }
157}
158
159impl<T> fmt::Display for Position<T>
160where
161    T: PositionNum + fmt::Display,
162{
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        let base = self.instrument.base();
165        let mark = if self.instrument.is_prefer_reversed() {
166            "*"
167        } else {
168            ""
169        };
170        if let Some(price) = self.price() {
171            write!(f, "({price}, {} {base}){mark}", self.size(),)?;
172        } else {
173            write!(f, "(Nan, {} {base}){mark}", self.size(),)?;
174        }
175        let value = self.value();
176        if !value.is_zero() {
177            let sign = if value.is_negative() { " - " } else { " + " };
178            write!(f, "{sign}{} {}", value.abs(), self.instrument.quote())?;
179        }
180        Ok(())
181    }
182}
183
184impl<T> PartialEq for Position<T>
185where
186    T: PositionNum,
187{
188    fn eq(&self, other: &Self) -> bool {
189        self.instrument == other.instrument && self.naive == other.naive
190    }
191}
192
193impl<T> Eq for Position<T> where T: PositionNum {}
194
195impl<T, P> AddAssign<P> for Position<T>
196where
197    T: PositionNum,
198    P: IntoNaivePosition<T>,
199{
200    fn add_assign(&mut self, rhs: P) {
201        self.naive += rhs;
202    }
203}
204
205impl<T, P> SubAssign<P> for Position<T>
206where
207    T: PositionNum,
208    P: IntoNaivePosition<T>,
209{
210    fn sub_assign(&mut self, rhs: P) {
211        self.naive -= rhs;
212    }
213}
214
215impl<T> Neg for Position<T>
216where
217    T: PositionNum,
218{
219    type Output = Self;
220
221    fn neg(self) -> Self::Output {
222        Self {
223            instrument: self.instrument.clone(),
224            naive: self.naive.neg(),
225        }
226    }
227}
228
229impl<T> AsRef<Position<T>> for Position<T> {
230    fn as_ref(&self) -> &Position<T> {
231        self
232    }
233}
234
235/// Single Value Positions.
236#[derive(Debug, Clone)]
237#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
238pub struct SingleValue<T> {
239    value: T,
240    positions: HashMap<Symbol, Position<T>>,
241}
242
243impl<T> Default for SingleValue<T>
244where
245    T: PositionNum,
246{
247    fn default() -> Self {
248        Self {
249            value: T::zero(),
250            positions: HashMap::default(),
251        }
252    }
253}
254
255impl<T> SingleValue<T> {
256    /// Get `value`.
257    pub fn value(&self) -> &T {
258        &self.value
259    }
260
261    /// Create an iterator of the positions.
262    #[inline]
263    pub fn iter(&self) -> impl Iterator<Item = (&Symbol, &Position<T>)> {
264        self.positions.iter()
265    }
266
267    /// Get the number of [`Position`]s.
268    #[inline]
269    pub fn len(&self) -> usize {
270        self.positions.len()
271    }
272
273    /// Is empty.
274    #[inline]
275    pub fn is_empty(&self) -> bool {
276        self.positions.is_empty()
277    }
278}
279
280impl<T> IntoIterator for SingleValue<T> {
281    type Item = (Symbol, Position<T>);
282
283    type IntoIter = <HashMap<Symbol, Position<T>> as IntoIterator>::IntoIter;
284
285    #[inline]
286    fn into_iter(self) -> Self::IntoIter {
287        self.positions.into_iter()
288    }
289}
290
291impl<T> SingleValue<T>
292where
293    T: PositionNum,
294{
295    fn insert(&mut self, position: Position<T>) {
296        if let Some(p) = self.positions.get_mut(position.instrument.as_symbol()) {
297            debug_assert_eq!(p.instrument, position.instrument);
298            p.naive += position.naive;
299        } else {
300            self.positions
301                .insert(position.instrument.as_symbol().clone(), position);
302        }
303    }
304
305    fn concentrate(&mut self) {
306        let value = self
307            .positions
308            .values_mut()
309            .map(|p| p.take())
310            .fold(T::zero(), T::add);
311        self.value += value;
312    }
313}
314
315impl<T> AddAssign<&Self> for SingleValue<T>
316where
317    T: PositionNum,
318{
319    fn add_assign(&mut self, rhs: &Self) {
320        self.value += &rhs.value;
321        for (inst, rhs) in rhs.positions.iter() {
322            if let Some(lhs) = self.positions.get_mut(inst) {
323                debug_assert_eq!(lhs.instrument, rhs.instrument);
324                lhs.naive += rhs.naive.clone();
325            } else {
326                self.positions.insert(inst.clone(), rhs.clone());
327            }
328        }
329    }
330}
331
332impl<T> PartialEq for SingleValue<T>
333where
334    T: PositionNum,
335{
336    fn eq(&self, other: &Self) -> bool {
337        self.value == other.value && self.positions == other.positions
338    }
339}
340
341impl<T> Eq for SingleValue<T> where T: PositionNum {}
342
343/// A table of positions.
344#[derive(Debug, Clone)]
345#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
346pub struct Positions<T> {
347    values: HashMap<Asset, SingleValue<T>>,
348}
349
350impl<T> Default for Positions<T> {
351    fn default() -> Self {
352        Self {
353            values: Default::default(),
354        }
355    }
356}
357
358impl<T> Positions<T> {
359    /// Create an iterator of [`SingleValue`]s.
360    #[inline]
361    pub fn iter(&self) -> impl Iterator<Item = (&Asset, &SingleValue<T>)> {
362        self.values.iter()
363    }
364
365    /// Get the number of [`SingleValue`]s.
366    #[inline]
367    pub fn len(&self) -> usize {
368        self.values.len()
369    }
370
371    /// Is empty.
372    #[inline]
373    pub fn is_empty(&self) -> bool {
374        self.values.is_empty()
375    }
376}
377
378impl<T> Positions<T>
379where
380    T: PositionNum,
381{
382    /// Convert to a positions expression.
383    pub fn as_expr(&self) -> Expr<'_, T> {
384        Expr::new(self)
385    }
386
387    /// Convert to a position tree, using the given asset as root.
388    pub fn as_tree<'a>(&'a self, root: &'a Asset) -> PositionTree<'a, T> {
389        let children = self
390            .values
391            .iter()
392            .filter_map(|(asset, sv)| {
393                if *asset == *root {
394                    None
395                } else {
396                    let inst = Instrument::from((asset.clone(), root.clone()));
397                    Some((
398                        inst,
399                        PositionTree {
400                            asset,
401                            value: sv.value.clone(),
402                            positions: sv.positions.values().map(|p| (p.instrument(), p)).collect(),
403                            children: HashMap::default(),
404                        },
405                    ))
406                }
407            })
408            .collect();
409        if let Some(sv) = self.values.get(root) {
410            PositionTree {
411                asset: root,
412                value: sv.value.clone(),
413                positions: sv.positions.values().map(|p| (p.instrument(), p)).collect(),
414                children,
415            }
416        } else {
417            PositionTree {
418                asset: root,
419                value: T::zero(),
420                positions: HashMap::default(),
421                children,
422            }
423        }
424    }
425
426    /// Insert a position.
427    pub fn insert_position(&mut self, position: Position<T>) -> &mut Self {
428        self.values
429            .entry(position.instrument.quote().clone())
430            .or_default()
431            .insert(position);
432        self
433    }
434
435    /// Insert an value.
436    pub fn insert_value(&mut self, value: T, asset: &Asset) -> &mut Self {
437        if let Some(sv) = self.values.get_mut(asset) {
438            sv.value += value;
439        } else {
440            self.values.insert(
441                asset.clone(),
442                SingleValue {
443                    value,
444                    ..Default::default()
445                },
446            );
447        }
448        self
449    }
450
451    /// Get the reference of the position of the given instrument.
452    pub fn get_position(&self, instrument: &Instrument) -> Option<&Position<T>> {
453        self.values
454            .get(instrument.quote())?
455            .positions
456            .get(instrument.as_symbol())
457    }
458
459    /// Get the reference of the value of the given asset.
460    pub fn get_value(&self, asset: &Asset) -> Option<&T> {
461        Some(&self.values.get(asset)?.value)
462    }
463
464    /// Get the mutable reference of the position of the given instrument.
465    pub fn get_position_mut(&mut self, instrument: &Instrument) -> Option<&mut Position<T>> {
466        self.values
467            .get_mut(instrument.quote())?
468            .positions
469            .get_mut(instrument.as_symbol())
470    }
471
472    /// Get the mutable reference of the value of the given asset.
473    pub fn get_value_mut(&mut self, asset: &Asset) -> Option<&mut T> {
474        Some(&mut self.values.get_mut(asset)?.value)
475    }
476
477    /// Concentrate the values.
478    pub fn concentrate(&mut self) {
479        for sv in self.values.values_mut() {
480            sv.concentrate();
481        }
482    }
483}
484
485impl<T> IntoIterator for Positions<T> {
486    type Item = (Asset, SingleValue<T>);
487
488    type IntoIter = <HashMap<Asset, SingleValue<T>> as IntoIterator>::IntoIter;
489
490    #[inline]
491    fn into_iter(self) -> Self::IntoIter {
492        self.values.into_iter()
493    }
494}
495
496impl<T> PartialEq for Positions<T>
497where
498    T: PositionNum,
499{
500    fn eq(&self, other: &Self) -> bool {
501        self.values == other.values
502    }
503}
504
505impl<T> Eq for Positions<T> where T: PositionNum {}
506
507impl<T> AddAssign<&Self> for Positions<T>
508where
509    T: PositionNum,
510{
511    fn add_assign(&mut self, rhs: &Self) {
512        for (asset, rhs) in rhs.values.iter() {
513            if let Some(lhs) = self.values.get_mut(asset) {
514                *lhs += rhs;
515            } else {
516                self.values.insert(asset.clone(), rhs.clone());
517            }
518        }
519    }
520}
521
522impl<T> AddAssign for Positions<T>
523where
524    T: PositionNum,
525{
526    fn add_assign(&mut self, rhs: Self) {
527        *self += &rhs;
528    }
529}
530
531impl<T> Add for Positions<T>
532where
533    T: PositionNum,
534{
535    type Output = Self;
536
537    fn add(mut self, rhs: Self) -> Self::Output {
538        self += rhs;
539        self
540    }
541}
542
543impl<T> AddAssign<Position<T>> for Positions<T>
544where
545    T: PositionNum,
546{
547    fn add_assign(&mut self, rhs: Position<T>) {
548        self.insert_position(rhs);
549    }
550}
551
552impl<'a, T> AddAssign<(T, &'a Asset)> for Positions<T>
553where
554    T: PositionNum,
555{
556    fn add_assign(&mut self, (value, asset): (T, &'a Asset)) {
557        self.insert_value(value, asset);
558    }
559}
560
561impl<'a, T> AddAssign<(T, T, &'a Instrument)> for Positions<T>
562where
563    T: PositionNum,
564{
565    fn add_assign(&mut self, (price, size, instrument): (T, T, &'a Instrument)) {
566        self.insert_position(Position::new(instrument.clone(), (price, size)));
567    }
568}
569
570impl<'a, T> AddAssign<(T, T, T, &'a Instrument)> for Positions<T>
571where
572    T: PositionNum,
573{
574    fn add_assign(&mut self, (price, size, value, instrument): (T, T, T, &'a Instrument)) {
575        self.insert_position(Position::new(instrument.clone(), (price, size, value)));
576    }
577}
578
579impl<'a, T> AddAssign<Reversed<(T, T, &'a Instrument)>> for Positions<T>
580where
581    T: PositionNum,
582{
583    fn add_assign(
584        &mut self,
585        Reversed((price, size, instrument)): Reversed<(T, T, &'a Instrument)>,
586    ) {
587        self.insert_position(Position::new(instrument.clone(), Reversed((price, size))));
588    }
589}
590
591impl<'a, T> AddAssign<Reversed<(T, T, T, &'a Instrument)>> for Positions<T>
592where
593    T: PositionNum,
594{
595    fn add_assign(
596        &mut self,
597        Reversed((price, size, value, instrument)): Reversed<(T, T, T, &'a Instrument)>,
598    ) {
599        self.insert_position(Position::new(
600            instrument.clone(),
601            Reversed((price, size, value)),
602        ));
603    }
604}
605
606impl<T> From<Position<T>> for Positions<T>
607where
608    T: PositionNum,
609{
610    fn from(p: Position<T>) -> Self {
611        let asset = p.instrument.quote().clone();
612        let inst = p.instrument.as_symbol().clone();
613        let sv = SingleValue {
614            value: T::zero(),
615            positions: HashMap::from([(inst, p)]),
616        };
617        Self {
618            values: HashMap::from([(asset, sv)]),
619        }
620    }
621}
622
623impl<T> fmt::Display for SingleValue<T>
624where
625    T: fmt::Display + PositionNum,
626{
627    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
628        const MIDDLE: &str = "├ ";
629        const LAST: &str = "└ ";
630        let len = self.positions.len();
631        for (idx, (inst, p)) in self.positions.iter().enumerate() {
632            if p.is_zero() {
633                continue;
634            }
635            if idx == len - 1 {
636                writeln!(f, "{LAST}{inst} => {p}")?;
637            } else {
638                writeln!(f, "{MIDDLE}{inst} => {p}")?;
639            }
640        }
641        Ok(())
642    }
643}
644
645impl<T> fmt::Display for Positions<T>
646where
647    T: PositionNum + fmt::Display,
648{
649    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
650        for (asset, sv) in self.values.iter() {
651            writeln!(f, "{asset} => {} {asset}", sv.value)?;
652            write!(f, "{sv}")?;
653        }
654        Ok(())
655    }
656}
657
658/// Positions Expression.
659#[derive(Debug)]
660pub struct Expr<'a, T>(&'a Positions<T>);
661
662impl<'a, T> Clone for Expr<'a, T> {
663    fn clone(&self) -> Self {
664        Self(self.0)
665    }
666}
667
668impl<'a, T> Copy for Expr<'a, T> {}
669
670impl<'a, T> Expr<'a, T> {
671    /// Create a [`Expr`] from a [`Positions`].
672    #[inline]
673    pub fn new(positions: &'a Positions<T>) -> Self {
674        Self(positions)
675    }
676}
677
678impl<'a, T> Deref for Expr<'a, T> {
679    type Target = Positions<T>;
680
681    fn deref(&self) -> &Self::Target {
682        self.0
683    }
684}
685
686impl<'a, T: PositionNum> Expr<'a, T> {
687    /// Get the reference instruments.
688    pub fn instruments<'b>(&'b self, root: &'b Asset) -> impl Iterator<Item = Instrument> + 'b {
689        self.0.values.iter().flat_map(move |(asset, sv)| {
690            let strong = if asset == root {
691                None
692            } else {
693                Some(Instrument::spot(asset, root))
694            };
695            sv.positions
696                .values()
697                .map(|p| p.instrument.clone())
698                .chain(strong)
699        })
700    }
701
702    /// Evaluate the expression with the given prices.
703    /// Return [`None`] if there are missing prices.
704    pub fn eval(&self, root: &Asset, prices: &HashMap<Symbol, T>) -> Option<T> {
705        self.eval_with(root, |p| {
706            Some(p.closed(prices.get(p.instrument().as_symbol())?))
707        })
708    }
709
710    /// Evaluate the expression with the value returned by the given function.
711    /// Return [`None`] if there is something wrong.
712    pub fn eval_with<F>(&self, root: &Asset, mut eval: F) -> Option<T>
713    where
714        F: FnMut(&Position<T>) -> Option<T>,
715    {
716        self.0
717            .values
718            .iter()
719            .map(move |(asset, sv)| {
720                let weak = sv
721                    .positions
722                    .values()
723                    .map(&mut eval)
724                    .try_fold(T::zero(), |acc, x| Some(acc + x?));
725                let value = weak.map(|v| v + sv.value.clone());
726                if asset == root {
727                    value
728                } else {
729                    let p = Instrument::spot(asset, root).position((T::zero(), value?));
730                    Some((eval)(&p)?)
731                }
732            })
733            .try_fold(T::zero(), |acc, x| Some(acc + x?))
734    }
735}
736
737impl<'a, T: PositionNum + fmt::Display> fmt::Display for Expr<'a, T> {
738    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739        if self.0.is_empty() {
740            return write!(f, "0");
741        }
742        for (idx, (asset, sv)) in self.0.values.iter().enumerate() {
743            let mut value = sv.value().clone();
744            let first_sv = idx == 0;
745            let no_position = sv.is_empty();
746            for (idx, p) in sv.positions.values().enumerate() {
747                if !(first_sv && idx == 0) {
748                    write!(f, " + ")?;
749                }
750                value += p.value();
751                let naive = p.as_naive();
752                super::tree::write_position(f, &naive.price, &naive.size, p.instrument())?;
753            }
754            if first_sv && no_position {
755                write!(f, "{} {asset}", value)?;
756            } else {
757                let sign = if value.is_negative() { " - " } else { " + " };
758                write!(f, "{sign}{} {asset}", value.abs())?;
759            }
760        }
761        Ok(())
762    }
763}
764
765#[cfg(test)]
766mod tests {
767    use super::*;
768    use crate::{asset::Asset, Reversed};
769    use fraction::{BigInt, GenericDecimal, Zero};
770
771    type Decimal = GenericDecimal<BigInt, usize>;
772
773    #[test]
774    fn normal() {
775        let mut p = Position::new(
776            Instrument::from((Asset::btc(), Asset::usdt())),
777            Decimal::zero(),
778        );
779        p += Decimal::from(1.5);
780        p += (Decimal::from(2.3), Decimal::from(2.5));
781        p += (Decimal::from(7.3), Decimal::from(3.4));
782        p += (Decimal::from(3.7), Decimal::from(-7.8), Decimal::from(12));
783        #[cfg(feature = "std")]
784        println!("{p}");
785        assert_eq!(
786            p,
787            Position::new(
788                Instrument::from((Asset::btc(), Asset::usdt())),
789                (Decimal::from(3.7), Decimal::from(-1.9), Decimal::from(4.7),)
790            )
791        );
792        #[cfg(feature = "std")]
793        println!("{}", p.as_tree());
794    }
795
796    #[test]
797    fn reversed() {
798        let mut p = Position::new(
799            Instrument::from((Asset::usdt(), Asset::btc())).prefer_reversed(true),
800            Decimal::zero(),
801        );
802        p += Reversed(Decimal::from(1.5));
803        p += Reversed((Decimal::from(3), Decimal::from(2)));
804        p += Reversed((Decimal::from(4), Decimal::from(1)));
805        p += Reversed((Decimal::from(2), Decimal::from(-7), Decimal::from(-1.4)));
806        #[cfg(feature = "std")]
807        println!("{p}");
808        assert_eq!(
809            *p.value(),
810            (Decimal::from(-29) / Decimal::from(60)).set_precision(1),
811        );
812        #[cfg(feature = "std")]
813        println!("{}", p.as_tree());
814    }
815
816    #[test]
817    fn basic_positions() {
818        let btc = Asset::btc();
819        let usdt = Asset::usdt();
820        let btc_usdt_swap =
821            Instrument::try_new("SWAP:BTC-USDT-SWAP", &Asset::btc(), &Asset::usdt()).unwrap();
822        let btc_usd_swap = Instrument::try_new("SWAP:BTC-USD-SWAP", &Asset::usd(), &Asset::btc())
823            .unwrap()
824            .prefer_reversed(true);
825        let eth_btc_swap =
826            Instrument::try_new("SWAP:ETH-BTC-SWAP", &Asset::eth(), &Asset::btc()).unwrap();
827        let mut p = Positions::default();
828        p += (Decimal::from(-16000), &usdt);
829        p += (Decimal::from(1), &btc);
830        p += Reversed((Decimal::from(16000), Decimal::from(-16000), &btc_usd_swap));
831        p += (Decimal::from(0.067), Decimal::from(-21.5), &eth_btc_swap);
832        p += (
833            Decimal::from(16001),
834            Decimal::from(-1.5),
835            Decimal::from(-2.7),
836            &btc_usdt_swap,
837        );
838        #[cfg(feature = "std")]
839        println!("{p}");
840    }
841
842    #[test]
843    fn positions_as_tree() {
844        let btc = Asset::btc();
845        let usdt = Asset::usdt();
846        let btc_usdt_swap =
847            Instrument::try_new("SWAP:BTC-USDT-SWAP", &Asset::btc(), &Asset::usdt()).unwrap();
848        let btc_usd_swap = Instrument::try_new("SWAP:BTC-USD-SWAP", &Asset::usd(), &Asset::btc())
849            .unwrap()
850            .prefer_reversed(true);
851        let eth_btc_swap =
852            Instrument::try_new("SWAP:ETH-BTC-SWAP", &Asset::eth(), &Asset::btc()).unwrap();
853        let mut p = Positions::default();
854        p += (Decimal::from(-16000), &usdt);
855        p += (Decimal::from(1), &btc);
856        p += Reversed((Decimal::from(16000), Decimal::from(-16000), &btc_usd_swap));
857        p += (Decimal::from(0.067), Decimal::from(-21.5), &eth_btc_swap);
858        p += (
859            Decimal::from(16001),
860            Decimal::from(-1.5),
861            Decimal::from(-2.7),
862            &btc_usdt_swap,
863        );
864        let tree = p.as_tree(&usdt);
865        #[cfg(feature = "std")]
866        println!("{tree}");
867        let prices = HashMap::from([
868            (eth_btc_swap.clone(), Decimal::from(0.059)),
869            (btc_usd_swap.clone(), Decimal::from(17000)),
870            (btc_usdt_swap.clone(), Decimal::from(17002)),
871            (
872                Instrument::from((btc.clone(), usdt.clone())),
873                Decimal::from(17000),
874            ),
875        ]);
876        #[cfg(feature = "std")]
877        for inst in tree.instruments() {
878            println!("{inst}");
879        }
880        let ans = tree.eval(&prices).unwrap().set_precision(1);
881        #[cfg(feature = "std")]
882        println!("{ans}");
883        assert_eq!(ans, Decimal::from(1419.8).set_precision(1));
884    }
885
886    #[test]
887    fn instruments_of_expr() {
888        #[cfg(not(feature = "std"))]
889        use alloc::vec::Vec;
890
891        let btc = Asset::btc();
892        let usdt = Asset::usdt();
893        let btc_usdt_swap =
894            Instrument::try_new("SWAP:BTC-USDT-SWAP", &Asset::btc(), &Asset::usdt()).unwrap();
895        let btc_usd_swap = Instrument::try_new("SWAP:BTC-USD-SWAP", &Asset::usd(), &Asset::btc())
896            .unwrap()
897            .prefer_reversed(true);
898        let eth_btc_swap =
899            Instrument::try_new("SWAP:ETH-BTC-SWAP", &Asset::eth(), &Asset::btc()).unwrap();
900        let mut p = Positions::default();
901        p += (Decimal::from(-16000), &usdt);
902        p += (Decimal::from(1), &btc);
903        p += Reversed((Decimal::from(16000), Decimal::from(-16000), &btc_usd_swap));
904        p += (Decimal::from(0.067), Decimal::from(-21.5), &eth_btc_swap);
905        p += (
906            Decimal::from(16001),
907            Decimal::from(-1.5),
908            Decimal::from(-2.7),
909            &btc_usdt_swap,
910        );
911        let insts = p.as_expr().instruments(&Asset::ETH).collect::<Vec<_>>();
912        let usdt_eth = Instrument::spot(&Asset::USDT, &Asset::ETH);
913        let btc_eth = Instrument::spot(&Asset::BTC, &Asset::ETH);
914        assert_eq!(insts.len(), 5);
915        for inst in [
916            &btc_usd_swap,
917            &btc_usdt_swap,
918            &eth_btc_swap,
919            &usdt_eth,
920            &btc_eth,
921        ] {
922            assert!(insts.contains(inst));
923        }
924    }
925
926    #[test]
927    fn eval_expr() {
928        let btc = Asset::btc();
929        let usdt = Asset::usdt();
930        let btc_usdt_swap =
931            Instrument::try_new("SWAP:BTC-USDT-SWAP", &Asset::btc(), &Asset::usdt()).unwrap();
932        let btc_usd_swap = Instrument::try_new("SWAP:BTC-USD-SWAP", &Asset::usd(), &Asset::btc())
933            .unwrap()
934            .prefer_reversed(true);
935        let eth_btc_swap =
936            Instrument::try_new("SWAP:ETH-BTC-SWAP", &Asset::eth(), &Asset::btc()).unwrap();
937        let mut p = Positions::default();
938        #[cfg(feature = "std")]
939        println!("{}", p.as_expr());
940        p += (Decimal::from(-16000), &usdt);
941        #[cfg(feature = "std")]
942        println!("{}", p.as_expr());
943        p += (Decimal::from(1), &btc);
944        #[cfg(feature = "std")]
945        println!("{}", p.as_expr());
946        p += Reversed((Decimal::from(16000), Decimal::from(-16000), &btc_usd_swap));
947        #[cfg(feature = "std")]
948        println!("{}", p.as_expr());
949        p += (Decimal::from(0.067), Decimal::from(-21.5), &eth_btc_swap);
950        #[cfg(feature = "std")]
951        println!("{}", p.as_expr());
952        p += (
953            Decimal::from(16001),
954            Decimal::from(-1.5),
955            Decimal::from(-2.7),
956            &btc_usdt_swap,
957        );
958        let expr = p.as_expr();
959        #[cfg(feature = "std")]
960        println!("{}", expr);
961        let prices = HashMap::from([
962            (eth_btc_swap.as_symbol().clone(), Decimal::from(0.059)),
963            (btc_usd_swap.as_symbol().clone(), Decimal::from(17000)),
964            (btc_usdt_swap.as_symbol().clone(), Decimal::from(17002)),
965            (Symbol::spot(&btc, &usdt), Decimal::from(17000)),
966        ]);
967        #[cfg(feature = "std")]
968        for inst in expr.instruments(&Asset::USDT) {
969            println!("{inst}");
970        }
971        let ans = expr.eval(&Asset::USDT, &prices).unwrap().set_precision(1);
972        #[cfg(feature = "std")]
973        println!("{ans}");
974        assert_eq!(ans, Decimal::from(1419.8).set_precision(1));
975    }
976
977    #[cfg(feature = "serde")]
978    #[test]
979    fn serde_single_value() -> anyhow::Result<()> {
980        use rust_decimal_macros::dec;
981
982        let inst = Instrument::try_new("SWAP:BTC-USD-SWAP", &Asset::usd(), &Asset::btc()).unwrap();
983        let sv = SingleValue {
984            value: dec!(1.2),
985            positions: HashMap::from([(
986                inst.as_symbol().clone(),
987                inst.position((dec!(1.4), dec!(2))),
988            )]),
989        };
990        let s = serde_json::to_string(&sv)?;
991        #[cfg(feature = "std")]
992        println!("{s}");
993        assert!(!s.is_empty());
994        Ok(())
995    }
996
997    #[cfg(feature = "serde")]
998    #[test]
999    fn serde_positions() -> anyhow::Result<()> {
1000        use rust_decimal_macros::dec;
1001        let inst = Instrument::try_new("SWAP:BTC-USD-SWAP", &Asset::usd(), &Asset::btc()).unwrap();
1002        let sv = SingleValue {
1003            value: dec!(1.2),
1004            positions: HashMap::from([(
1005                inst.as_symbol().clone(),
1006                inst.position((dec!(1.4), dec!(2))),
1007            )]),
1008        };
1009        let positoins = Positions {
1010            values: HashMap::from([(inst.quote().clone(), sv)]),
1011        };
1012        let s = serde_json::to_string(&positoins)?;
1013        #[cfg(feature = "std")]
1014        println!("{s}");
1015        assert!(!s.is_empty());
1016        Ok(())
1017    }
1018}