use async_trait::async_trait;
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use std::{
collections::HashMap,
sync::{Arc, RwLock as SyncRwLock},
time::Instant,
};
use tokio::sync::RwLock;
use uuid::Uuid;
use binary_options_tools_core_pre::{
reimports::{AsyncSender, Message},
traits::AppState,
};
use crate::pocketoption::types::ServerTimeState;
use crate::pocketoption::types::{
Action, Assets, Deal, OpenOrder, Outgoing, PendingOrder, SubscriptionEvent,
};
use crate::pocketoption::{
error::{PocketError, PocketResult},
ssid::Ssid,
};
use crate::validator::Validator;
pub struct State {
pub ssid: Ssid,
pub default_connection_url: Option<String>,
pub default_symbol: String,
pub balance: RwLock<Option<Decimal>>,
pub server_time: ServerTimeState,
pub assets: RwLock<Option<Assets>>,
pub trade_state: Arc<TradeState>,
pub raw_validators: SyncRwLock<HashMap<Uuid, Arc<Validator>>>,
pub active_subscriptions: RwLock<
HashMap<
String,
(
AsyncSender<SubscriptionEvent>,
crate::pocketoption::candle::SubscriptionType,
),
>,
>,
pub histories: RwLock<Vec<(String, u32, Uuid)>>,
pub raw_sinks: RwLock<HashMap<Uuid, Arc<AsyncSender<Arc<Message>>>>>,
pub raw_keep_alive: Arc<RwLock<HashMap<Uuid, Outgoing>>>,
pub urls: Vec<String>,
}
#[derive(Default)]
pub struct StateBuilder {
ssid: Option<Ssid>,
default_connection_url: Option<String>,
default_symbol: Option<String>,
urls: Vec<String>,
}
impl StateBuilder {
pub fn ssid(mut self, ssid: Ssid) -> Self {
self.ssid = Some(ssid);
self
}
pub fn default_connection_url(mut self, url: String) -> Self {
self.default_connection_url = Some(url);
self
}
pub fn default_symbol(mut self, symbol: String) -> Self {
self.default_symbol = Some(symbol);
self
}
pub fn urls(mut self, urls: Vec<String>) -> Self {
self.urls = urls;
self
}
pub fn build(self) -> PocketResult<State> {
Ok(State {
ssid: self
.ssid
.ok_or(PocketError::StateBuilder("SSID is required".into()))?,
default_connection_url: self.default_connection_url,
default_symbol: self
.default_symbol
.unwrap_or_else(|| "EURUSD_otc".to_string()),
balance: RwLock::new(None),
server_time: ServerTimeState::default(),
assets: RwLock::new(None),
trade_state: Arc::new(TradeState::default()),
raw_validators: SyncRwLock::new(HashMap::new()),
active_subscriptions: RwLock::new(HashMap::new()),
histories: RwLock::new(Vec::new()),
raw_sinks: RwLock::new(HashMap::new()),
raw_keep_alive: Arc::new(RwLock::new(HashMap::new())),
urls: self.urls,
})
}
}
#[async_trait]
impl AppState for State {
async fn clear_temporal_data(&self) {
let mut balance = self.balance.write().await;
*balance = None;
self.trade_state.clear_opened_deals().await;
self.active_subscriptions.write().await.clear();
self.clear_raw_validators();
}
}
impl State {
pub async fn set_balance(&self, balance: Decimal) {
let mut state = self.balance.write().await;
*state = Some(balance);
}
pub async fn get_balance(&self) -> Option<Decimal> {
let state = self.balance.read().await;
*state
}
pub fn is_demo(&self) -> bool {
self.ssid.demo()
}
pub async fn get_server_time(&self) -> i64 {
self.server_time.read().await.get_server_time()
}
pub async fn update_server_time(&self, timestamp: i64) {
self.server_time.write().await.update(timestamp);
}
pub async fn is_server_time_stale(&self) -> bool {
self.server_time.read().await.is_stale()
}
pub async fn get_server_datetime(&self) -> DateTime<Utc> {
let timestamp = self.get_server_time().await;
DateTime::from_timestamp(timestamp, 0).unwrap_or_else(Utc::now)
}
pub async fn local_to_server(&self, local_time: DateTime<Utc>) -> i64 {
self.server_time.read().await.local_to_server(local_time)
}
pub async fn server_to_local(&self, server_timestamp: i64) -> DateTime<Utc> {
self.server_time
.read()
.await
.server_to_local(server_timestamp)
}
pub async fn set_assets(&self, assets: Assets) {
let mut state = self.assets.write().await;
*state = Some(assets);
}
pub fn add_raw_validator(&self, id: Uuid, validator: Validator) {
self.raw_validators
.write()
.unwrap()
.insert(id, Arc::new(validator));
}
pub fn remove_raw_validator(&self, id: &Uuid) -> bool {
self.raw_validators.write().unwrap().remove(id).is_some()
}
pub fn clear_raw_validators(&self) {
self.raw_validators.write().unwrap().clear();
}
}
type RecentTradeKey = (String, Action, u32, Decimal);
#[derive(Debug, Default)]
pub struct TradeState {
pub opened_deals: RwLock<HashMap<Uuid, Deal>>,
pub closed_deals: RwLock<HashMap<Uuid, Deal>>,
pub pending_deals: RwLock<HashMap<Uuid, PendingOrder>>,
pub pending_market_orders: RwLock<HashMap<Uuid, (OpenOrder, Instant)>>,
pub recent_trades: RwLock<HashMap<RecentTradeKey, (Uuid, Instant)>>,
}
impl TradeState {
pub async fn add_opened_deal(&self, deal: Deal) {
self.opened_deals.write().await.insert(deal.id, deal);
}
pub async fn add_pending_deal(&self, deal: PendingOrder) {
self.pending_deals.write().await.insert(deal.ticket, deal);
}
pub async fn update_opened_deals(&self, deals: Vec<Deal>) {
self.opened_deals
.write()
.await
.extend(deals.into_iter().map(|deal| (deal.id, deal)));
}
pub async fn update_closed_deals(&self, deals: Vec<Deal>) {
let ids: Vec<_> = deals.iter().map(|deal| deal.id).collect();
self.opened_deals
.write()
.await
.retain(|id, _| !ids.contains(id));
self.closed_deals
.write()
.await
.extend(deals.into_iter().map(|deal| (deal.id, deal)));
}
pub async fn clear_closed_deals(&self) {
self.closed_deals.write().await.clear();
}
pub async fn clear_opened_deals(&self) {
self.opened_deals.write().await.clear();
}
pub async fn get_opened_deals(&self) -> HashMap<Uuid, Deal> {
self.opened_deals.read().await.clone()
}
pub async fn get_closed_deals(&self) -> HashMap<Uuid, Deal> {
self.closed_deals.read().await.clone()
}
pub async fn contains_opened_deal(&self, deal_id: Uuid) -> bool {
self.opened_deals.read().await.contains_key(&deal_id)
}
pub async fn contains_closed_deal(&self, deal_id: Uuid) -> bool {
self.closed_deals.read().await.contains_key(&deal_id)
}
pub async fn get_opened_deal(&self, deal_id: Uuid) -> Option<Deal> {
self.opened_deals.read().await.get(&deal_id).cloned()
}
pub async fn get_closed_deal(&self, deal_id: Uuid) -> Option<Deal> {
self.closed_deals.read().await.get(&deal_id).cloned()
}
pub async fn get_pending_deal(&self, deal_id: Uuid) -> Option<PendingOrder> {
self.pending_deals.read().await.get(&deal_id).cloned()
}
pub async fn get_pending_deals(&self) -> HashMap<Uuid, PendingOrder> {
self.pending_deals.read().await.clone()
}
pub async fn remove_pending_deal(&self, deal_id: &Uuid) -> Option<PendingOrder> {
self.pending_deals.write().await.remove(deal_id)
}
}