1use crate::currency::{
8 self, AccountKind, currency_id, legacy_backend_fund_market_id as legacy_fund, trd_market_id,
9};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum RefreshAction {
14 CacheOnly,
15 Forward { reason: &'static str },
16}
17
18#[must_use]
19pub fn decide_refresh_action(
20 refresh_cache_requested: bool,
21 cache_currency_match: bool,
22) -> RefreshAction {
23 if refresh_cache_requested {
24 return RefreshAction::Forward {
25 reason: "user requested refresh_cache=true",
26 };
27 }
28 if !cache_currency_match {
29 return RefreshAction::Forward {
30 reason: "cache currency mismatch (per-currency snapshot missing)",
31 };
32 }
33 RefreshAction::CacheOnly
34}
35
36#[must_use]
37pub fn decide_cache_snapshot_refresh_action(
38 refresh_cache_requested: bool,
39 cache_snapshot_present: bool,
40) -> RefreshAction {
41 if refresh_cache_requested {
42 return RefreshAction::Forward {
43 reason: "user requested refresh_cache=true",
44 };
45 }
46 if !cache_snapshot_present {
47 return RefreshAction::Forward {
48 reason: "cache snapshot missing",
49 };
50 }
51 RefreshAction::CacheOnly
52}
53
54#[must_use]
55pub fn decide_explicit_refresh_action(refresh_cache_requested: bool) -> RefreshAction {
56 if refresh_cache_requested {
57 return RefreshAction::Forward {
58 reason: "user requested refresh_cache=true",
59 };
60 }
61 RefreshAction::CacheOnly
62}
63
64#[must_use]
72pub fn funds_currency_mismatch_warning(
73 requested_currency: Option<i32>,
74 returned_currency: Option<i32>,
75) -> Option<String> {
76 let requested = requested_currency?;
77 let returned = returned_currency?;
78 if returned == 0 || returned == requested {
79 return None;
80 }
81
82 let requested_label = currency::currency_label(requested);
83 let returned_label = currency::currency_label(returned);
84 Some(format!(
85 "currency ignored by backend: requested `{requested_label}` (id={requested}), \
86 returned `{returned_label}` (id={returned}). 此账户按账户基准币种返回资金数据."
87 ))
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub struct GetFundsReadPlan {
92 pub account_kind: AccountKind,
93 pub effective_currency: Option<i32>,
94 pub lookup_currency: Option<i32>,
95}
96
97#[must_use]
107pub fn plan_get_funds_read(
108 requested_currency: Option<i32>,
109 security_firm: Option<i32>,
110 trd_market: Option<i32>,
111 uni_card_num: Option<&str>,
112 trd_market_auth_list: &[i32],
113) -> GetFundsReadPlan {
114 let account_kind = currency::classify_account_with_auth_list(
115 trd_market,
116 security_firm,
117 uni_card_num,
118 trd_market_auth_list,
119 );
120 let effective_currency = currency::effective_get_funds_currency_for_account(
121 requested_currency,
122 security_firm,
123 trd_market,
124 uni_card_num,
125 trd_market_auth_list,
126 );
127 let lookup_currency = match account_kind {
128 AccountKind::Futures | AccountKind::Universal => effective_currency,
129 AccountKind::SingleCurrency => requested_currency,
130 };
131 GetFundsReadPlan {
132 account_kind,
133 effective_currency,
134 lookup_currency,
135 }
136}
137
138#[must_use]
145pub fn derive_top_level_currency(
146 account_kind: AccountKind,
147 effective_currency: Option<i32>,
148 acc_security_firm: Option<i32>,
149 acc_trd_market: Option<i32>,
150 cached_currency: Option<i32>,
151 header_trd_market: i32,
152 trd_env: i32,
153) -> Option<i32> {
154 match account_kind {
155 AccountKind::Futures | AccountKind::Universal => {
156 sim_or_fund_currency_override(trd_env, acc_trd_market)
157 .or(effective_currency)
158 .or(cached_currency)
159 .or_else(|| currency::default_currency_by_security_firm(acc_security_firm))
160 .or_else(|| primary_currency_by_trd_market(acc_trd_market))
161 .or_else(|| primary_currency_by_trd_market(Some(header_trd_market)))
162 }
163 AccountKind::SingleCurrency => sim_or_fund_currency_override(trd_env, acc_trd_market)
164 .or(cached_currency)
165 .or_else(|| currency::default_currency_by_security_firm(acc_security_firm))
166 .or_else(|| primary_currency_by_trd_market(acc_trd_market))
167 .or_else(|| primary_currency_by_trd_market(Some(header_trd_market))),
168 }
169}
170
171#[must_use]
173pub fn derive_top_level_net_cash_power(
174 account_kind: AccountKind,
175 acc_type: Option<i32>,
176 cached_net_cash_power: Option<f64>,
177) -> Option<f64> {
178 match account_kind {
179 AccountKind::Futures => None,
180 AccountKind::Universal => {
181 if acc_type == Some(2) { Some(0.0) } else { None }
183 }
184 AccountKind::SingleCurrency => cached_net_cash_power,
185 }
186}
187
188fn primary_currency_by_trd_market(market: Option<i32>) -> Option<i32> {
189 match market? {
190 trd_market_id::HK | trd_market_id::HKCC | trd_market_id::FUTURES => Some(currency_id::HKD),
191 trd_market_id::US => Some(currency_id::USD),
192 trd_market_id::CN => Some(currency_id::CNH),
193 trd_market_id::SG => Some(currency_id::SGD),
194 trd_market_id::AU => Some(currency_id::AUD),
195 trd_market_id::JP => Some(currency_id::JPY),
196 trd_market_id::MY => Some(currency_id::MYR),
197 trd_market_id::CA => Some(currency_id::CAD),
198 _ => None,
199 }
200}
201
202#[must_use]
208pub fn sim_or_fund_currency_override(trd_env: i32, market: Option<i32>) -> Option<i32> {
209 let market = market?;
210 if trd_env == 0 {
211 match market {
212 trd_market_id::FUTURES_SIMULATE_HK => return Some(currency_id::HKD),
213 trd_market_id::FUTURES_SIMULATE_US => return Some(currency_id::USD),
214 trd_market_id::FUTURES_SIMULATE_SG => return Some(currency_id::SGD),
215 trd_market_id::FUTURES_SIMULATE_JP => return Some(currency_id::JPY),
216 _ => {}
217 }
218 }
219
220 match market {
221 trd_market_id::HK_FUND | legacy_fund::HK_FUND => Some(currency_id::HKD),
222 trd_market_id::US_FUND | legacy_fund::US_FUND_OLD | legacy_fund::US_FUND => {
223 Some(currency_id::USD)
224 }
225 trd_market_id::SG_FUND | legacy_fund::SG_FUND => Some(currency_id::SGD),
226 trd_market_id::MY_FUND => Some(currency_id::MYR),
227 trd_market_id::JP_FUND => Some(currency_id::JPY),
228 _ => None,
229 }
230}
231
232#[cfg(test)]
233mod tests;