1use num_traits::{CheckedAdd, CheckedDiv, CheckedSub, Zero};
2
3use crate::{
4 market::{PerpMarket, PerpMarketExt, SwapMarketMutExt},
5 num::{MulDiv, Unsigned},
6 params::fee::PositionFees,
7 pool::delta::PriceImpact,
8 position::{
9 CollateralDelta, Position, PositionExt, PositionMut, PositionMutExt, PositionStateExt,
10 WillCollateralBeSufficient,
11 },
12 price::{Price, Prices},
13 BorrowingFeeMarketExt, PerpMarketMut, PoolExt,
14};
15
16use self::collateral_processor::{CollateralProcessor, ProcessResult};
17
18mod claimable;
19mod collateral_processor;
20mod report;
21mod utils;
22
23pub use self::{
24 claimable::ClaimableCollateral,
25 report::{DecreasePositionReport, OutputAmounts, Pnl},
26};
27
28use super::{swap::SwapReport, MarketAction};
29
30#[must_use = "actions do nothing unless you `execute` them"]
32pub struct DecreasePosition<P: Position<DECIMALS>, const DECIMALS: u8> {
33 position: P,
34 params: DecreasePositionParams<P::Num>,
35 withdrawable_collateral_amount: P::Num,
36 size_delta_usd: P::Num,
37}
38
39#[derive(
41 Debug,
42 Clone,
43 Copy,
44 Default,
45 num_enum::TryFromPrimitive,
46 num_enum::IntoPrimitive,
47 PartialEq,
48 Eq,
49 PartialOrd,
50 Ord,
51 Hash,
52)]
53#[cfg_attr(
54 feature = "strum",
55 derive(strum::EnumIter, strum::EnumString, strum::Display)
56)]
57#[cfg_attr(feature = "strum", strum(serialize_all = "snake_case"))]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
60#[cfg_attr(
61 feature = "anchor-lang",
62 derive(
63 anchor_lang::AnchorSerialize,
64 anchor_lang::AnchorDeserialize,
65 anchor_lang::InitSpace
66 )
67)]
68#[repr(u8)]
69#[non_exhaustive]
70pub enum DecreasePositionSwapType {
71 #[default]
73 NoSwap,
74 PnlTokenToCollateralToken,
76 CollateralToPnlToken,
78}
79
80#[derive(Debug, Clone, Copy)]
82pub struct DecreasePositionParams<T> {
83 prices: Prices<T>,
84 initial_size_delta_usd: T,
85 acceptable_price: Option<T>,
86 initial_collateral_withdrawal_amount: T,
87 flags: DecreasePositionFlags,
88 swap: DecreasePositionSwapType,
89}
90
91impl<T> DecreasePositionParams<T> {
92 pub fn prices(&self) -> &Prices<T> {
94 &self.prices
95 }
96
97 pub fn initial_size_delta_usd(&self) -> &T {
99 &self.initial_size_delta_usd
100 }
101
102 pub fn acceptable_price(&self) -> Option<&T> {
104 self.acceptable_price.as_ref()
105 }
106
107 pub fn initial_collateral_withdrawal_amount(&self) -> &T {
109 &self.initial_collateral_withdrawal_amount
110 }
111
112 pub fn is_insolvent_close_allowed(&self) -> bool {
114 self.flags.is_insolvent_close_allowed
115 }
116
117 pub fn is_liquidation_order(&self) -> bool {
119 self.flags.is_liquidation_order
120 }
121
122 pub fn is_cap_size_delta_usd_allowed(&self) -> bool {
124 self.flags.is_cap_size_delta_usd_allowed
125 }
126
127 pub fn swap(&self) -> DecreasePositionSwapType {
129 self.swap
130 }
131}
132
133#[derive(Debug, Clone, Copy, Default)]
135pub struct DecreasePositionFlags {
136 pub is_insolvent_close_allowed: bool,
138 pub is_liquidation_order: bool,
140 pub is_cap_size_delta_usd_allowed: bool,
142}
143
144impl DecreasePositionFlags {
145 fn init<T>(&mut self, size_in_usd: &T, size_delta_usd: &mut T) -> crate::Result<()>
146 where
147 T: Ord + Clone,
148 {
149 if *size_delta_usd > *size_in_usd {
150 if self.is_cap_size_delta_usd_allowed {
151 *size_delta_usd = size_in_usd.clone();
152 } else {
153 return Err(crate::Error::InvalidArgument("invalid decrease order size"));
154 }
155 }
156
157 let is_full_close = *size_in_usd == *size_delta_usd;
158 self.is_insolvent_close_allowed = is_full_close && self.is_insolvent_close_allowed;
159
160 Ok(())
161 }
162}
163
164struct ProcessCollateralResult<T: Unsigned> {
165 price_impact_value: T::Signed,
166 price_impact_diff: T,
167 execution_price: T,
168 size_delta_in_tokens: T,
169 is_output_token_long: bool,
170 is_secondary_output_token_long: bool,
171 collateral: ProcessResult<T>,
172 fees: PositionFees<T>,
173 pnl: Pnl<T::Signed>,
174}
175
176impl<const DECIMALS: u8, P: PositionMut<DECIMALS>> DecreasePosition<P, DECIMALS>
177where
178 P::Market: PerpMarketMut<DECIMALS, Num = P::Num, Signed = P::Signed>,
179{
180 pub fn try_new(
182 position: P,
183 prices: Prices<P::Num>,
184 mut size_delta_usd: P::Num,
185 acceptable_price: Option<P::Num>,
186 collateral_withdrawal_amount: P::Num,
187 mut flags: DecreasePositionFlags,
188 ) -> crate::Result<Self> {
189 if !prices.is_valid() {
190 return Err(crate::Error::InvalidArgument("invalid prices"));
191 }
192 if position.is_empty() {
193 return Err(crate::Error::InvalidPosition("empty position"));
194 }
195
196 let initial_size_delta_usd = size_delta_usd.clone();
197 flags.init(position.size_in_usd(), &mut size_delta_usd)?;
198
199 Ok(Self {
200 params: DecreasePositionParams {
201 prices,
202 initial_size_delta_usd,
203 acceptable_price,
204 initial_collateral_withdrawal_amount: collateral_withdrawal_amount.clone(),
205 flags,
206 swap: DecreasePositionSwapType::NoSwap,
207 },
208 withdrawable_collateral_amount: collateral_withdrawal_amount
209 .min(position.collateral_amount().clone()),
210 size_delta_usd,
211 position,
212 })
213 }
214
215 pub fn set_swap(mut self, kind: DecreasePositionSwapType) -> Self {
217 self.params.swap = kind;
218 self
219 }
220
221 fn check_partial_close(&mut self) -> crate::Result<()> {
223 use num_traits::CheckedMul;
224
225 if self.will_size_remain() {
226 let (estimated_pnl, _, _) = self
227 .position
228 .pnl_value(&self.params.prices, self.position.size_in_usd())?;
229 let estimated_realized_pnl = self
230 .size_delta_usd
231 .checked_mul_div_with_signed_numerator(&estimated_pnl, self.position.size_in_usd())
232 .ok_or(crate::Error::Computation("estimating realized pnl"))?;
233 let estimated_remaining_pnl = estimated_pnl
234 .checked_sub(&estimated_realized_pnl)
235 .ok_or(crate::Error::Computation("estimating remaining pnl"))?;
236
237 let delta = CollateralDelta::new(
238 self.position
239 .size_in_usd()
240 .checked_sub(&self.size_delta_usd)
241 .expect("should have been capped"),
242 self.position
243 .collateral_amount()
244 .checked_sub(&self.withdrawable_collateral_amount)
245 .expect("should have been capped"),
246 estimated_realized_pnl,
247 self.size_delta_usd.to_opposite_signed()?,
248 );
249
250 let mut will_be_sufficient = self
251 .position
252 .will_collateral_be_sufficient(&self.params.prices, &delta)?;
253
254 if let WillCollateralBeSufficient::Insufficient(remaining_collateral_value) =
255 &mut will_be_sufficient
256 {
257 if self.size_delta_usd.is_zero() {
258 return Err(crate::Error::InvalidArgument(
259 "unable to withdraw collateral: insufficient collateral",
260 ));
261 }
262
263 let collateral_token_price = if self.position.is_collateral_token_long() {
264 &self.params.prices.long_token_price
265 } else {
266 &self.params.prices.short_token_price
267 };
268 let add_back = self
270 .withdrawable_collateral_amount
271 .checked_mul(collateral_token_price.pick_price(false))
272 .ok_or(crate::Error::Computation("overflow calculating add back"))?
273 .to_signed()?;
274 *remaining_collateral_value = remaining_collateral_value
275 .checked_add(&add_back)
276 .ok_or(crate::Error::Computation("adding back"))?;
277 self.withdrawable_collateral_amount = Zero::zero();
278 }
279
280 let params = self.position.market().position_params()?;
283
284 let remaining_value = will_be_sufficient
285 .checked_add(&estimated_remaining_pnl)
286 .ok_or(crate::Error::Computation("calculating remaining value"))?;
287 if remaining_value < params.min_collateral_value().to_signed()? {
288 self.size_delta_usd = self.position.size_in_usd().clone();
289 }
290
291 if *self.position.size_in_usd() > self.size_delta_usd
292 && self.is_remaining_size_too_small(params.min_position_size_usd())?
293 {
294 self.size_delta_usd = self.position.size_in_usd().clone();
295 }
296 }
297 Ok(())
298 }
299
300 fn is_remaining_size_too_small(&self, min_position_size_usd: &P::Num) -> crate::Result<bool> {
301 if self
302 .position
303 .size_in_usd()
304 .checked_sub(&self.size_delta_usd)
305 .ok_or(crate::Error::Computation(
306 "calculating remaining size_in_usd",
307 ))?
308 < *min_position_size_usd
309 {
310 return Ok(true);
311 }
312
313 Ok(*self.position.size_in_tokens()
318 <= self.position.size_delta_in_tokens(&self.size_delta_usd)?)
319 }
320
321 fn check_close(&mut self) -> crate::Result<()> {
322 if self.size_delta_usd == *self.position.size_in_usd()
323 && !self.withdrawable_collateral_amount.is_zero()
324 {
325 self.withdrawable_collateral_amount = Zero::zero();
327 }
328 Ok(())
329 }
330
331 fn check_liquidation(&self) -> crate::Result<()> {
332 if self.params.is_liquidation_order() {
333 let Some(_reason) =
334 self.position
335 .check_liquidatable(&self.params.prices, true, true)?
336 else {
337 return Err(crate::Error::NotLiquidatable);
338 };
339 Ok(())
340 } else {
341 Ok(())
342 }
343 }
344
345 fn will_size_remain(&self) -> bool {
346 self.size_delta_usd < *self.position.size_in_usd()
347 }
348
349 pub fn is_full_close(&self) -> bool {
351 self.size_delta_usd == *self.position.size_in_usd()
352 }
353
354 fn collateral_token_price(&self) -> &Price<P::Num> {
355 self.position.collateral_price(self.params.prices())
356 }
357
358 #[allow(clippy::type_complexity)]
359 fn process_collateral(&mut self) -> crate::Result<ProcessCollateralResult<P::Num>> {
360 debug_assert!(!self.params.is_insolvent_close_allowed() || self.is_full_close());
362
363 let ExecutionParams {
364 price_impact,
365 price_impact_diff,
366 execution_price,
367 } = self.get_execution_params()?;
368
369 let (base_pnl_usd, uncapped_base_pnl_usd, size_delta_in_tokens) = self
371 .position
372 .pnl_value(&self.params.prices, &self.size_delta_usd)?;
373
374 let is_output_token_long = self.position.is_collateral_token_long();
375 let is_pnl_token_long = self.position.is_long();
376 let are_pnl_and_collateral_tokens_the_same =
377 self.position.are_pnl_and_collateral_tokens_the_same();
378
379 let mut fees = self.position.position_fees(
380 self.params
381 .prices
382 .collateral_token_price(is_output_token_long),
383 &self.size_delta_usd,
384 price_impact.balance_change,
385 self.params.is_liquidation_order(),
386 )?;
387
388 let remaining_collateral_amount = self.position.collateral_amount().clone();
389
390 let processor = CollateralProcessor::new(
391 self.position.market_mut(),
392 is_output_token_long,
393 is_pnl_token_long,
394 are_pnl_and_collateral_tokens_the_same,
395 &self.params.prices,
396 remaining_collateral_amount,
397 self.params.is_insolvent_close_allowed(),
398 );
399
400 let mut result = {
401 let ty = self.params.swap;
402 let mut swap_result = None;
403
404 let price_impact_value = &price_impact.value;
405 let result = processor.process(|mut ctx| {
406 ctx.add_pnl_if_positive(&base_pnl_usd)?
407 .add_price_impact_if_positive(price_impact_value)?
408 .swap_profit_to_collateral_tokens(self.params.swap, |error| {
409 swap_result = Some(error);
410 Ok(())
411 })?
412 .pay_for_funding_fees(fees.funding_fees())?
413 .pay_for_pnl_if_negative(&base_pnl_usd)?
414 .pay_for_fees_excluding_funding(&mut fees)?
415 .pay_for_price_impact_if_negative(price_impact_value)?
416 .pay_for_price_impact_diff(&price_impact_diff)?;
417 Ok(())
418 })?;
419
420 if let Some(result) = swap_result {
421 match result {
422 Ok(report) => self.position.on_swapped(ty, &report)?,
423 Err(error) => self.position.on_swap_error(ty, error)?,
424 }
425 }
426
427 result
428 };
429
430 if !self.withdrawable_collateral_amount.is_zero() && !price_impact_diff.is_zero() {
439 debug_assert!(!self.collateral_token_price().has_zero());
441 let diff_amount = price_impact_diff
442 .checked_div(self.collateral_token_price().pick_price(false))
443 .ok_or(crate::Error::Computation("calculating diff amount"))?;
444 if self.withdrawable_collateral_amount > diff_amount {
445 self.withdrawable_collateral_amount = self
446 .withdrawable_collateral_amount
447 .checked_sub(&diff_amount)
448 .ok_or(crate::Error::Computation(
449 "calculating new withdrawable amount",
450 ))?;
451 } else {
452 self.withdrawable_collateral_amount = P::Num::zero();
453 }
454 }
455
456 if self.withdrawable_collateral_amount > result.remaining_collateral_amount {
458 self.withdrawable_collateral_amount = result.remaining_collateral_amount.clone();
459 }
460
461 if !self.withdrawable_collateral_amount.is_zero() {
462 result.remaining_collateral_amount = result
463 .remaining_collateral_amount
464 .checked_sub(&self.withdrawable_collateral_amount)
465 .expect("must be success");
466 result.output_amount = result
467 .output_amount
468 .checked_add(&self.withdrawable_collateral_amount)
469 .ok_or(crate::Error::Computation(
470 "overflow occurred while adding withdrawable amount",
471 ))?;
472 }
473
474 Ok(ProcessCollateralResult {
475 price_impact_value: price_impact.value,
476 price_impact_diff,
477 execution_price,
478 size_delta_in_tokens,
479 is_output_token_long,
480 is_secondary_output_token_long: is_pnl_token_long,
481 collateral: result,
482 fees,
483 pnl: Pnl::new(base_pnl_usd, uncapped_base_pnl_usd),
484 })
485 }
486
487 fn get_execution_params(&self) -> crate::Result<ExecutionParams<P::Num>> {
488 let index_token_price = &self.params.prices.index_token_price;
489 let size_delta_usd = &self.size_delta_usd;
490
491 if size_delta_usd.is_zero() {
492 return Ok(ExecutionParams {
493 price_impact: Default::default(),
494 price_impact_diff: Zero::zero(),
495 execution_price: index_token_price
496 .pick_price(!self.position.is_long())
497 .clone(),
498 });
499 }
500
501 let (price_impact, price_impact_diff_usd) = self.position.capped_position_price_impact(
502 index_token_price,
503 &self.size_delta_usd.to_opposite_signed()?,
504 true,
505 )?;
506
507 let execution_price = utils::get_execution_price_for_decrease(
508 index_token_price,
509 self.position.size_in_usd(),
510 self.position.size_in_tokens(),
511 size_delta_usd,
512 &price_impact.value,
513 self.params.acceptable_price.as_ref(),
514 self.position.is_long(),
515 )?;
516
517 Ok(ExecutionParams {
518 price_impact,
519 price_impact_diff: price_impact_diff_usd,
520 execution_price,
521 })
522 }
523
524 #[allow(clippy::type_complexity)]
526 fn swap_collateral_token_to_pnl_token(
527 market: &mut P::Market,
528 report: &mut DecreasePositionReport<P::Num, P::Signed>,
529 prices: &Prices<P::Num>,
530 swap: DecreasePositionSwapType,
531 ) -> crate::Result<Option<crate::Result<SwapReport<P::Num, <P::Num as Unsigned>::Signed>>>>
532 {
533 let is_token_in_long = report.is_output_token_long();
534 let is_secondary_output_token_long = report.is_secondary_output_token_long();
535 let (output_amount, secondary_output_amount) = report.output_amounts_mut();
536 if !output_amount.is_zero()
537 && matches!(swap, DecreasePositionSwapType::CollateralToPnlToken)
538 {
539 if is_token_in_long == is_secondary_output_token_long {
540 return Err(crate::Error::InvalidArgument(
541 "swap collateral: swap is not required",
542 ));
543 }
544
545 let token_in_amount = output_amount.clone();
546
547 match market
548 .swap(is_token_in_long, token_in_amount, prices.clone())
549 .and_then(|a| a.execute())
550 {
551 Ok(swap_report) => {
552 *secondary_output_amount = secondary_output_amount
553 .checked_add(swap_report.token_out_amount())
554 .ok_or(crate::Error::Computation(
555 "swap collateral: overflow occurred while adding token_out_amount",
556 ))?;
557 *output_amount = Zero::zero();
558 Ok(Some(Ok(swap_report)))
559 }
560 Err(err) => Ok(Some(Err(err))),
561 }
562 } else {
563 Ok(None)
564 }
565 }
566}
567
568impl<const DECIMALS: u8, P: PositionMut<DECIMALS>> MarketAction for DecreasePosition<P, DECIMALS>
569where
570 P::Market: PerpMarketMut<DECIMALS, Num = P::Num, Signed = P::Signed>,
571{
572 type Report = Box<DecreasePositionReport<P::Num, P::Signed>>;
573
574 fn execute(mut self) -> crate::Result<Self::Report> {
575 debug_assert!(
576 self.size_delta_usd <= *self.position.size_in_usd_mut(),
577 "must have been checked or capped by the position size"
578 );
579 debug_assert!(
580 self.withdrawable_collateral_amount <= *self.position.collateral_amount_mut(),
581 "must have been capped by the position collateral amount"
582 );
583
584 self.check_partial_close()?;
585 self.check_close()?;
586
587 if !matches!(self.params.swap, DecreasePositionSwapType::NoSwap)
588 && self.position.are_pnl_and_collateral_tokens_the_same()
589 {
590 self.params.swap = DecreasePositionSwapType::NoSwap;
591 }
592
593 self.check_liquidation()?;
594
595 let initial_collateral_amount = self.position.collateral_amount_mut().clone();
596
597 let mut execution = self.process_collateral()?;
598
599 let should_remove;
600 {
601 let is_long = self.position.is_long();
602 let is_collateral_long = self.position.is_collateral_token_long();
603
604 let next_position_size_in_usd = self
605 .position
606 .size_in_usd_mut()
607 .checked_sub(&self.size_delta_usd)
608 .ok_or(crate::Error::Computation(
609 "calculating next position size in usd",
610 ))?;
611 let next_position_borrowing_factor = self
612 .position
613 .market()
614 .cumulative_borrowing_factor(is_long)?;
615
616 self.position.update_total_borrowing(
618 &next_position_size_in_usd,
619 &next_position_borrowing_factor,
620 )?;
621
622 let next_position_size_in_tokens = self
623 .position
624 .size_in_tokens_mut()
625 .checked_sub(&execution.size_delta_in_tokens)
626 .ok_or(crate::Error::Computation("calculating next size in tokens"))?;
627 let next_position_collateral_amount =
628 execution.collateral.remaining_collateral_amount.clone();
629
630 should_remove =
631 next_position_size_in_usd.is_zero() || next_position_size_in_tokens.is_zero();
632
633 if should_remove {
634 *self.position.size_in_usd_mut() = Zero::zero();
635 *self.position.size_in_tokens_mut() = Zero::zero();
636 *self.position.collateral_amount_mut() = Zero::zero();
637 execution.collateral.output_amount = execution
638 .collateral
639 .output_amount
640 .checked_add(&next_position_collateral_amount)
641 .ok_or(crate::Error::Computation("calculating output amount"))?;
642 } else {
643 *self.position.size_in_usd_mut() = next_position_size_in_usd;
644 *self.position.size_in_tokens_mut() = next_position_size_in_tokens;
645 *self.position.collateral_amount_mut() = next_position_collateral_amount;
646 };
647
648 {
650 let collateral_delta_amount = initial_collateral_amount
651 .checked_sub(self.position.collateral_amount_mut())
652 .ok_or(crate::Error::Computation("collateral amount increased"))?;
653
654 self.position
655 .market_mut()
656 .collateral_sum_pool_mut(is_long)?
657 .apply_delta_amount(
658 is_collateral_long,
659 &collateral_delta_amount.to_opposite_signed()?,
660 )?;
661 }
662
663 *self.position.borrowing_factor_mut() = next_position_borrowing_factor;
665 *self.position.funding_fee_amount_per_size_mut() = self
666 .position
667 .market()
668 .funding_fee_amount_per_size(is_long, is_collateral_long)?;
669 for is_long_collateral in [true, false] {
670 *self
671 .position
672 .claimable_funding_fee_amount_per_size_mut(is_long_collateral) = self
673 .position
674 .market()
675 .claimable_funding_fee_amount_per_size(is_long, is_long_collateral)?;
676 }
677 }
678
679 self.position.update_open_interest(
699 &self.size_delta_usd.to_opposite_signed()?,
700 &execution.size_delta_in_tokens.to_opposite_signed()?,
701 )?;
702
703 if !should_remove {
704 self.position.validate(&self.params.prices, false, false)?;
705 }
706
707 self.position.on_decreased()?;
708
709 let mut report = Box::new(DecreasePositionReport::new(
710 &self.params,
711 execution,
712 self.withdrawable_collateral_amount,
713 self.size_delta_usd,
714 should_remove,
715 ));
716
717 {
719 let ty = self.params.swap;
720 let swap_result = Self::swap_collateral_token_to_pnl_token(
721 self.position.market_mut(),
722 &mut report,
723 self.params.prices(),
724 ty,
725 )?;
726
727 if let Some(result) = swap_result {
728 match result {
729 Ok(report) => {
730 self.position.on_swapped(ty, &report)?;
731 }
732 Err(err) => {
733 self.position.on_swap_error(ty, err)?;
734 }
735 }
736 }
737 }
738
739 let (output_amount, secondary_output_amount) = report.output_amounts_mut();
741 if self.position.are_pnl_and_collateral_tokens_the_same()
742 && !secondary_output_amount.is_zero()
743 {
744 *output_amount = output_amount.checked_add(secondary_output_amount).ok_or(
745 crate::Error::Computation(
746 "overflow occurred while merging the secondary output amount",
747 ),
748 )?;
749 *secondary_output_amount = Zero::zero();
750 }
751
752 Ok(report)
753 }
754}
755
756struct ExecutionParams<T: Unsigned> {
757 price_impact: PriceImpact<T::Signed>,
758 price_impact_diff: T,
759 execution_price: T,
760}
761
762#[cfg(test)]
763mod tests {
764 use crate::{
765 market::LiquidityMarketMutExt,
766 test::{TestMarket, TestPosition},
767 MarketAction,
768 };
769
770 use super::*;
771
772 #[test]
773 fn basic() -> crate::Result<()> {
774 let mut market = TestMarket::<u64, 9>::default();
775 let prices = Prices::new_for_test(120, 120, 1);
776 market.deposit(1_000_000_000, 0, prices)?.execute()?;
777 market.deposit(0, 1_000_000_000, prices)?.execute()?;
778 println!("{market:#?}");
779 let mut position = TestPosition::long(true);
780 let report = position
781 .ops(&mut market)
782 .increase(
783 Prices::new_for_test(123, 123, 1),
784 100_000_000,
785 80_000_000_000,
786 None,
787 )?
788 .execute()?;
789 println!("{report:#?}");
790 println!("{position:#?}");
791
792 let report = position
793 .ops(&mut market)
794 .decrease(
795 Prices::new_for_test(125, 125, 1),
796 40_000_000_000,
797 None,
798 100_000_000,
799 Default::default(),
800 )?
801 .execute()?;
802 println!("{report:#?}");
803 println!("{position:#?}");
804 println!("{market:#?}");
805
806 let report = position
807 .ops(&mut market)
808 .decrease(
809 Prices::new_for_test(118, 118, 1),
810 40_000_000_000,
811 None,
812 0,
813 Default::default(),
814 )?
815 .execute()?;
816 println!("{report:#?}");
817 println!("{position:#?}");
818 println!("{market:#?}");
819 Ok(())
820 }
821}