indicators and plots
This commit is contained in:
@@ -6,9 +6,10 @@ the free CCXT library (not ccxt.pro), supporting both historical data and
|
||||
polling-based subscriptions.
|
||||
|
||||
Numerical Precision:
|
||||
- Uses Decimal for all monetary values (prices, volumes) to avoid floating-point errors
|
||||
- OHLCV data uses native floats for optimal DataFrame/analysis performance
|
||||
- Account balances and order data should use Decimal (via _to_decimal method)
|
||||
- CCXT returns numeric values as strings or floats depending on configuration
|
||||
- All financial values are converted to Decimal to maintain precision
|
||||
- Price data converted to float (_to_float), financial data to Decimal (_to_decimal)
|
||||
|
||||
Real-time Updates:
|
||||
- Uses polling instead of WebSocket (free CCXT doesn't have WebSocket support)
|
||||
@@ -72,6 +73,20 @@ class CCXTDataSource(DataSource):
|
||||
exchange_class = getattr(ccxt, exchange_id)
|
||||
self.exchange = exchange_class(self._config)
|
||||
|
||||
# Configure CCXT to use Decimal mode for precise financial calculations
|
||||
# This ensures all numeric values from the exchange use Decimal internally
|
||||
# We then convert OHLCV to float for DataFrame performance, but keep
|
||||
# Decimal precision for account balances, order sizes, etc.
|
||||
from decimal import Decimal as PythonDecimal
|
||||
self.exchange.number = PythonDecimal
|
||||
|
||||
# Log the precision mode being used by this exchange
|
||||
precision_mode = getattr(self.exchange, 'precisionMode', 'UNKNOWN')
|
||||
logger.info(
|
||||
f"CCXT {exchange_id}: Configured with Decimal mode. "
|
||||
f"Exchange precision mode: {precision_mode}"
|
||||
)
|
||||
|
||||
if sandbox and hasattr(self.exchange, 'set_sandbox_mode'):
|
||||
self.exchange.set_sandbox_mode(True)
|
||||
|
||||
@@ -103,6 +118,33 @@ class CCXTDataSource(DataSource):
|
||||
return Decimal(str(value))
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _to_float(value: Union[str, int, float, Decimal, None]) -> Optional[float]:
|
||||
"""
|
||||
Convert a value to float for OHLCV data.
|
||||
|
||||
OHLCV data is used for charting and DataFrame analysis, where native
|
||||
floats provide better performance and compatibility with pandas/numpy.
|
||||
For financial precision (balances, order sizes), use _to_decimal() instead.
|
||||
|
||||
When CCXT is in Decimal mode (exchange.number = Decimal), it returns
|
||||
Decimal objects. This method converts them to float for performance.
|
||||
|
||||
Handles CCXT's output in both modes:
|
||||
- Decimal mode: receives Decimal objects
|
||||
- Default mode: receives strings, floats, or ints
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, float):
|
||||
return value
|
||||
if isinstance(value, Decimal):
|
||||
# CCXT in Decimal mode - convert to float for OHLCV
|
||||
return float(value)
|
||||
if isinstance(value, (str, int)):
|
||||
return float(value)
|
||||
return None
|
||||
|
||||
async def _ensure_markets_loaded(self):
|
||||
"""Ensure markets are loaded from exchange"""
|
||||
if not self._markets_loaded:
|
||||
@@ -241,31 +283,31 @@ class CCXTDataSource(DataSource):
|
||||
columns=[
|
||||
ColumnInfo(
|
||||
name="open",
|
||||
type="decimal",
|
||||
type="float",
|
||||
description=f"Opening price in {quote}",
|
||||
unit=quote,
|
||||
),
|
||||
ColumnInfo(
|
||||
name="high",
|
||||
type="decimal",
|
||||
type="float",
|
||||
description=f"Highest price in {quote}",
|
||||
unit=quote,
|
||||
),
|
||||
ColumnInfo(
|
||||
name="low",
|
||||
type="decimal",
|
||||
type="float",
|
||||
description=f"Lowest price in {quote}",
|
||||
unit=quote,
|
||||
),
|
||||
ColumnInfo(
|
||||
name="close",
|
||||
type="decimal",
|
||||
type="float",
|
||||
description=f"Closing price in {quote}",
|
||||
unit=quote,
|
||||
),
|
||||
ColumnInfo(
|
||||
name="volume",
|
||||
type="decimal",
|
||||
type="float",
|
||||
description=f"Trading volume in {base}",
|
||||
unit=base,
|
||||
),
|
||||
@@ -370,7 +412,7 @@ class CCXTDataSource(DataSource):
|
||||
all_ohlcv = all_ohlcv[:countback]
|
||||
break
|
||||
|
||||
# Convert to our Bar format with Decimal precision
|
||||
# Convert to our Bar format with float for OHLCV (used in DataFrames)
|
||||
bars = []
|
||||
for candle in all_ohlcv:
|
||||
timestamp_ms, open_price, high, low, close, volume = candle
|
||||
@@ -384,11 +426,11 @@ class CCXTDataSource(DataSource):
|
||||
Bar(
|
||||
time=timestamp,
|
||||
data={
|
||||
"open": self._to_decimal(open_price),
|
||||
"high": self._to_decimal(high),
|
||||
"low": self._to_decimal(low),
|
||||
"close": self._to_decimal(close),
|
||||
"volume": self._to_decimal(volume),
|
||||
"open": self._to_float(open_price),
|
||||
"high": self._to_float(high),
|
||||
"low": self._to_float(low),
|
||||
"close": self._to_float(close),
|
||||
"volume": self._to_float(volume),
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -476,14 +518,14 @@ class CCXTDataSource(DataSource):
|
||||
if timestamp > last_timestamp:
|
||||
self._last_bars[subscription_id] = timestamp
|
||||
|
||||
# Convert to our format with Decimal precision
|
||||
# Convert to our format with float for OHLCV (used in DataFrames)
|
||||
tick_data = {
|
||||
"time": timestamp,
|
||||
"open": self._to_decimal(open_price),
|
||||
"high": self._to_decimal(high),
|
||||
"low": self._to_decimal(low),
|
||||
"close": self._to_decimal(close),
|
||||
"volume": self._to_decimal(volume),
|
||||
"open": self._to_float(open_price),
|
||||
"high": self._to_float(high),
|
||||
"low": self._to_float(low),
|
||||
"close": self._to_float(close),
|
||||
"volume": self._to_float(volume),
|
||||
}
|
||||
|
||||
# Call the callback
|
||||
|
||||
Reference in New Issue
Block a user