1#![allow(clippy::too_many_arguments)]
4
5use crate::end_point::*;
6use crate::error::Error;
7use crate::execution_type::ExecutionType;
8use crate::headers::Headers;
9use crate::http_client::*;
10use crate::json::*;
11use crate::response::*;
12use crate::side::Side;
13use crate::symbol::Symbol;
14use crate::time_in_force::TimeInForce;
15use chrono::{DateTime, Utc};
16use serde::Deserialize;
17use serde_json::{json, Value};
18
19const ORDER_API_PATH: &str = "/v1/order";
21
22#[derive(Deserialize)]
24pub struct Order {
25 pub status: i16,
27
28 #[serde(deserialize_with = "gmo_timestamp_to_chrono_timestamp")]
30 pub responsetime: DateTime<Utc>,
31
32 pub data: String,
34}
35
36impl RestResponse<Order> {
37 pub fn order_id(&self) -> &str {
39 &self.body.data
40 }
41}
42
43fn build_parameters(
44 execution_type: &ExecutionType,
45 symbol: &Symbol,
46 side: &Side,
47 size: f64,
48 time_in_force: &TimeInForce,
49 price: Option<i64>,
50 losscut_price: Option<i64>,
51) -> Result<Value, Error> {
52 Ok(match execution_type {
53 ExecutionType::Market => {
54 build_market_parameters(&execution_type, &symbol, &side, size, &time_in_force)
55 }
56 _ => match price {
57 Some(p) => build_limit_or_stop_paramters(
58 &execution_type,
59 &symbol,
60 &side,
61 size,
62 &time_in_force,
63 p,
64 losscut_price,
65 ),
66 None => return Err(Error::PriceNotSpecifiedError()),
67 },
68 })
69}
70
71fn build_market_parameters(
72 execution_type: &ExecutionType,
73 symbol: &Symbol,
74 side: &Side,
75 size: f64,
76 time_in_force: &TimeInForce,
77) -> Value {
78 json!({
79 "executionType": execution_type.to_string(),
80 "symbol": symbol.to_string(),
81 "side": side.to_string(),
82 "size": size.to_string(),
83 "timeInForce": time_in_force.to_string(),
84 })
85}
86
87fn build_limit_or_stop_paramters(
88 execution_type: &ExecutionType,
89 symbol: &Symbol,
90 side: &Side,
91 size: f64,
92 time_in_force: &TimeInForce,
93 price: i64,
94 losscut_price: Option<i64>,
95) -> Value {
96 match losscut_price {
97 Some(lp) => json!({
98 "symbol": symbol.to_string(),
99 "side": side.to_string(),
100 "executionType": execution_type.to_string(),
101 "size": size.to_string(),
102 "price": price.to_string(),
103 "timeInForce": time_in_force.to_string(),
104 "losscutPrice": lp,
105 }),
106 None => json!({
107 "symbol": symbol.to_string(),
108 "side": side.to_string(),
109 "executionType": execution_type.to_string(),
110 "size": size.to_string(),
111 "price": price.to_string(),
112 "timeInForce": time_in_force.to_string(),
113 }),
114 }
115}
116
117pub async fn request_order(
119 http_client: &impl HttpClient,
120 execution_type: &ExecutionType,
121 symbol: &Symbol,
122 side: &Side,
123 size: f64,
124 time_in_force: &TimeInForce,
125 price: Option<i64>,
126 losscut_price: Option<i64>,
127) -> Result<RestResponse<Order>, Error> {
128 let url = format!("{}{}", PRIVATE_ENDPOINT, ORDER_API_PATH,);
129 let parameters = build_parameters(
130 &execution_type,
131 &symbol,
132 &side,
133 size,
134 &time_in_force,
135 price,
136 losscut_price,
137 )?;
138 let headers = Headers::create_post_headers(&ORDER_API_PATH, ¶meters)?;
139 let response = http_client.post(url, &headers, ¶meters).await?;
140 parse_from_http_response::<Order>(&response)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::http_client::tests::InmemClient;
147 use chrono::SecondsFormat;
148
149 const SAMPLE_RESPONSE: &str = r#"
150 {
151 "status": 0,
152 "data": "637000",
153 "responsetime": "2019-03-19T02:15:06.108Z"
154 }
155 "#;
156
157 #[tokio::test]
158 async fn test_market_order() {
159 let body = SAMPLE_RESPONSE;
160 let http_client = InmemClient {
161 http_status_code: 200,
162 body_text: body.to_string(),
163 return_error: false,
164 };
165 let resp = request_order(
166 &http_client,
167 &ExecutionType::Market,
168 &Symbol::BtcJpy,
169 &Side::Buy,
170 0.1,
171 &TimeInForce::Fak,
172 None,
173 None,
174 )
175 .await
176 .unwrap();
177 assert_eq!(resp.http_status_code, 200);
178 assert_eq!(resp.body.status, 0);
179 assert_eq!(
180 resp.body
181 .responsetime
182 .to_rfc3339_opts(SecondsFormat::Millis, true),
183 "2019-03-19T02:15:06.108Z"
184 );
185 assert_eq!(resp.order_id(), "637000");
186 }
187
188 #[tokio::test]
189 async fn test_limit_order() {
190 let body = SAMPLE_RESPONSE;
191 let http_client = InmemClient {
192 http_status_code: 200,
193 body_text: body.to_string(),
194 return_error: false,
195 };
196 let resp = request_order(
197 &http_client,
198 &ExecutionType::Limit,
199 &Symbol::BtcJpy,
200 &Side::Buy,
201 0.1,
202 &TimeInForce::Fas,
203 Some(100),
204 Some(100),
205 )
206 .await
207 .unwrap();
208 assert_eq!(resp.http_status_code, 200);
209 assert_eq!(resp.body.status, 0);
210 assert_eq!(
211 resp.body
212 .responsetime
213 .to_rfc3339_opts(SecondsFormat::Millis, true),
214 "2019-03-19T02:15:06.108Z"
215 );
216 assert_eq!(resp.order_id(), "637000");
217 }
218
219 #[tokio::test]
220 async fn test_stop_order() {
221 let body = SAMPLE_RESPONSE;
222 let http_client = InmemClient {
223 http_status_code: 200,
224 body_text: body.to_string(),
225 return_error: false,
226 };
227 let resp = request_order(
228 &http_client,
229 &ExecutionType::Stop,
230 &Symbol::BtcJpy,
231 &Side::Buy,
232 0.1,
233 &TimeInForce::Fas,
234 Some(100),
235 None,
236 )
237 .await
238 .unwrap();
239 assert_eq!(resp.http_status_code, 200);
240 assert_eq!(resp.body.status, 0);
241 assert_eq!(
242 resp.body
243 .responsetime
244 .to_rfc3339_opts(SecondsFormat::Millis, true),
245 "2019-03-19T02:15:06.108Z"
246 );
247 assert_eq!(resp.order_id(), "637000");
248 }
249}