use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use crate::models::common::{Currency, DealId, Epic};
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ActivityRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<NaiveDateTime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<NaiveDateTime>,
pub detailed: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub deal_id: Option<DealId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<String>,
pub page_size: u32,
}
impl Default for ActivityRequest {
fn default() -> Self {
Self {
from: None,
to: None,
detailed: false,
deal_id: None,
filter: None,
page_size: 50,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Activity {
pub date: NaiveDateTime,
pub epic: Epic,
pub period: String,
pub deal_id: DealId,
pub channel: ActivityChannel,
#[serde(rename = "type")]
pub activity_type: ActivityType,
pub status: ActivityStatus,
pub description: String,
pub details: Option<ActivityDetails>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ActivityChannel {
Web,
Mobile,
Dealer,
PublicWebApi,
System,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ActivityType {
Position,
WorkingOrder,
Edit,
System,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ActivityStatus {
Accepted,
Rejected,
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ActivityDetails {
pub actions: Vec<ActivityAction>,
#[serde(skip_serializing_if = "Option::is_none")]
pub market_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub direction: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit_level: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_level: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trailing_stop: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub guaranteed_stop: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ActivityAction {
pub action_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub affected_deal_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ActivityV1 {
pub channel: String,
#[serde(with = "ig_v1_dt")]
pub date: NaiveDateTime,
pub deal_id: DealId,
pub description: String,
pub details: Option<String>,
pub epic: Epic,
pub period: String,
pub status: String,
#[serde(rename = "type")]
pub activity_type: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TransactionType {
All,
AllDeal,
Deposit,
Withdrawal,
}
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionsRequest {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub trans_type: Option<TransactionType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<NaiveDateTime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<NaiveDateTime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_span_seconds: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_size: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_number: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionsResponse {
pub transactions: Vec<Transaction>,
pub metadata: TransactionsMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionsMetadata {
pub page_data: PageData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PageData {
pub page_number: u32,
pub page_size: u32,
pub total_pages: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
pub date: String,
pub date_utc: NaiveDateTime,
pub instrument_name: String,
pub period: String,
pub profit_and_loss: String,
pub transaction_type: String,
pub reference: String,
pub open_level: String,
pub close_level: String,
pub size: String,
pub currency: Currency,
pub cash_transaction: bool,
}
impl Transaction {
pub fn profit_and_loss_value(&self) -> Option<f64> {
parse_ig_numeric(&self.profit_and_loss)
}
pub fn open_level_value(&self) -> Option<f64> {
parse_ig_numeric(&self.open_level)
}
pub fn close_level_value(&self) -> Option<f64> {
parse_ig_numeric(&self.close_level)
}
pub fn size_value(&self) -> Option<f64> {
parse_ig_numeric(&self.size)
}
}
fn parse_ig_numeric(s: &str) -> Option<f64> {
let start = s.find(|c: char| c == '-' || c == '+' || c.is_ascii_digit())?;
s[start..].parse::<f64>().ok()
}
#[cfg(feature = "polars")]
impl crate::dataframe::IntoDataFrame for Vec<Activity> {
fn to_dataframe(&self) -> crate::Result<polars::prelude::DataFrame> {
use polars::prelude::*;
let date: Vec<chrono::NaiveDateTime> = self.iter().map(|a| a.date).collect();
let epic: Vec<&str> = self.iter().map(|a| a.epic.as_str()).collect();
let period: Vec<&str> = self.iter().map(|a| a.period.as_str()).collect();
let deal_id: Vec<&str> = self.iter().map(|a| a.deal_id.as_str()).collect();
let channel: Vec<&str> = self
.iter()
.map(|a| activity_channel_str(a.channel))
.collect();
let activity_type: Vec<&str> = self
.iter()
.map(|a| activity_type_str(a.activity_type))
.collect();
let status: Vec<&str> = self.iter().map(|a| activity_status_str(a.status)).collect();
let description: Vec<&str> = self.iter().map(|a| a.description.as_str()).collect();
let date_series = Series::new("date".into(), date);
DataFrame::new(vec![
date_series.into(),
Column::new("epic".into(), epic),
Column::new("period".into(), period),
Column::new("deal_id".into(), deal_id),
Column::new("channel".into(), channel),
Column::new("activity_type".into(), activity_type),
Column::new("status".into(), status),
Column::new("description".into(), description),
])
.map_err(|e| crate::Error::Config(format!("polars conversion failed: {e}")))
}
}
#[cfg(feature = "polars")]
fn activity_channel_str(c: ActivityChannel) -> &'static str {
match c {
ActivityChannel::Web => "WEB",
ActivityChannel::Mobile => "MOBILE",
ActivityChannel::Dealer => "DEALER",
ActivityChannel::PublicWebApi => "PUBLIC_WEB_API",
ActivityChannel::System => "SYSTEM",
ActivityChannel::Unknown => "UNKNOWN",
}
}
#[cfg(feature = "polars")]
fn activity_type_str(t: ActivityType) -> &'static str {
match t {
ActivityType::Position => "POSITION",
ActivityType::WorkingOrder => "WORKING_ORDER",
ActivityType::Edit => "EDIT",
ActivityType::System => "SYSTEM",
ActivityType::Unknown => "UNKNOWN",
}
}
#[cfg(feature = "polars")]
fn activity_status_str(s: ActivityStatus) -> &'static str {
match s {
ActivityStatus::Accepted => "ACCEPTED",
ActivityStatus::Rejected => "REJECTED",
ActivityStatus::Unknown => "UNKNOWN",
}
}
#[cfg(feature = "polars")]
impl crate::dataframe::IntoDataFrame for Vec<Transaction> {
fn to_dataframe(&self) -> crate::Result<polars::prelude::DataFrame> {
use polars::prelude::*;
let date: Vec<&str> = self.iter().map(|t| t.date.as_str()).collect();
let date_utc: Vec<chrono::NaiveDateTime> = self.iter().map(|t| t.date_utc).collect();
let instrument_name: Vec<&str> = self.iter().map(|t| t.instrument_name.as_str()).collect();
let period: Vec<&str> = self.iter().map(|t| t.period.as_str()).collect();
let transaction_type: Vec<&str> =
self.iter().map(|t| t.transaction_type.as_str()).collect();
let reference: Vec<&str> = self.iter().map(|t| t.reference.as_str()).collect();
let currency: Vec<&str> = self.iter().map(|t| t.currency.as_str()).collect();
let cash_transaction: Vec<bool> = self.iter().map(|t| t.cash_transaction).collect();
let profit_and_loss: Vec<&str> = self.iter().map(|t| t.profit_and_loss.as_str()).collect();
let profit_and_loss_value: Vec<Option<f64>> = self
.iter()
.map(Transaction::profit_and_loss_value)
.collect();
let open_level: Vec<&str> = self.iter().map(|t| t.open_level.as_str()).collect();
let open_level_value: Vec<Option<f64>> =
self.iter().map(Transaction::open_level_value).collect();
let close_level: Vec<&str> = self.iter().map(|t| t.close_level.as_str()).collect();
let close_level_value: Vec<Option<f64>> =
self.iter().map(Transaction::close_level_value).collect();
let size: Vec<&str> = self.iter().map(|t| t.size.as_str()).collect();
let size_value: Vec<Option<f64>> = self.iter().map(Transaction::size_value).collect();
let date_utc_series = Series::new("date_utc".into(), date_utc);
DataFrame::new(vec![
Column::new("date".into(), date),
date_utc_series.into(),
Column::new("instrument_name".into(), instrument_name),
Column::new("period".into(), period),
Column::new("transaction_type".into(), transaction_type),
Column::new("reference".into(), reference),
Column::new("currency".into(), currency),
Column::new("cash_transaction".into(), cash_transaction),
Column::new("profit_and_loss".into(), profit_and_loss),
Column::new("profit_and_loss_value".into(), profit_and_loss_value),
Column::new("open_level".into(), open_level),
Column::new("open_level_value".into(), open_level_value),
Column::new("close_level".into(), close_level),
Column::new("close_level_value".into(), close_level_value),
Column::new("size".into(), size),
Column::new("size_value".into(), size_value),
])
.map_err(|e| crate::Error::Config(format!("polars conversion failed: {e}")))
}
}
#[derive(Debug, Deserialize)]
pub(crate) struct ActivityPage {
pub activities: Vec<Activity>,
pub metadata: ActivityMetadata,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ActivityMetadata {
pub paging: ActivityPaging,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ActivityPaging {
pub next: Option<String>,
#[allow(dead_code)]
pub size: u32,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ActivityV1Envelope {
pub activities: Vec<ActivityV1>,
}
mod ig_v1_dt {
use chrono::NaiveDateTime;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S: Serializer>(dt: &NaiveDateTime, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&crate::time::format(*dt, crate::time::ApiVersion::V1))
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<NaiveDateTime, D::Error> {
let raw = String::deserialize(d)?;
crate::time::parse(&raw, crate::time::ApiVersion::V1).map_err(serde::de::Error::custom)
}
}