Data Models
This page documents the data models used throughout the VRVP Strategy system, including DTOs, API response models, and internal data structures.
Data Transfer Objects (DTOs)
DTOs provide a source-agnostic layer for data handling, enabling easy integration with different brokers.
CandleDTO
Represents OHLCV candlestick data.
@dataclass
class CandleDTO:
timestamp: datetime
open: float
high: float
low: float
close: float
volume: float
@property
def is_bullish(self) -> bool:
return self.close > self.open
@property
def is_bearish(self) -> bool:
return self.close < self.open
@property
def body_size(self) -> float:
return abs(self.close - self.open)
@property
def upper_wick(self) -> float:
return self.high - max(self.open, self.close)
@property
def lower_wick(self) -> float:
return min(self.open, self.close) - self.lowPriceDTO
Represents current price data.
@dataclass
class PriceDTO:
instrument: str
bid: float
ask: float
timestamp: datetime
@property
def mid(self) -> float:
return (self.bid + self.ask) / 2
@property
def spread(self) -> float:
return self.ask - self.bid
@property
def spread_pips(self) -> float:
# For forex pairs
return self.spread * 10000AccountDTO
Represents trading account information.
@dataclass
class AccountDTO:
balance: float
equity: float
margin_used: float
margin_available: float
unrealized_pnl: float
currency: str = "USD"
@property
def margin_level(self) -> float:
if self.margin_used == 0:
return float('inf')
return (self.equity / self.margin_used) * 100PositionDTO
Represents an open position.
@dataclass
class PositionDTO:
id: str
instrument: str
direction: str # "LONG" or "SHORT"
size: float
entry_price: float
current_price: float
stop_loss: Optional[float]
take_profit: Optional[float]
unrealized_pnl: float
opened_at: datetimeAPI Response Models
Pydantic models for API responses (defined in api/models.py).
HealthResponse
class HealthResponse(BaseModel):
status: Literal["healthy", "unhealthy"]
timestamp: datetime
uptime_seconds: int
version: str
class Config:
json_schema_extra = {
"example": {
"status": "healthy",
"timestamp": "2024-01-15T10:30:00Z",
"uptime_seconds": 3600,
"version": "1.0.0"
}
}StatusResponse
class StatusResponse(BaseModel):
running: bool
started_at: Optional[datetime]
active_pairs: List[str]
total_signals_today: int
last_signal_time: Optional[datetime]
account: Optional[AccountInfo]
class AccountInfo(BaseModel):
balance: float
equity: float
margin_used: float
margin_available: floatSignalResponse
class SignalResponse(BaseModel):
instrument: str
signal: Optional[SignalInfo]
message: Optional[str] = None
class SignalInfo(BaseModel):
type: Literal["LONG", "SHORT"]
generated_at: datetime
entry_price: float
stop_loss: float
take_profit: float
risk_reward: float
indicators: IndicatorStates
confluence_score: int
class IndicatorStates(BaseModel):
supertrend: SupertrendState
stochrsi: StochRSIState
fvg: Optional[FVGState]
volume_profile: VolumeProfileStateIndicator State Models
class SupertrendState(BaseModel):
direction: Literal[1, -1]
trend: Literal["bullish", "bearish"]
value: float
class StochRSIState(BaseModel):
k: float
d: float
signal: Optional[str] # "oversold_cross", "overbought_cross", etc.
class FVGState(BaseModel):
type: Literal["bullish", "bearish"]
zone: Tuple[float, float]
age_bars: int
class VolumeProfileState(BaseModel):
poc: float
vah: float
val: float
price_position: str # "above_vah", "value_area", "below_val"Internal Models
Signal
Internal signal representation used by the strategy engine.
@dataclass
class Signal:
instrument: str
direction: Literal["LONG", "SHORT"]
entry_price: float
stop_loss: float
take_profit: float
generated_at: datetime
indicators: Dict[str, Any]
confluence_score: int
metadata: Dict[str, Any] = field(default_factory=dict)
@property
def risk_pips(self) -> float:
return abs(self.entry_price - self.stop_loss) * 10000
@property
def reward_pips(self) -> float:
return abs(self.take_profit - self.entry_price) * 10000
@property
def risk_reward(self) -> float:
return self.reward_pips / self.risk_pips
def to_dict(self) -> dict:
return {
"instrument": self.instrument,
"direction": self.direction,
"entry_price": self.entry_price,
"stop_loss": self.stop_loss,
"take_profit": self.take_profit,
"generated_at": self.generated_at.isoformat(),
"risk_reward": self.risk_reward,
"confluence_score": self.confluence_score
}Trade
Represents a completed trade for backtesting.
@dataclass
class Trade:
id: int
instrument: str
direction: Literal["LONG", "SHORT"]
entry_time: datetime
entry_price: float
exit_time: datetime
exit_price: float
size: float
pnl: float
pnl_percent: float
exit_reason: str # "TAKE_PROFIT", "STOP_LOSS", "SIGNAL_EXIT", etc.
metadata: Dict[str, Any] = field(default_factory=dict)
@property
def is_winner(self) -> bool:
return self.pnl > 0
@property
def duration(self) -> timedelta:
return self.exit_time - self.entry_time
@property
def r_multiple(self) -> float:
"""Return as multiple of risk."""
if 'risk' in self.metadata:
return self.pnl / self.metadata['risk']
return 0.0BacktestResult
Results from a backtest run.
@dataclass
class BacktestResult:
instrument: str
start_date: datetime
end_date: datetime
initial_balance: float
final_balance: float
trades: List[Trade]
@property
def net_profit(self) -> float:
return self.final_balance - self.initial_balance
@property
def net_profit_percent(self) -> float:
return (self.net_profit / self.initial_balance) * 100
@property
def total_trades(self) -> int:
return len(self.trades)
@property
def winning_trades(self) -> int:
return sum(1 for t in self.trades if t.is_winner)
@property
def win_rate(self) -> float:
if self.total_trades == 0:
return 0.0
return (self.winning_trades / self.total_trades) * 100
@property
def profit_factor(self) -> float:
gross_profit = sum(t.pnl for t in self.trades if t.pnl > 0)
gross_loss = abs(sum(t.pnl for t in self.trades if t.pnl < 0))
if gross_loss == 0:
return float('inf')
return gross_profit / gross_loss
@property
def max_drawdown(self) -> float:
"""Calculate maximum drawdown percentage."""
peak = self.initial_balance
max_dd = 0.0
balance = self.initial_balance
for trade in self.trades:
balance += trade.pnl
if balance > peak:
peak = balance
dd = (peak - balance) / peak
if dd > max_dd:
max_dd = dd
return max_dd * 100Type Definitions
Common type aliases used throughout the codebase.
from typing import Literal, Dict, List, Optional, Tuple
# Direction types
Direction = Literal["LONG", "SHORT"]
# Timeframe types
Timeframe = Literal["1M", "5M", "15M", "30M", "1H", "4H", "1D", "1W"]
# Signal strength
SignalStrength = Literal["WEAK", "MODERATE", "STRONG"]
# Exit reasons
ExitReason = Literal[
"TAKE_PROFIT",
"STOP_LOSS",
"TRAILING_STOP",
"SIGNAL_EXIT",
"MANUAL",
"END_OF_DATA"
]
# Indicator state
TrendDirection = Literal[1, -1] # 1 = bullish, -1 = bearishData Transformers
Functions for converting between data formats.
Capital.com Response Transformer
# data/dto_transformers.py
def transform_candle_response(response: dict) -> List[CandleDTO]:
"""Transform Capital.com candle response to DTOs."""
candles = []
for candle in response.get('prices', []):
candles.append(CandleDTO(
timestamp=datetime.fromisoformat(candle['snapshotTime']),
open=float(candle['openPrice']['mid']),
high=float(candle['highPrice']['mid']),
low=float(candle['lowPrice']['mid']),
close=float(candle['closePrice']['mid']),
volume=float(candle.get('lastTradedVolume', 0))
))
return candles
def transform_account_response(response: dict) -> AccountDTO:
"""Transform Capital.com account response to DTO."""
return AccountDTO(
balance=float(response['balance']),
equity=float(response['equity']),
margin_used=float(response['margin']),
margin_available=float(response['availableToTrade']),
unrealized_pnl=float(response.get('profitLoss', 0)),
currency=response.get('currency', 'USD')
)DataFrame Conversions
def candles_to_dataframe(candles: List[CandleDTO]) -> pd.DataFrame:
"""Convert list of CandleDTO to pandas DataFrame."""
data = {
'timestamp': [c.timestamp for c in candles],
'open': [c.open for c in candles],
'high': [c.high for c in candles],
'low': [c.low for c in candles],
'close': [c.close for c in candles],
'volume': [c.volume for c in candles]
}
df = pd.DataFrame(data)
df.set_index('timestamp', inplace=True)
return df
def dataframe_to_candles(df: pd.DataFrame) -> List[CandleDTO]:
"""Convert pandas DataFrame to list of CandleDTO."""
return [
CandleDTO(
timestamp=idx,
open=row['open'],
high=row['high'],
low=row['low'],
close=row['close'],
volume=row['volume']
)
for idx, row in df.iterrows()
]Last updated on