korea_investment_api/
lib.rs1mod 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 #[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 #[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}