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#[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 pub fn instrument(&self) -> &Instrument {
24 &self.instrument
25 }
26
27 pub fn as_naive(&self) -> &NaivePosition<T> {
29 &self.naive
30 }
31
32 pub fn value(&self) -> &T {
34 &self.naive.value
35 }
36}
37
38impl<T> Position<T>
39where
40 T: PositionNum,
41{
42 pub fn new(instrument: Instrument, position: impl IntoNaivePosition<T>) -> Self {
44 Self {
45 instrument,
46 naive: position.into_naive(),
47 }
48 }
49
50 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 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 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 pub fn notional_value(&self) -> T {
96 let mut value = self.naive.price.clone();
97 value *= &self.naive.size;
98 value
99 }
100
101 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 #[inline]
115 pub fn take(&mut self) -> T {
116 self.naive.take()
117 }
118
119 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 pub fn is_zero(&self) -> bool {
139 self.naive.size.is_zero() && self.naive.value.is_zero()
140 }
141
142 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#[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 pub fn value(&self) -> &T {
258 &self.value
259 }
260
261 #[inline]
263 pub fn iter(&self) -> impl Iterator<Item = (&Symbol, &Position<T>)> {
264 self.positions.iter()
265 }
266
267 #[inline]
269 pub fn len(&self) -> usize {
270 self.positions.len()
271 }
272
273 #[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#[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 #[inline]
361 pub fn iter(&self) -> impl Iterator<Item = (&Asset, &SingleValue<T>)> {
362 self.values.iter()
363 }
364
365 #[inline]
367 pub fn len(&self) -> usize {
368 self.values.len()
369 }
370
371 #[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 pub fn as_expr(&self) -> Expr<'_, T> {
384 Expr::new(self)
385 }
386
387 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 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 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 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 pub fn get_value(&self, asset: &Asset) -> Option<&T> {
461 Some(&self.values.get(asset)?.value)
462 }
463
464 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 pub fn get_value_mut(&mut self, asset: &Asset) -> Option<&mut T> {
474 Some(&mut self.values.get_mut(asset)?.value)
475 }
476
477 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#[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 #[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 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 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 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), ð_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), ð_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), ð_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 ð_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), ð_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}