rust_okx/api/account/requests/
risk.rs1use serde::Serialize;
2
3use crate::model::{
4 InstType, RequestValidationError, ValidateRequest, at_least_one, non_empty, optional_non_empty,
5 optional_one_of, optional_positive_decimal_string,
6};
7
8#[derive(Debug, Clone, Serialize)]
10pub struct SimulatedPosition {
11 #[serde(rename = "instId")]
12 inst_id: String,
13 #[serde(skip_serializing_if = "Option::is_none")]
14 pos: Option<String>,
15 #[serde(rename = "avgPx", skip_serializing_if = "Option::is_none")]
16 avg_px: Option<String>,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 lever: Option<String>,
19}
20
21impl SimulatedPosition {
22 pub fn new(inst_id: impl Into<String>) -> Self {
24 Self {
25 inst_id: inst_id.into(),
26 pos: None,
27 avg_px: None,
28 lever: None,
29 }
30 }
31
32 pub fn position(mut self, pos: impl Into<String>) -> Self {
34 self.pos = Some(pos.into());
35 self
36 }
37
38 pub fn average_price(mut self, avg_px: impl Into<String>) -> Self {
40 self.avg_px = Some(avg_px.into());
41 self
42 }
43
44 pub fn leverage(mut self, lever: impl Into<String>) -> Self {
46 self.lever = Some(lever.into());
47 self
48 }
49}
50
51#[derive(Debug, Clone, Serialize)]
53pub struct SimulatedAsset {
54 ccy: String,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 eq: Option<String>,
57}
58
59impl SimulatedAsset {
60 pub fn new(ccy: impl Into<String>) -> Self {
62 Self {
63 ccy: ccy.into(),
64 eq: None,
65 }
66 }
67
68 pub fn equity(mut self, eq: impl Into<String>) -> Self {
70 self.eq = Some(eq.into());
71 self
72 }
73}
74
75#[derive(Debug, Clone, Default, Serialize)]
77pub struct SimulatedMarginRequest {
78 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
79 inst_type: Option<InstType>,
80 #[serde(rename = "inclRealPos", skip_serializing_if = "Option::is_none")]
81 include_real_positions: Option<bool>,
82 #[serde(rename = "spotOffsetType", skip_serializing_if = "Option::is_none")]
83 spot_offset_type: Option<String>,
84 #[serde(rename = "simPos", skip_serializing_if = "Option::is_none")]
85 simulated_positions: Option<Vec<SimulatedPosition>>,
86}
87
88impl SimulatedMarginRequest {
89 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn inst_type(mut self, inst_type: InstType) -> Self {
96 self.inst_type = Some(inst_type);
97 self
98 }
99
100 pub fn include_real_positions(mut self, include_real_positions: bool) -> Self {
102 self.include_real_positions = Some(include_real_positions);
103 self
104 }
105
106 pub fn spot_offset_type(mut self, spot_offset_type: impl Into<String>) -> Self {
108 self.spot_offset_type = Some(spot_offset_type.into());
109 self
110 }
111
112 pub fn simulated_positions(mut self, simulated_positions: Vec<SimulatedPosition>) -> Self {
114 self.simulated_positions = Some(simulated_positions);
115 self
116 }
117}
118
119#[derive(Debug, Clone, Default, Serialize)]
121pub struct AccountPositionTiersRequest {
122 #[serde(rename = "instType", skip_serializing_if = "Option::is_none")]
123 inst_type: Option<InstType>,
124 #[serde(rename = "uly", skip_serializing_if = "Option::is_none")]
125 underlying: Option<String>,
126 #[serde(rename = "instFamily", skip_serializing_if = "Option::is_none")]
127 inst_family: Option<String>,
128}
129
130impl AccountPositionTiersRequest {
131 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub fn inst_type(mut self, inst_type: InstType) -> Self {
138 self.inst_type = Some(inst_type);
139 self
140 }
141
142 pub fn underlying(mut self, underlying: impl Into<String>) -> Self {
144 self.underlying = Some(underlying.into());
145 self
146 }
147
148 pub fn inst_family(mut self, inst_family: impl Into<String>) -> Self {
150 self.inst_family = Some(inst_family.into());
151 self
152 }
153}
154
155#[derive(Debug, Clone, Default, Serialize)]
157pub struct PositionBuilderRequest {
158 #[serde(rename = "acctLv", skip_serializing_if = "Option::is_none")]
159 acct_lv: Option<String>,
160 #[serde(rename = "inclRealPosAndEq", skip_serializing_if = "Option::is_none")]
161 include_real_positions_and_equity: Option<bool>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 lever: Option<String>,
164 #[serde(rename = "greeksType", skip_serializing_if = "Option::is_none")]
165 greeks_type: Option<String>,
166 #[serde(rename = "simPos", skip_serializing_if = "Option::is_none")]
167 simulated_positions: Option<Vec<SimulatedPosition>>,
168 #[serde(rename = "simAsset", skip_serializing_if = "Option::is_none")]
169 simulated_assets: Option<Vec<SimulatedAsset>>,
170 #[serde(rename = "idxVol", skip_serializing_if = "Option::is_none")]
171 index_volatility: Option<String>,
172}
173
174impl PositionBuilderRequest {
175 pub fn new() -> Self {
177 Self::default()
178 }
179
180 pub fn account_level(mut self, acct_lv: impl Into<String>) -> Self {
182 self.acct_lv = Some(acct_lv.into());
183 self
184 }
185
186 pub fn include_real_positions_and_equity(mut self, include: bool) -> Self {
188 self.include_real_positions_and_equity = Some(include);
189 self
190 }
191
192 pub fn leverage(mut self, lever: impl Into<String>) -> Self {
194 self.lever = Some(lever.into());
195 self
196 }
197
198 pub fn greeks_type(mut self, greeks_type: impl Into<String>) -> Self {
200 self.greeks_type = Some(greeks_type.into());
201 self
202 }
203
204 pub fn simulated_positions(mut self, simulated_positions: Vec<SimulatedPosition>) -> Self {
206 self.simulated_positions = Some(simulated_positions);
207 self
208 }
209
210 pub fn simulated_assets(mut self, simulated_assets: Vec<SimulatedAsset>) -> Self {
212 self.simulated_assets = Some(simulated_assets);
213 self
214 }
215
216 pub fn index_volatility(mut self, index_volatility: impl Into<String>) -> Self {
218 self.index_volatility = Some(index_volatility.into());
219 self
220 }
221}
222
223impl SimulatedPosition {
224 fn validate(&self) -> Result<(), RequestValidationError> {
225 non_empty("simPos.instId", &self.inst_id)?;
226 optional_non_empty("simPos.pos", self.pos.as_deref())?;
227 optional_positive_decimal_string("simPos.avgPx", self.avg_px.as_deref())?;
228 optional_positive_decimal_string("simPos.lever", self.lever.as_deref())?;
229 Ok(())
230 }
231}
232
233impl SimulatedAsset {
234 fn validate(&self) -> Result<(), RequestValidationError> {
235 non_empty("simAsset.ccy", &self.ccy)?;
236 optional_non_empty("simAsset.eq", self.eq.as_deref())
237 }
238}
239
240impl ValidateRequest for SimulatedMarginRequest {
241 fn validate(&self) -> Result<(), RequestValidationError> {
242 if matches!(self.inst_type, Some(InstType::Unknown(_))) {
243 return Err(RequestValidationError::InvalidFormat {
244 field: "instType",
245 expected: "SPOT, MARGIN, SWAP, FUTURES, OPTION, or EVENTS",
246 });
247 }
248 optional_one_of(
249 "spotOffsetType",
250 self.spot_offset_type.as_deref(),
251 &["1", "2", "3"],
252 "1, 2, or 3",
253 )?;
254 if self.include_real_positions == Some(false) && self.simulated_positions.is_none() {
255 return Err(RequestValidationError::RequiredWhen {
256 field: "simPos",
257 condition: "inclRealPos is false",
258 });
259 }
260 if let Some(positions) = &self.simulated_positions {
261 if positions.is_empty() {
262 return Err(RequestValidationError::EmptyField { field: "simPos" });
263 }
264 for position in positions {
265 position.validate()?;
266 }
267 }
268 Ok(())
269 }
270}
271
272impl ValidateRequest for AccountPositionTiersRequest {
273 fn validate(&self) -> Result<(), RequestValidationError> {
274 match &self.inst_type {
275 Some(InstType::Swap | InstType::Futures | InstType::Option) => {}
276 Some(_) => {
277 return Err(RequestValidationError::InvalidFormat {
278 field: "instType",
279 expected: "SWAP, FUTURES, or OPTION",
280 });
281 }
282 None => {
283 return Err(RequestValidationError::RequiredWhen {
284 field: "instType",
285 condition: "querying account position tiers",
286 });
287 }
288 }
289 optional_non_empty("uly", self.underlying.as_deref())?;
290 optional_non_empty("instFamily", self.inst_family.as_deref())?;
291 at_least_one(
292 "uly, instFamily",
293 &[self.underlying.is_some(), self.inst_family.is_some()],
294 )
295 }
296}
297
298impl ValidateRequest for PositionBuilderRequest {
299 fn validate(&self) -> Result<(), RequestValidationError> {
300 optional_one_of(
301 "acctLv",
302 self.acct_lv.as_deref(),
303 &["1", "2", "3", "4"],
304 "1, 2, 3, or 4",
305 )?;
306 optional_positive_decimal_string("lever", self.lever.as_deref())?;
307 optional_one_of(
308 "greeksType",
309 self.greeks_type.as_deref(),
310 &["PA", "BS"],
311 "PA or BS",
312 )?;
313 optional_positive_decimal_string("idxVol", self.index_volatility.as_deref())?;
314 if let Some(positions) = &self.simulated_positions {
315 if positions.is_empty() {
316 return Err(RequestValidationError::EmptyField { field: "simPos" });
317 }
318 for position in positions {
319 position.validate()?;
320 }
321 }
322 if let Some(assets) = &self.simulated_assets {
323 if assets.is_empty() {
324 return Err(RequestValidationError::EmptyField { field: "simAsset" });
325 }
326 for asset in assets {
327 asset.validate()?;
328 }
329 }
330 if self.include_real_positions_and_equity != Some(true) {
331 at_least_one(
332 "simPos, simAsset",
333 &[
334 self.simulated_positions.is_some(),
335 self.simulated_assets.is_some(),
336 ],
337 )?;
338 }
339 Ok(())
340 }
341}