294 lines
10 KiB
Markdown
294 lines
10 KiB
Markdown
|
|
# CRITICAL UPDATE: Actual Price Data Integration
|
||
|
|
|
||
|
|
**Date**: 2026-02-18
|
||
|
|
**Status**: COMPLETE
|
||
|
|
**Priority**: CRITICAL
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Summary
|
||
|
|
|
||
|
|
The eigenvalue JSON files **DO contain actual price data** in `pricing_data.current_prices`. The system has been updated to use these **REAL prices** instead of synthetic/generated prices.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Price Data Structure in JSON Files
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"scan_number": 22284,
|
||
|
|
"timestamp": "2026-01-01T16:00:05.291658",
|
||
|
|
"windows": { ... },
|
||
|
|
"pricing_data": {
|
||
|
|
"current_prices": {
|
||
|
|
"BTCUSDT": 87967.06,
|
||
|
|
"ETHUSDT": 2985.16,
|
||
|
|
"BNBUSDT": 857.94,
|
||
|
|
...
|
||
|
|
},
|
||
|
|
"price_changes": {
|
||
|
|
"BTCUSDT": 1.1367892858475202e-05,
|
||
|
|
...
|
||
|
|
},
|
||
|
|
"volatility": {
|
||
|
|
"BTCUSDT": 0.04329507905570856,
|
||
|
|
...
|
||
|
|
},
|
||
|
|
"per_asset_correlation": { ... }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Files Modified
|
||
|
|
|
||
|
|
### 1. `nautilus/data_adapter.py`
|
||
|
|
|
||
|
|
**Changes**:
|
||
|
|
- Added `_extract_prices()` method to get actual prices from `pricing_data.current_prices`
|
||
|
|
- Added `_extract_price_changes()` method
|
||
|
|
- Added `_extract_volatility()` method
|
||
|
|
- Added `_extract_regime_data()` method for regime signals
|
||
|
|
- Updated `generate_bars()` to use **ACTUAL prices** instead of synthetic
|
||
|
|
- Updated `get_signal_metadata()` to include actual price in signals
|
||
|
|
- Updated metadata to indicate `price_source: 'actual_from_json'`
|
||
|
|
|
||
|
|
**Key Code**:
|
||
|
|
```python
|
||
|
|
def _extract_prices(self, data: dict) -> Dict[str, float]:
|
||
|
|
"""Extract ACTUAL current prices from scan data."""
|
||
|
|
prices = {}
|
||
|
|
pricing_data = data.get('pricing_data', {})
|
||
|
|
current_prices = pricing_data.get('current_prices', {})
|
||
|
|
|
||
|
|
for asset, price in current_prices.items():
|
||
|
|
if isinstance(price, (int, float)):
|
||
|
|
prices[asset] = float(price)
|
||
|
|
return prices
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. `signal_generator/generator.py`
|
||
|
|
|
||
|
|
**Changes**:
|
||
|
|
- Added `_extract_prices()` method using actual price data
|
||
|
|
- Updated `_extract_vel_div()` to compute from window tracking data
|
||
|
|
- Added `_extract_volatility()` method
|
||
|
|
- Added `_extract_price_changes()` method
|
||
|
|
- Updated `_generate_signals_from_scan()` to include actual price in signals
|
||
|
|
|
||
|
|
**Key Code**:
|
||
|
|
```python
|
||
|
|
def _generate_signals_from_scan(self, scan_data: Dict) -> List[Dict]:
|
||
|
|
# Get ACTUAL prices and vel_div
|
||
|
|
prices = self._extract_prices(scan_data)
|
||
|
|
vel_div_data = self._extract_vel_div(scan_data)
|
||
|
|
|
||
|
|
for asset, vel_div in vel_div_data.items():
|
||
|
|
if vel_div < self.VEL_DIV_THRESHOLD:
|
||
|
|
price = prices.get(asset, 0.0) # ACTUAL price
|
||
|
|
|
||
|
|
signal = {
|
||
|
|
'timestamp': timestamp,
|
||
|
|
'asset': asset,
|
||
|
|
'direction': self.SIGNAL_DIRECTION,
|
||
|
|
'vel_div': vel_div,
|
||
|
|
'strength': strength,
|
||
|
|
'price': price, # ACTUAL price from JSON
|
||
|
|
'volatility': vol,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. `signal_generator/enricher.py`
|
||
|
|
|
||
|
|
**Changes**:
|
||
|
|
- Added `_extract_price_data()` method to get actual price, price_change, volatility
|
||
|
|
- Updated `_compute_direction_confirm()` to use **actual price changes** from `pricing_data.price_changes`
|
||
|
|
- Added momentum threshold check (0.75bps)
|
||
|
|
- Updated `enrich()` to use actual price data for direction confirmation
|
||
|
|
|
||
|
|
**Key Code**:
|
||
|
|
```python
|
||
|
|
def _extract_price_data(self, scan_data: Dict, asset: str) -> Dict[str, Any]:
|
||
|
|
"""Extract ACTUAL price data for asset."""
|
||
|
|
pricing_data = scan_data.get('pricing_data', {})
|
||
|
|
return {
|
||
|
|
'price': pricing_data.get('current_prices', {}).get(asset, 0.0),
|
||
|
|
'price_change': pricing_data.get('price_changes', {}).get(asset, 0.0),
|
||
|
|
'volatility': pricing_data.get('volatility', {}).get(asset, 0.1),
|
||
|
|
}
|
||
|
|
|
||
|
|
def _compute_direction_confirm(self, signal, price_data, asset_data) -> bool:
|
||
|
|
"""Compute direction confirmation using ACTUAL price changes."""
|
||
|
|
price_change = price_data.get('price_change', 0.0)
|
||
|
|
change_bps = abs(price_change) * 10000
|
||
|
|
|
||
|
|
if change_bps < self.MOMENTUM_THRESHOLD_BPS: # 0.75bps
|
||
|
|
return False
|
||
|
|
|
||
|
|
if signal['direction'] == 'SHORT':
|
||
|
|
return price_change < 0
|
||
|
|
else:
|
||
|
|
return price_change > 0
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. `nautilus/strategy.py`
|
||
|
|
|
||
|
|
**Changes**:
|
||
|
|
- Updated `_execute_entry()` to use **actual price from signal** when available
|
||
|
|
- Falls back to quote tick only for live trading
|
||
|
|
- Logs price source for transparency
|
||
|
|
|
||
|
|
**Key Code**:
|
||
|
|
```python
|
||
|
|
def _execute_entry(self, signal_data: dict):
|
||
|
|
# Get price: Use ACTUAL price from signal (validation) or quote (live)
|
||
|
|
signal_price = signal_data.get('price')
|
||
|
|
if signal_price and signal_price > 0:
|
||
|
|
price = float(signal_price)
|
||
|
|
price_source = "signal" # ACTUAL from eigenvalue JSON
|
||
|
|
else:
|
||
|
|
quote = self.cache.quote_tick(instrument_id)
|
||
|
|
price = float(quote.bid if direction == 'SHORT' else quote.ask)
|
||
|
|
price_source = "quote" # Live market data
|
||
|
|
|
||
|
|
self.log.info(
|
||
|
|
f"Entry order: {asset} {direction}, price=${price:.2f} "
|
||
|
|
f"(source: {price_source})"
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Validation Impact
|
||
|
|
|
||
|
|
### Before (Synthetic Prices)
|
||
|
|
```python
|
||
|
|
# Generated synthetic prices from vel_div
|
||
|
|
drift = -vel_div_value * 0.01
|
||
|
|
noise = random.gauss(0, abs(vel_div_value) * 0.005)
|
||
|
|
price = base_price * (1 + drift + noise) # SYNTHETIC!
|
||
|
|
```
|
||
|
|
|
||
|
|
### After (Actual Prices)
|
||
|
|
```python
|
||
|
|
# Extract ACTUAL prices from JSON
|
||
|
|
prices = pricing_data.get('current_prices', {})
|
||
|
|
price = prices.get('BTCUSDT') # 87967.06 - ACTUAL!
|
||
|
|
```
|
||
|
|
|
||
|
|
### Benefits
|
||
|
|
1. **Accurate Validation**: VBT and Nautilus use identical prices
|
||
|
|
2. **Real P&L Calculation**: Based on actual market prices
|
||
|
|
3. **Proper Slippage**: Measured against real prices
|
||
|
|
4. **Faithful Backtests**: Reflects actual historical conditions
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Price Flow Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
eigenvalue JSON
|
||
|
|
├── pricing_data.current_prices ──────┐
|
||
|
|
├── pricing_data.price_changes ───────┤
|
||
|
|
└── pricing_data.volatility ──────────┤
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────────────┐
|
||
|
|
│ SignalGenerator │
|
||
|
|
│ - _extract_prices() → ACTUAL prices │
|
||
|
|
│ - _extract_vel_div() → velocity divergence │
|
||
|
|
│ - signal['price'] = actual_price │
|
||
|
|
└────────────────────┬────────────────────────────┘
|
||
|
|
│ signal with ACTUAL price
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────────────┐
|
||
|
|
│ RedisSignalPublisher │
|
||
|
|
│ - Publish signal with price │
|
||
|
|
└────────────────────┬────────────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────────────┐
|
||
|
|
│ SignalBridgeActor │
|
||
|
|
│ - Consume from Redis │
|
||
|
|
│ - Publish to Nautilus bus │
|
||
|
|
└────────────────────┬────────────────────────────┘
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────────────┐
|
||
|
|
│ DolphinExecutionStrategy │
|
||
|
|
│ - signal_data['price'] → ACTUAL price │
|
||
|
|
│ - Use for position sizing & order entry │
|
||
|
|
│ - Fallback to quote only if no signal price │
|
||
|
|
└─────────────────────────────────────────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Validation Checklist
|
||
|
|
|
||
|
|
- [x] Data adapter extracts actual prices from JSON
|
||
|
|
- [x] Signal generator includes actual price in signals
|
||
|
|
- [x] Enricher uses actual price changes for direction confirmation
|
||
|
|
- [x] Strategy uses signal price (actual) when available
|
||
|
|
- [x] OHLC bars constructed from actual prices
|
||
|
|
- [x] Trade P&L calculated from actual prices
|
||
|
|
- [x] Comparator validates entry/exit prices within 0.1%
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Testing
|
||
|
|
|
||
|
|
### Unit Test
|
||
|
|
```python
|
||
|
|
def test_actual_price_extraction():
|
||
|
|
adapter = JSONEigenvalueDataAdapter(eigenvalues_dir)
|
||
|
|
scan_data = adapter.load_scan_file(sample_file)
|
||
|
|
|
||
|
|
prices = adapter._extract_prices(scan_data)
|
||
|
|
assert 'BTCUSDT' in prices
|
||
|
|
assert prices['BTCUSDT'] > 0 # Actual price, not synthetic
|
||
|
|
assert isinstance(prices['BTCUSDT'], float)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Integration Test
|
||
|
|
```python
|
||
|
|
def test_signal_contains_actual_price():
|
||
|
|
generator = SignalGenerator(eigenvalues_dir)
|
||
|
|
scan_data = generator._load_scan_file(sample_file)
|
||
|
|
signals = generator._generate_signals_from_scan(scan_data)
|
||
|
|
|
||
|
|
for signal in signals:
|
||
|
|
assert 'price' in signal
|
||
|
|
assert signal['price'] > 0 # ACTUAL price
|
||
|
|
assert signal['price_source'] == 'actual_json'
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Migration Notes
|
||
|
|
|
||
|
|
For existing deployments:
|
||
|
|
1. No configuration changes required
|
||
|
|
2. Price source automatically detected (signal vs quote)
|
||
|
|
3. Backward compatible - falls back to quotes if no signal price
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Documentation Updates
|
||
|
|
|
||
|
|
- [x] `data_adapter.py` - Docstrings updated
|
||
|
|
- [x] `generator.py` - Comments added
|
||
|
|
- [x] `enricher.py` - Documentation updated
|
||
|
|
- [x] `strategy.py` - Logging includes price source
|
||
|
|
- [x] This document created
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**CRITICAL**: Both VBT and Nautilus now use **identical actual prices** from `pricing_data.current_prices`, ensuring accurate validation comparison.
|