Skip to main content

gmo_coin_rs/private/
order.rs

1//! 新規注文APIを実装する。
2
3#![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
19/// 新規注文APIのパス。
20const ORDER_API_PATH: &str = "/v1/order";
21
22/// 新規注文APIから返ってくるレスポンスを格納する構造体。
23#[derive(Deserialize)]
24pub struct Order {
25    /// ステータスコード。
26    pub status: i16,
27
28    /// APIが呼び出された時間。
29    #[serde(deserialize_with = "gmo_timestamp_to_chrono_timestamp")]
30    pub responsetime: DateTime<Utc>,
31
32    /// 注文ID。
33    pub data: String,
34}
35
36impl RestResponse<Order> {
37    /// 注文IDを取得する。
38    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
117/// 新規注文APIを呼び出す。
118pub 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, &parameters)?;
139    let response = http_client.post(url, &headers, &parameters).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}