use serde_json::Value;
use crate::core::{
ExchangeError, ExchangeResult,
Ticker, Kline, OrderBook,
OrderSide, OrderStatus, OrderType,
Position, PositionSide, MarginType,
Balance, AccountInfo, AccountType,
};
use crate::core::types::OrderBookLevel;
pub struct OrderDetailsData {
pub order_id: String,
pub symbol: String,
pub status: OrderStatus,
pub side: OrderSide,
pub order_type: OrderType,
pub quantity: f64,
pub filled_quantity: Option<f64>,
pub price: Option<f64>,
pub average_price: Option<f64>,
}
pub struct AngelOneParser;
impl AngelOneParser {
pub fn extract_data(response: &Value) -> ExchangeResult<&Value> {
let status = response.get("status")
.and_then(|v| v.as_bool())
.unwrap_or(false);
if !status {
let message = Self::get_str(response, "message")
.unwrap_or("Unknown error");
let error_code = Self::get_str(response, "errorcode")
.unwrap_or("");
return Err(ExchangeError::Api {
code: -1,
message: format!("Angel One API error: {} (code: {})", message, error_code),
});
}
response.get("data")
.ok_or_else(|| ExchangeError::Parse("Missing 'data' field in response".to_string()))
}
pub fn parse_login(response: &Value) -> ExchangeResult<(String, String)> {
let data = Self::extract_data(response)?;
let jwt_token = Self::require_str(data, "jwtToken")?.to_string();
let refresh_token = Self::require_str(data, "refreshToken")?.to_string();
Ok((jwt_token, refresh_token))
}
pub fn parse_token_refresh(response: &Value) -> ExchangeResult<(String, String)> {
Self::parse_login(response) }
pub fn parse_price(response: &Value) -> ExchangeResult<f64> {
let data = Self::extract_data(response)?;
let fetched = data.get("fetched")
.and_then(|v| v.as_array())
.ok_or_else(|| ExchangeError::Parse("Missing 'fetched' array".to_string()))?;
if fetched.is_empty() {
return Err(ExchangeError::Parse("Empty fetched array".to_string()));
}
let quote = &fetched[0];
Self::require_f64(quote, "ltp")
}
pub fn parse_ticker(response: &Value, _symbol: &str) -> ExchangeResult<Ticker> {
let data = Self::extract_data(response)?;
let fetched = data.get("fetched")
.and_then(|v| v.as_array())
.ok_or_else(|| ExchangeError::Parse("Missing 'fetched' array".to_string()))?;
if fetched.is_empty() {
return Err(ExchangeError::Parse("Empty fetched array".to_string()));
}
let quote = &fetched[0];
Ok(Ticker {
last_price: Self::require_f64(quote, "ltp")?,
bid_price: Self::get_f64(quote, "bidprice"),
ask_price: Self::get_f64(quote, "askprice"),
high_24h: Self::get_f64(quote, "high"),
low_24h: Self::get_f64(quote, "low"),
volume_24h: Self::get_f64(quote, "volume"),
quote_volume_24h: None,
price_change_24h: None,
price_change_percent_24h: None,
timestamp: chrono::Utc::now().timestamp_millis(),
})
}
pub fn parse_orderbook(response: &Value) -> ExchangeResult<OrderBook> {
let data = Self::extract_data(response)?;
let fetched = data.get("fetched")
.and_then(|v| v.as_array())
.ok_or_else(|| ExchangeError::Parse("Missing 'fetched' array".to_string()))?;
if fetched.is_empty() {
return Err(ExchangeError::Parse("Empty fetched array".to_string()));
}
let quote = &fetched[0];
let mut bids = Vec::new();
let mut asks = Vec::new();
if let (Some(bid_price), Some(bid_qty)) = (
Self::get_f64(quote, "bidprice"),
Self::get_f64(quote, "bidqty"),
) {
bids.push(OrderBookLevel::new(bid_price, bid_qty));
}
if let (Some(ask_price), Some(ask_qty)) = (
Self::get_f64(quote, "askprice"),
Self::get_f64(quote, "askqty"),
) {
asks.push(OrderBookLevel::new(ask_price, ask_qty));
}
Ok(OrderBook {
bids,
asks,
timestamp: chrono::Utc::now().timestamp_millis(),
sequence: None,
last_update_id: None,
first_update_id: None,
prev_update_id: None,
event_time: None,
transaction_time: None,
checksum: None,
})
}
pub fn parse_klines(response: &Value) -> ExchangeResult<Vec<Kline>> {
let data = Self::extract_data(response)?;
let candles = data.as_array()
.ok_or_else(|| ExchangeError::Parse("Expected array of candles".to_string()))?;
candles.iter().map(|candle| {
let arr = candle.as_array()
.ok_or_else(|| ExchangeError::Parse("Candle should be array".to_string()))?;
if arr.len() < 6 {
return Err(ExchangeError::Parse("Candle array too short".to_string()));
}
let timestamp_str = arr[0].as_str()
.ok_or_else(|| ExchangeError::Parse("Invalid timestamp".to_string()))?;
let open_time = chrono::DateTime::parse_from_rfc3339(timestamp_str)
.map_err(|e| ExchangeError::Parse(format!("Failed to parse timestamp: {}", e)))?
.timestamp_millis();
Ok(Kline {
open_time,
open: Self::parse_value_as_f64(&arr[1])?,
high: Self::parse_value_as_f64(&arr[2])?,
low: Self::parse_value_as_f64(&arr[3])?,
close: Self::parse_value_as_f64(&arr[4])?,
volume: Self::parse_value_as_f64(&arr[5])?,
close_time: None,
quote_volume: None,
trades: None,
})
}).collect()
}
pub fn parse_order_id(response: &Value) -> ExchangeResult<String> {
let data = Self::extract_data(response)?;
let order_id = if let Some(id) = Self::get_str(data, "orderid") {
id.to_string()
} else {
Self::require_str(data, "uniqueorderid")?.to_string()
};
Ok(order_id)
}
pub fn parse_order_details(response: &Value) -> ExchangeResult<OrderDetailsData> {
let data = Self::extract_data(response)?;
let order_id = Self::require_str(data, "orderid")?.to_string();
let symbol = Self::require_str(data, "tradingsymbol")?.to_string();
let status = Self::parse_order_status(Self::get_str(data, "orderstatus").unwrap_or(""));
let side = match Self::get_str(data, "transactiontype") {
Some("BUY") => OrderSide::Buy,
Some("SELL") => OrderSide::Sell,
_ => OrderSide::Buy,
};
let order_type = match Self::get_str(data, "ordertype") {
Some("MARKET") => OrderType::Market,
Some("LIMIT") => OrderType::Limit { price: 0.0 },
Some("STOPLOSS_LIMIT") => OrderType::StopLimit { stop_price: 0.0, limit_price: 0.0 },
Some("STOPLOSS_MARKET") => OrderType::StopMarket { stop_price: 0.0 },
_ => OrderType::Market,
};
Ok(OrderDetailsData {
order_id,
symbol,
status,
side,
order_type,
quantity: Self::get_f64(data, "quantity").unwrap_or(0.0),
filled_quantity: Self::get_f64(data, "filledshares"),
price: Self::get_f64(data, "price"),
average_price: Self::get_f64(data, "averageprice"),
})
}
fn parse_order_status(status: &str) -> OrderStatus {
match status.to_uppercase().as_str() {
"OPEN" | "PENDING" => OrderStatus::New,
"COMPLETE" | "EXECUTED" => OrderStatus::Filled,
"CANCELLED" => OrderStatus::Canceled,
"REJECTED" => OrderStatus::Rejected,
_ => OrderStatus::New,
}
}
pub fn parse_positions(response: &Value) -> ExchangeResult<Vec<Position>> {
let data = Self::extract_data(response)?;
let positions_array = data.as_array()
.ok_or_else(|| ExchangeError::Parse("Expected array of positions".to_string()))?;
positions_array.iter().map(|pos| {
let symbol = Self::require_str(pos, "tradingsymbol")?.to_string();
let quantity = Self::get_f64(pos, "netqty").unwrap_or(0.0);
let side = if quantity > 0.0 {
PositionSide::Long
} else if quantity < 0.0 {
PositionSide::Short
} else {
PositionSide::Long };
Ok(Position {
symbol,
side,
quantity: quantity.abs(),
entry_price: Self::get_f64(pos, "averageprice").unwrap_or(0.0),
mark_price: Self::get_f64(pos, "ltp"),
liquidation_price: None,
leverage: 1, unrealized_pnl: Self::get_f64(pos, "unrealisedprofitandloss").unwrap_or(0.0),
realized_pnl: Self::get_f64(pos, "realisedprofitandloss"),
margin: None,
margin_type: MarginType::Cross, take_profit: None,
stop_loss: None,
})
}).collect()
}
pub fn parse_balance(response: &Value) -> ExchangeResult<Vec<Balance>> {
let data = Self::extract_data(response)?;
let available = Self::get_f64(data, "availablecash").unwrap_or(0.0);
let used = Self::get_f64(data, "utiliseddebits").unwrap_or(0.0);
Ok(vec![Balance {
asset: "INR".to_string(),
free: available,
locked: used,
total: available + used,
}])
}
pub fn parse_account_info(response: &Value) -> ExchangeResult<AccountInfo> {
let data = Self::extract_data(response)?;
let _client_code = Self::require_str(data, "clientcode")?;
Ok(AccountInfo {
account_type: AccountType::Spot,
can_trade: true,
can_withdraw: true,
can_deposit: true,
maker_commission: 0.0,
taker_commission: 0.0,
balances: vec![],
})
}
fn require_str<'a>(obj: &'a Value, field: &str) -> ExchangeResult<&'a str> {
obj.get(field)
.and_then(|v| v.as_str())
.ok_or_else(|| ExchangeError::Parse(format!("Missing/invalid '{}'", field)))
}
fn get_str<'a>(obj: &'a Value, field: &str) -> Option<&'a str> {
obj.get(field).and_then(|v| v.as_str())
}
fn require_f64(obj: &Value, field: &str) -> ExchangeResult<f64> {
obj.get(field)
.and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
.ok_or_else(|| ExchangeError::Parse(format!("Missing/invalid '{}'", field)))
}
fn get_f64(obj: &Value, field: &str) -> Option<f64> {
obj.get(field)
.and_then(|v| v.as_f64().or_else(|| v.as_str().and_then(|s| s.parse().ok())))
}
fn parse_value_as_f64(val: &Value) -> ExchangeResult<f64> {
val.as_f64()
.or_else(|| val.as_str().and_then(|s| s.parse().ok()))
.ok_or_else(|| ExchangeError::Parse("Failed to parse number".to_string()))
}
}