Skip to main content

korea_investment_api/
lib.rs

1mod auth;
2mod stock;
3pub mod types;
4pub(crate) mod util;
5
6#[macro_use]
7extern crate log;
8
9pub const BUF_SIZE: usize = 4096;
10pub const VIRTUAL_FREQ: i64 = 2;
11pub const REAL_FREQ: i64 = 20;
12lazy_static::lazy_static! {
13    pub static ref LAST_CALL: std::sync::Mutex<types::Time> = std::sync::Mutex::new(types::Time::now());
14}
15
16pub(crate) async fn wait(env: types::Environment) {
17    let freq = if env == types::Environment::Virtual {
18        VIRTUAL_FREQ
19    } else {
20        REAL_FREQ
21    };
22    let sleep_time = (LAST_CALL.lock().unwrap().inner().timestamp_millis() + 1000 / freq
23        - chrono::Utc::now().timestamp_millis())
24    .max(0) as u64;
25    tokio::time::sleep(std::time::Duration::from_millis(sleep_time)).await;
26}
27pub(crate) fn update_last_call() {
28    *LAST_CALL.lock().unwrap() = types::Time::now();
29}
30
31#[derive(Debug)]
32pub struct KoreaInvestmentApi {
33    pub auth: auth::Auth,
34    pub real_auth: auth::Auth,
35    pub order: stock::order::Korea,
36    pub quote: stock::quote::Quote,
37    pub k_data: stock::data::KoreaStockData,
38}
39
40impl KoreaInvestmentApi {
41    pub async fn new(
42        acc: types::Environment,
43        appkey: &str,
44        appsecret: &str,
45        account: types::Account,
46        hts_id: &str,
47        token: Option<String>,
48        approval_key: Option<String>,
49        real_appkey: Option<String>,
50        real_appsecret: Option<String>,
51        real_token: Option<String>,
52        real_approval_key: Option<String>,
53    ) -> Result<KoreaInvestmentApi, Error> {
54        let client = reqwest::Client::new();
55        let mut auth = auth::Auth::new(&client, acc.clone(), appkey, appsecret);
56        info!(
57            "Authorizing: acc={}, appkey={}, appsecret={}",
58            &acc, &appkey, &appsecret,
59        );
60        if let Some(token) = token {
61            auth.set_token(token);
62        } else {
63            auth.create_token().await?;
64        }
65        debug!("token: {:?}", auth.get_token());
66        if let Some(approval_key) = approval_key {
67            auth.set_approval_key(approval_key);
68        } else {
69            auth.create_approval_key().await?;
70        }
71        debug!("approval_key: {:?}", auth.get_approval_key());
72
73        let mut real_auth = if acc == types::Environment::Virtual {
74            auth::Auth::new(
75                &client,
76                types::Environment::Real,
77                &real_appkey.unwrap(),
78                &real_appsecret.unwrap(),
79            )
80        } else {
81            auth.clone()
82        };
83        if let Some(token) = real_token {
84            real_auth.set_token(token);
85        } else if real_auth.get_token().is_none() {
86            real_auth.create_token().await?;
87        }
88        debug!("실전투자계좌(view용) token: {:?}", auth.get_token());
89        if let Some(approval_key) = real_approval_key {
90            real_auth.set_approval_key(approval_key);
91        } else if real_auth.get_approval_key().is_none() {
92            real_auth.create_approval_key().await?;
93        }
94        debug!(
95            "실전투자계좌(view용) approval_key: {:?}",
96            auth.get_approval_key()
97        );
98
99        let order = stock::order::Korea::new(&client, acc.clone(), auth.clone(), account.clone())?;
100        let quote = stock::quote::Quote::new(
101            &client,
102            acc.clone(),
103            auth.clone(),
104            account.clone(),
105            real_auth.clone(),
106        )?;
107        let k_data =
108            stock::data::KoreaStockData::new(acc.clone(), auth.clone(), account.clone(), hts_id)
109                .await?;
110        info!("API Ready");
111        Ok(Self {
112            auth,
113            real_auth,
114            order,
115            quote,
116            k_data,
117        })
118    }
119
120    pub fn export_config(&self, config: &types::config::Config) -> Result<(), Error> {
121        let mut config = config.clone();
122        config.set_approval_key(self.auth.get_approval_key());
123        config.set_token(self.auth.get_token());
124        config.set_real_approval_key(self.real_auth.get_approval_key());
125        config.set_real_token(self.real_auth.get_token());
126        let toml = toml::to_string(&config)?;
127        std::fs::write("config.toml", toml)?;
128
129        Ok(())
130    }
131}
132
133#[derive(thiserror::Error, Debug)]
134pub enum Error {
135    // from lib
136    #[error(transparent)]
137    WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
138    #[error(transparent)]
139    ReqwestError(#[from] reqwest::Error),
140    #[error(transparent)]
141    Utf8Error(#[from] std::str::Utf8Error),
142    #[error(transparent)]
143    JsonError(#[from] json::JsonError),
144    #[error(transparent)]
145    ChronoError(#[from] chrono::ParseError),
146    #[error(transparent)]
147    ParseIntError(#[from] std::num::ParseIntError),
148    #[error(transparent)]
149    ParseFloatError(#[from] std::num::ParseFloatError),
150    #[error(transparent)]
151    Base64DecodeError(#[from] base64::DecodeError),
152    #[error(transparent)]
153    UrlParseError(#[from] url::ParseError),
154    #[error(transparent)]
155    TomlSerializeError(#[from] toml::ser::Error),
156    #[error(transparent)]
157    IoError(#[from] std::io::Error),
158    // custom
159    #[error("Auth init failed - None value in {0}")]
160    AuthInitFailed(&'static str),
161    #[error("Broken protocol - {0}: {1}")]
162    BrokenProtocol(&'static str, String),
163    #[error("The remote websocket server sent invalid data")]
164    InvalidData,
165    #[error("Wrong TrId: {0:?}. Expect {1}")]
166    WrongTrId(crate::types::TrId, &'static str),
167    #[error("AES cipher length error")]
168    AesInvalidLength,
169    #[error("AES decrypt error: {0}")]
170    AesDecryptError(String),
171    #[error("Not supported: {0}")]
172    NotSupported(&'static str),
173}