1use chrono::{DateTime, Utc};
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(thiserror::Error, Debug)]
10pub enum Error {
11 #[error("Candle data is empty: backtesting requires at least one candle")]
15 CandleDataEmpty,
16
17 #[error("Candle not found")]
19 CandleNotFound,
20
21 #[error("The Aggregator factor is invalid")]
23 InvalidFactor,
24
25 #[error("Missing required field: {0}")]
27 MissingField(&'static str),
28
29 #[error("Invalid price order: open={0}, low={1}, high={2}, close={3}")]
31 InvalidPriceOrder(f64, f64, f64, f64),
32
33 #[error("Volume cannot be negative (got: {0})")]
35 NegativeVolume(f64),
36
37 #[error("Invalid time order: open={0}, close={1}")]
39 InvalideTimes(DateTime<Utc>, DateTime<Utc>),
40
41 #[error("Balance must be positive (got: {0})")]
46 NegZeroBalance(f64),
47
48 #[error("Insufficient funds: required {0}, available {1}")]
54 InsufficientFunds(f64, f64),
55
56 #[error("Negative free balance: balance={0}, locked={1}")]
62 NegFreeBalance(f64, f64),
63
64 #[error("Negative fees")]
66 NegZeroFees,
67
68 #[error("Locked funds {0} are insufficient for amount {1}")]
74 UnlockBalance(f64, f64),
75
76 #[error("Order not found")]
78 OrderNotFound,
79
80 #[error("Failed to remove order")]
82 RemoveOrder,
83
84 #[error("Position not found")]
86 PositionNotFound,
87
88 #[error("Failed to remove position")]
90 RemovePosition,
91
92 #[error("Invalid exit price {0}")]
94 ExitPrice(f64),
95
96 #[error("{0}")]
101 Msg(String),
102
103 #[error("TakeProfit or StopLoss must be positive")]
105 NegTakeProfitAndStopLoss,
106
107 #[error("TrailingStop must be positive and greater than 0")]
109 NegZeroTrailingStop,
110
111 #[error("Try another order type")]
115 MismatchedOrderType,
116
117 #[cfg(feature = "draws")]
122 #[error("{0}")]
123 Plotters(String),
124
125 #[cfg(feature = "draws")]
130 #[error("{0}")]
131 Charming(#[from] charming::EchartsError),
132}
133
134#[cfg(feature = "serde")]
135impl serde::Serialize for Error {
136 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
137 where
138 S: serde::Serializer,
139 {
140 serializer.serialize_str(self.to_string().as_ref())
141 }
142}
143
144#[cfg(feature = "serde")]
145impl<'de> serde::Deserialize<'de> for Error {
146 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
147 where
148 D: serde::Deserializer<'de>,
149 {
150 #[derive(serde::Deserialize)]
152 #[serde(tag = "type", content = "data")]
153 enum ErrorWrapper {
154 CandleDataEmpty,
155 CandleNotFound,
156 InvalidFactor,
157 MissingField {
158 field: String,
159 },
160 InvalidPriceOrder {
161 open: f64,
162 low: f64,
163 high: f64,
164 close: f64,
165 },
166 NegativeVolume {
167 volume: f64,
168 },
169 InvalideTimes {
170 open: i64,
171 close: i64,
172 },
173 NegZeroBalance {
174 balance: f64,
175 },
176 InsufficientFunds {
177 required: f64,
178 available: f64,
179 },
180 NegFreeBalance {
181 balance: f64,
182 locked: f64,
183 },
184 NegZeroFees,
185 UnlockBalance {
186 locked: f64,
187 amount: f64,
188 },
189 OrderNotFound,
190 RemoveOrder,
191 PositionNotFound,
192 RemovePosition,
193 ExitPrice {
194 price: f64,
195 },
196 Msg {
197 message: String,
198 },
199 NegTakeProfitAndStopLoss,
200 NegZeroTrailingStop,
201 MismatchedOrderType,
202 #[cfg(feature = "draws")]
203 Plotters {
204 error: String,
205 },
206 #[cfg(feature = "draws")]
207 Charming {
208 error: String,
209 },
210 }
211
212 let wrapper = ErrorWrapper::deserialize(deserializer)?;
214
215 Ok(match wrapper {
217 ErrorWrapper::CandleDataEmpty => Error::CandleDataEmpty,
218 ErrorWrapper::CandleNotFound => Error::CandleNotFound,
219 ErrorWrapper::InvalidFactor => Error::InvalidFactor,
220 ErrorWrapper::MissingField { field } => Error::MissingField(Box::leak(field.into_boxed_str())),
221 ErrorWrapper::InvalidPriceOrder { open, low, high, close } => {
222 Error::InvalidPriceOrder(open, low, high, close)
223 }
224 ErrorWrapper::NegativeVolume { volume } => Error::NegativeVolume(volume),
225 ErrorWrapper::InvalideTimes { open, close } => {
226 let open_dt = DateTime::from_timestamp_millis(open).unwrap_or(Utc::now());
227 let close_dt = DateTime::from_timestamp_millis(close).unwrap_or(Utc::now());
228 Error::InvalideTimes(open_dt, close_dt)
229 }
230 ErrorWrapper::NegZeroBalance { balance } => Error::NegZeroBalance(balance),
231 ErrorWrapper::InsufficientFunds { required, available } => Error::InsufficientFunds(required, available),
232 ErrorWrapper::NegFreeBalance { balance, locked } => Error::NegFreeBalance(balance, locked),
233 ErrorWrapper::NegZeroFees => Error::NegZeroFees,
234 ErrorWrapper::UnlockBalance { locked, amount } => Error::UnlockBalance(locked, amount),
235 ErrorWrapper::OrderNotFound => Error::OrderNotFound,
236 ErrorWrapper::RemoveOrder => Error::RemoveOrder,
237 ErrorWrapper::PositionNotFound => Error::PositionNotFound,
238 ErrorWrapper::RemovePosition => Error::RemovePosition,
239 ErrorWrapper::ExitPrice { price } => Error::ExitPrice(price),
240 ErrorWrapper::Msg { message } => Error::Msg(message),
241 ErrorWrapper::NegTakeProfitAndStopLoss => Error::NegTakeProfitAndStopLoss,
242 ErrorWrapper::NegZeroTrailingStop => Error::NegZeroTrailingStop,
243 ErrorWrapper::MismatchedOrderType => Error::MismatchedOrderType,
244 #[cfg(feature = "draws")]
245 ErrorWrapper::Plotters { error } => Error::Plotters(error),
246 #[cfg(feature = "draws")]
247 ErrorWrapper::Charming { error } => Error::Charming(charming::EchartsError::HtmlRenderingError(error)),
248 })
249 }
250}