indicators and plots

This commit is contained in:
2026-03-02 18:34:38 -04:00
parent 3b29096dab
commit 3ffce97b3e
43 changed files with 6690 additions and 878 deletions

View File

@@ -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