1use crate::types::Side;
6use qs_symbols::SymbolSpec;
7use thiserror::Error;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum SizingPolicy {
12 FixedLot {
14 lots: f64,
16 },
17 FixedRiskAmount {
19 amount: f64,
21 },
22 BalanceRiskPercent {
24 percent: f64,
26 },
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum LotCapStatus {
32 NotCapped,
34 CappedAtMaximum,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq)]
40pub struct SizingResult {
41 pub final_lot_steps: u64,
43 pub final_lot: f64,
45 pub scaled_raw_lot: f64,
47 pub requested_account_risk: Option<f64>,
49 pub native_loss_per_lot: Option<f64>,
51 pub account_loss_per_lot: Option<f64>,
53 pub cap_status: LotCapStatus,
55}
56
57#[derive(Debug, Clone, PartialEq, Error)]
59pub enum SizingError {
60 #[error("risk_multiplier must be finite and positive, got {value}")]
62 InvalidRiskMultiplier {
63 value: f64,
65 },
66 #[error("fixed lots must be finite and positive, got {value}")]
68 InvalidFixedLots {
69 value: f64,
71 },
72 #[error("fixed risk amount must be finite and positive, got {value}")]
74 InvalidFixedRiskAmount {
75 value: f64,
77 },
78 #[error("balance risk percent must be finite and positive, got {value}")]
80 InvalidBalanceRiskPercent {
81 value: f64,
83 },
84 #[error("balance_before must be finite and positive, got {value}")]
86 InvalidBalanceBefore {
87 value: f64,
89 },
90 #[error("entry price must be finite and positive, got {value}")]
92 InvalidEntryPrice {
93 value: f64,
95 },
96 #[error("protective stop must be finite and positive, got {value}")]
98 InvalidProtectiveStop {
99 value: f64,
101 },
102 #[error("monetary sizing requires a protective stop")]
104 MissingProtectiveStop,
105 #[error("invalid {side} protective stop geometry: entry {entry_price}, stop {stop_price}")]
107 InvalidStopGeometry {
108 side: Side,
110 entry_price: f64,
112 stop_price: f64,
114 },
115 #[error("entry {entry_price} and stop {stop_price} have zero distance at {digits} digits")]
117 StopDistanceBelowTick {
118 entry_price: f64,
120 stop_price: f64,
122 digits: u16,
124 },
125 #[error("{field} price {value} is out of range at {digits} digits")]
127 PriceOutOfRange {
128 field: &'static str,
130 value: f64,
132 digits: u16,
134 },
135 #[error("monetary sizing requires account_loss_per_lot")]
137 MissingAccountLossPerLot,
138 #[error("account_loss_per_lot must be finite and positive, got {value}")]
140 InvalidAccountLossPerLot {
141 value: f64,
143 },
144 #[error("invalid symbol price precision: digits={digits}, pip_position={pip_position}")]
146 InvalidPricePrecision {
147 digits: u16,
149 pip_position: u16,
151 },
152 #[error("symbol lot_base_units must be positive, got {value}")]
154 InvalidLotBaseUnits {
155 value: i64,
157 },
158 #[error("symbol lot_step_units must be positive, got {value}")]
160 InvalidLotStepUnits {
161 value: i64,
163 },
164 #[error("symbol lot_min_steps must be positive, got {value}")]
166 InvalidMinimumLotSteps {
167 value: i64,
169 },
170 #[error("symbol lot_max_steps {maximum} must be zero or at least lot_min_steps {minimum}")]
172 InvalidMaximumLotSteps {
173 maximum: i64,
175 minimum: i64,
177 },
178 #[error(
180 "scaling policy value {base_value} by risk_multiplier {risk_multiplier} did not produce a finite positive value"
181 )]
182 InvalidScaledPolicyValue {
183 base_value: f64,
185 risk_multiplier: f64,
187 },
188 #[error("scaled raw lot must be finite and positive, got {value}")]
190 InvalidScaledRawLot {
191 value: f64,
193 },
194 #[error("native loss per lot is not finite for entry {entry_price} and stop {stop_price}")]
196 InvalidNativeLossPerLot {
197 entry_price: f64,
199 stop_price: f64,
201 },
202 #[error(
204 "scaled raw lot {scaled_raw_lot} floors to {floored_lot_steps} steps below minimum {minimum_lot_steps}"
205 )]
206 BelowMinimumLot {
207 scaled_raw_lot: f64,
209 floored_lot_steps: u64,
211 minimum_lot_steps: u64,
213 },
214 #[error("scaled raw lot {scaled_raw_lot} exceeds the supported lot step count")]
216 LotStepOverflow {
217 scaled_raw_lot: f64,
219 },
220}
221
222#[derive(Debug, Clone, Copy)]
223struct ValidatedLotSpec {
224 lot_base_units: u64,
225 lot_step_units: u64,
226 lot_min_steps: u64,
227 lot_max_steps: Option<u64>,
228}
229
230#[derive(Debug, Clone, Copy)]
231enum PolicyBasis {
232 FixedLots(f64),
233 AccountRisk(f64),
234}
235
236#[allow(clippy::too_many_arguments)]
240pub fn compute_size(
241 policy: &SizingPolicy,
242 risk_multiplier: f64,
243 balance_before: f64,
244 side: Side,
245 entry_price: f64,
246 protective_stop: Option<f64>,
247 spec: &SymbolSpec,
248 account_loss_per_lot: Option<f64>,
249) -> Result<SizingResult, SizingError> {
250 validate_risk_multiplier(risk_multiplier)?;
251 validate_entry_price(entry_price)?;
252 let lot_spec = validate_lot_spec(spec)?;
253 let basis = policy_basis(policy, balance_before)?;
254 let native_loss_per_lot = protective_stop
255 .map(|stop_price| compute_native_loss_per_lot(side, entry_price, stop_price, spec))
256 .transpose()?;
257
258 let scaled_policy_value = match basis {
259 PolicyBasis::FixedLots(base_value) | PolicyBasis::AccountRisk(base_value) => {
260 let scaled = base_value * risk_multiplier;
261 if !scaled.is_finite() || scaled <= 0.0 {
262 return Err(SizingError::InvalidScaledPolicyValue {
263 base_value,
264 risk_multiplier,
265 });
266 }
267 scaled
268 }
269 };
270
271 let (scaled_raw_lot, requested_account_risk, result_account_loss_per_lot) = match basis {
272 PolicyBasis::FixedLots(_) => (scaled_policy_value, None, None),
273 PolicyBasis::AccountRisk(_) => {
274 native_loss_per_lot.ok_or(SizingError::MissingProtectiveStop)?;
275 let account_loss_per_lot = account_loss_per_lot
276 .ok_or(SizingError::MissingAccountLossPerLot)
277 .and_then(validate_account_loss_per_lot)?;
278 let scaled_raw_lot = scaled_policy_value / account_loss_per_lot;
279 if !scaled_raw_lot.is_finite() || scaled_raw_lot <= 0.0 {
280 return Err(SizingError::InvalidScaledRawLot {
281 value: scaled_raw_lot,
282 });
283 }
284 (
285 scaled_raw_lot,
286 Some(scaled_policy_value),
287 Some(account_loss_per_lot),
288 )
289 }
290 };
291
292 let (final_lot_steps, final_lot, cap_status) = apply_lot_constraints(scaled_raw_lot, lot_spec)?;
293
294 Ok(SizingResult {
295 final_lot_steps,
296 final_lot,
297 scaled_raw_lot,
298 requested_account_risk,
299 native_loss_per_lot,
300 account_loss_per_lot: result_account_loss_per_lot,
301 cap_status,
302 })
303}
304
305fn validate_risk_multiplier(value: f64) -> Result<(), SizingError> {
306 if value.is_finite() && value > 0.0 {
307 Ok(())
308 } else {
309 Err(SizingError::InvalidRiskMultiplier { value })
310 }
311}
312
313fn validate_entry_price(value: f64) -> Result<(), SizingError> {
314 if value.is_finite() && value > 0.0 {
315 Ok(())
316 } else {
317 Err(SizingError::InvalidEntryPrice { value })
318 }
319}
320
321fn validate_account_loss_per_lot(value: f64) -> Result<f64, SizingError> {
322 if value.is_finite() && value > 0.0 {
323 Ok(value)
324 } else {
325 Err(SizingError::InvalidAccountLossPerLot { value })
326 }
327}
328
329fn policy_basis(policy: &SizingPolicy, balance_before: f64) -> Result<PolicyBasis, SizingError> {
330 match *policy {
331 SizingPolicy::FixedLot { lots } => {
332 if lots.is_finite() && lots > 0.0 {
333 Ok(PolicyBasis::FixedLots(lots))
334 } else {
335 Err(SizingError::InvalidFixedLots { value: lots })
336 }
337 }
338 SizingPolicy::FixedRiskAmount { amount } => {
339 if amount.is_finite() && amount > 0.0 {
340 Ok(PolicyBasis::AccountRisk(amount))
341 } else {
342 Err(SizingError::InvalidFixedRiskAmount { value: amount })
343 }
344 }
345 SizingPolicy::BalanceRiskPercent { percent } => {
346 if !percent.is_finite() || percent <= 0.0 {
347 return Err(SizingError::InvalidBalanceRiskPercent { value: percent });
348 }
349 if !balance_before.is_finite() || balance_before <= 0.0 {
350 return Err(SizingError::InvalidBalanceBefore {
351 value: balance_before,
352 });
353 }
354 let account_risk = balance_before * (percent / 100.0);
355 if account_risk.is_finite() && account_risk > 0.0 {
356 Ok(PolicyBasis::AccountRisk(account_risk))
357 } else {
358 Err(SizingError::InvalidScaledPolicyValue {
359 base_value: balance_before,
360 risk_multiplier: percent / 100.0,
361 })
362 }
363 }
364 }
365}
366
367fn validate_lot_spec(spec: &SymbolSpec) -> Result<ValidatedLotSpec, SizingError> {
368 let lot_base_units = u64::try_from(spec.lot_base_units)
369 .ok()
370 .filter(|value| *value > 0)
371 .ok_or(SizingError::InvalidLotBaseUnits {
372 value: spec.lot_base_units,
373 })?;
374 let lot_step_units = u64::try_from(spec.lot_step_units)
375 .ok()
376 .filter(|value| *value > 0)
377 .ok_or(SizingError::InvalidLotStepUnits {
378 value: spec.lot_step_units,
379 })?;
380 let lot_min_steps = u64::try_from(spec.lot_min_steps)
381 .ok()
382 .filter(|value| *value > 0)
383 .ok_or(SizingError::InvalidMinimumLotSteps {
384 value: spec.lot_min_steps,
385 })?;
386 let lot_max_steps = match spec.lot_max_steps {
387 0 => None,
388 maximum if maximum >= spec.lot_min_steps => Some(maximum as u64),
389 maximum => {
390 return Err(SizingError::InvalidMaximumLotSteps {
391 maximum,
392 minimum: spec.lot_min_steps,
393 });
394 }
395 };
396
397 Ok(ValidatedLotSpec {
398 lot_base_units,
399 lot_step_units,
400 lot_min_steps,
401 lot_max_steps,
402 })
403}
404
405pub fn compute_native_loss_per_lot(
409 side: Side,
410 entry_price: f64,
411 protective_stop: f64,
412 spec: &SymbolSpec,
413) -> Result<f64, SizingError> {
414 validate_entry_price(entry_price)?;
415 if spec.lot_base_units <= 0 {
416 return Err(SizingError::InvalidLotBaseUnits {
417 value: spec.lot_base_units,
418 });
419 }
420 if !protective_stop.is_finite() || protective_stop <= 0.0 {
421 return Err(SizingError::InvalidProtectiveStop {
422 value: protective_stop,
423 });
424 }
425 let valid_geometry = match side {
426 Side::Buy => protective_stop < entry_price,
427 Side::Sell => protective_stop > entry_price,
428 };
429 if !valid_geometry {
430 return Err(SizingError::InvalidStopGeometry {
431 side,
432 entry_price,
433 stop_price: protective_stop,
434 });
435 }
436 if spec.digits > 18 || spec.pip_position > spec.digits {
437 return Err(SizingError::InvalidPricePrecision {
438 digits: spec.digits,
439 pip_position: spec.pip_position,
440 });
441 }
442
443 let scale = 10_i64.pow(spec.digits as u32) as f64;
444 let entry_ticks = price_to_ticks("entry", entry_price, spec.digits, scale)?;
445 let stop_ticks = price_to_ticks("protective stop", protective_stop, spec.digits, scale)?;
446 let distance_ticks = entry_ticks.abs_diff(stop_ticks);
447 if distance_ticks == 0 {
448 return Err(SizingError::StopDistanceBelowTick {
449 entry_price,
450 stop_price: protective_stop,
451 digits: spec.digits,
452 });
453 }
454
455 let native_loss_per_lot = distance_ticks as f64 * spec.lot_base_units as f64 / scale;
456 if !native_loss_per_lot.is_finite() || native_loss_per_lot <= 0.0 {
457 return Err(SizingError::InvalidNativeLossPerLot {
458 entry_price,
459 stop_price: protective_stop,
460 });
461 }
462
463 Ok(native_loss_per_lot)
464}
465
466fn price_to_ticks(
467 field: &'static str,
468 value: f64,
469 digits: u16,
470 scale: f64,
471) -> Result<i64, SizingError> {
472 let scaled = value * scale;
473 if !scaled.is_finite() || scaled >= i64::MAX as f64 {
474 return Err(SizingError::PriceOutOfRange {
475 field,
476 value,
477 digits,
478 });
479 }
480 Ok(scaled.round() as i64)
481}
482
483fn apply_lot_constraints(
484 scaled_raw_lot: f64,
485 spec: ValidatedLotSpec,
486) -> Result<(u64, f64, LotCapStatus), SizingError> {
487 if !scaled_raw_lot.is_finite() || scaled_raw_lot <= 0.0 {
488 return Err(SizingError::InvalidScaledRawLot {
489 value: scaled_raw_lot,
490 });
491 }
492
493 let raw_steps = scaled_raw_lot * spec.lot_base_units as f64 / spec.lot_step_units as f64;
494 if !raw_steps.is_finite() || raw_steps >= u64::MAX as f64 {
495 return Err(SizingError::LotStepOverflow { scaled_raw_lot });
496 }
497 let floored_lot_steps = raw_steps.floor() as u64;
498 if floored_lot_steps < spec.lot_min_steps {
499 return Err(SizingError::BelowMinimumLot {
500 scaled_raw_lot,
501 floored_lot_steps,
502 minimum_lot_steps: spec.lot_min_steps,
503 });
504 }
505
506 let (final_lot_steps, cap_status) = match spec.lot_max_steps {
507 Some(maximum) if floored_lot_steps > maximum => (maximum, LotCapStatus::CappedAtMaximum),
508 _ => (floored_lot_steps, LotCapStatus::NotCapped),
509 };
510 let final_lot =
511 final_lot_steps as f64 * spec.lot_step_units as f64 / spec.lot_base_units as f64;
512
513 Ok((final_lot_steps, final_lot, cap_status))
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 fn forex_spec() -> SymbolSpec {
521 SymbolSpec {
522 canonical: "eurusd".into(),
523 pip_position: 4,
524 digits: 5,
525 category: "forex".into(),
526 lot_base_units: 100_000,
527 lot_step_units: 1_000,
528 lot_min_steps: 1,
529 lot_max_steps: 0,
530 }
531 }
532
533 fn assert_close(actual: f64, expected: f64) {
534 assert!(
535 (actual - expected).abs() < 1e-12,
536 "expected {expected}, got {actual}"
537 );
538 }
539
540 #[test]
541 fn public_native_loss_helper_matches_compute_size_normalization() {
542 let spec = forex_spec();
543 let native_loss =
544 compute_native_loss_per_lot(Side::Buy, 1.100004, 1.095003, &spec).unwrap();
545 let result = compute_size(
546 &SizingPolicy::FixedRiskAmount { amount: 100.0 },
547 1.0,
548 10_000.0,
549 Side::Buy,
550 1.100004,
551 Some(1.095003),
552 &spec,
553 Some(native_loss),
554 )
555 .unwrap();
556
557 assert_close(native_loss, 500.0);
558 assert_eq!(result.native_loss_per_lot, Some(native_loss));
559
560 let helper_error =
561 compute_native_loss_per_lot(Side::Buy, 1.000004, 1.000003, &spec).unwrap_err();
562 let sizing_error = compute_size(
563 &SizingPolicy::FixedRiskAmount { amount: 100.0 },
564 1.0,
565 10_000.0,
566 Side::Buy,
567 1.000004,
568 Some(1.000003),
569 &spec,
570 Some(100.0),
571 )
572 .unwrap_err();
573 assert_eq!(helper_error, sizing_error);
574 }
575
576 #[test]
577 fn fixed_lot_applies_multiplier_before_lot_step() {
578 let result = compute_size(
579 &SizingPolicy::FixedLot { lots: 0.006 },
580 2.0,
581 10_000.0,
582 Side::Buy,
583 1.10000,
584 None,
585 &forex_spec(),
586 None,
587 )
588 .unwrap();
589
590 assert_close(result.scaled_raw_lot, 0.012);
591 assert_eq!(result.final_lot_steps, 1);
592 assert_close(result.final_lot, 0.01);
593 assert_eq!(result.requested_account_risk, None);
594 assert_eq!(result.native_loss_per_lot, None);
595 assert_eq!(result.account_loss_per_lot, None);
596 assert_eq!(result.cap_status, LotCapStatus::NotCapped);
597 }
598
599 #[test]
600 fn invalid_risk_multipliers_are_rejected() {
601 for value in [0.0, -1.0, f64::NAN, f64::INFINITY] {
602 let error = compute_size(
603 &SizingPolicy::FixedLot { lots: 0.01 },
604 value,
605 10_000.0,
606 Side::Buy,
607 1.10000,
608 None,
609 &forex_spec(),
610 None,
611 )
612 .unwrap_err();
613
614 assert!(matches!(error, SizingError::InvalidRiskMultiplier { .. }));
615 }
616 }
617
618 #[test]
619 fn fixed_lot_may_be_stopless_but_monetary_policies_may_not() {
620 let fixed = compute_size(
621 &SizingPolicy::FixedLot { lots: 0.01 },
622 1.0,
623 10_000.0,
624 Side::Buy,
625 1.10000,
626 None,
627 &forex_spec(),
628 None,
629 )
630 .unwrap();
631 assert_eq!(fixed.final_lot_steps, 1);
632
633 for policy in [
634 SizingPolicy::FixedRiskAmount { amount: 100.0 },
635 SizingPolicy::BalanceRiskPercent { percent: 1.0 },
636 ] {
637 let error = compute_size(
638 &policy,
639 1.0,
640 10_000.0,
641 Side::Buy,
642 1.10000,
643 None,
644 &forex_spec(),
645 Some(500.0),
646 )
647 .unwrap_err();
648 assert_eq!(error, SizingError::MissingProtectiveStop);
649 }
650 }
651
652 #[test]
653 fn monetary_policy_requires_positive_account_loss_per_lot() {
654 let missing = compute_size(
655 &SizingPolicy::FixedRiskAmount { amount: 100.0 },
656 1.0,
657 10_000.0,
658 Side::Buy,
659 1.10000,
660 Some(1.09500),
661 &forex_spec(),
662 None,
663 )
664 .unwrap_err();
665 assert_eq!(missing, SizingError::MissingAccountLossPerLot);
666
667 for value in [0.0, -1.0, f64::NAN, f64::INFINITY] {
668 let error = compute_size(
669 &SizingPolicy::FixedRiskAmount { amount: 100.0 },
670 1.0,
671 10_000.0,
672 Side::Buy,
673 1.10000,
674 Some(1.09500),
675 &forex_spec(),
676 Some(value),
677 )
678 .unwrap_err();
679 assert!(matches!(
680 error,
681 SizingError::InvalidAccountLossPerLot { .. }
682 ));
683 }
684 }
685
686 #[test]
687 fn fixed_risk_returns_requested_and_per_lot_audit_values() {
688 let result = compute_size(
689 &SizingPolicy::FixedRiskAmount { amount: 100.0 },
690 2.0,
691 10_000.0,
692 Side::Buy,
693 1.10000,
694 Some(1.09500),
695 &forex_spec(),
696 Some(500.0),
697 )
698 .unwrap();
699
700 assert_eq!(result.requested_account_risk, Some(200.0));
701 assert_eq!(result.native_loss_per_lot, Some(500.0));
702 assert_eq!(result.account_loss_per_lot, Some(500.0));
703 assert_close(result.scaled_raw_lot, 0.4);
704 assert_eq!(result.final_lot_steps, 40);
705 assert_close(result.final_lot, 0.4);
706 }
707
708 #[test]
709 fn balance_percent_uses_realized_balance_before() {
710 let result = compute_size(
711 &SizingPolicy::BalanceRiskPercent { percent: 1.0 },
712 0.5,
713 20_000.0,
714 Side::Buy,
715 1.10000,
716 Some(1.09500),
717 &forex_spec(),
718 Some(500.0),
719 )
720 .unwrap();
721
722 assert_eq!(result.requested_account_risk, Some(100.0));
723 assert_close(result.scaled_raw_lot, 0.2);
724 assert_eq!(result.final_lot_steps, 20);
725 assert_close(result.final_lot, 0.2);
726 }
727
728 #[test]
729 fn all_policies_reject_lots_below_the_minimum() {
730 let cases = [
731 (SizingPolicy::FixedLot { lots: 0.009 }, None),
732 (SizingPolicy::FixedRiskAmount { amount: 4.5 }, Some(500.0)),
733 (
734 SizingPolicy::BalanceRiskPercent { percent: 0.045 },
735 Some(500.0),
736 ),
737 ];
738
739 for (policy, account_loss_per_lot) in cases {
740 let error = compute_size(
741 &policy,
742 1.0,
743 10_000.0,
744 Side::Buy,
745 1.10000,
746 Some(1.09500),
747 &forex_spec(),
748 account_loss_per_lot,
749 )
750 .unwrap_err();
751
752 assert!(matches!(
753 error,
754 SizingError::BelowMinimumLot {
755 floored_lot_steps: 0,
756 minimum_lot_steps: 1,
757 ..
758 }
759 ));
760 }
761 }
762
763 #[test]
764 fn maximum_cap_preserves_raw_lot_and_reports_audit_status() {
765 let mut spec = forex_spec();
766 spec.lot_max_steps = 5;
767
768 let result = compute_size(
769 &SizingPolicy::FixedRiskAmount { amount: 100.0 },
770 1.0,
771 10_000.0,
772 Side::Buy,
773 1.10000,
774 Some(1.09500),
775 &spec,
776 Some(100.0),
777 )
778 .unwrap();
779
780 assert_close(result.scaled_raw_lot, 1.0);
781 assert_eq!(result.requested_account_risk, Some(100.0));
782 assert_eq!(result.final_lot_steps, 5);
783 assert_close(result.final_lot, 0.05);
784 assert_eq!(result.cap_status, LotCapStatus::CappedAtMaximum);
785 }
786
787 #[test]
788 fn geometry_is_checked_before_sub_tick_distance() {
789 let invalid_geometry = compute_size(
790 &SizingPolicy::FixedRiskAmount { amount: 100.0 },
791 1.0,
792 10_000.0,
793 Side::Buy,
794 1.000003,
795 Some(1.000004),
796 &forex_spec(),
797 Some(100.0),
798 )
799 .unwrap_err();
800 assert!(matches!(
801 invalid_geometry,
802 SizingError::InvalidStopGeometry {
803 side: Side::Buy,
804 ..
805 }
806 ));
807
808 let sub_tick = compute_size(
809 &SizingPolicy::FixedRiskAmount { amount: 100.0 },
810 1.0,
811 10_000.0,
812 Side::Buy,
813 1.000004,
814 Some(1.000003),
815 &forex_spec(),
816 Some(100.0),
817 )
818 .unwrap_err();
819 assert_eq!(
820 sub_tick,
821 SizingError::StopDistanceBelowTick {
822 entry_price: 1.000004,
823 stop_price: 1.000003,
824 digits: 5,
825 }
826 );
827 }
828
829 #[test]
830 fn fixed_lot_validates_a_supplied_stop_but_does_not_require_one() {
831 let error = compute_size(
832 &SizingPolicy::FixedLot { lots: 0.01 },
833 1.0,
834 10_000.0,
835 Side::Sell,
836 1.10000,
837 Some(1.09500),
838 &forex_spec(),
839 None,
840 )
841 .unwrap_err();
842
843 assert!(matches!(
844 error,
845 SizingError::InvalidStopGeometry {
846 side: Side::Sell,
847 ..
848 }
849 ));
850 }
851}