-
Notifications
You must be signed in to change notification settings - Fork 1
TVIndicator Base Class API
- Introduction
- Indicator Lifecycle Methods
- Configuration System
- Signal Generation and Drawing
- Registration System
- Implementation Examples
- Integration with TVEngine
The TVIndicator base class serves as the foundation for all custom indicators in the PyTradingView framework. This abstract base class defines the interface and core functionality that all indicators must implement, providing a standardized approach to indicator development, configuration management, signal generation, and chart integration. The class implements the Abstract Base Class (ABC) pattern to enforce implementation of critical methods while providing helper methods for common operations.
The TVIndicator class defines a comprehensive lifecycle for indicator execution, with callback methods that are automatically invoked at specific points during the indicator's operation. These methods provide hooks for initialization, data processing, calculation, and cleanup.
The on_init method is called when an indicator is initialized on a chart, receiving references to the widget, chart, and optional chart ID. This method sets up the indicator's configuration and internal state.
sequenceDiagram
participant Engine as TVEngine
participant Indicator as TVIndicator
participant Registry as IndicatorRegistry
Engine->>Registry : create_instance(name)
Registry->>Indicator : __init__()
Engine->>Indicator : on_init(widget, chart, chart_id)
Indicator->>Indicator : get_config()
Indicator->>Engine : Initialization complete
The indicator lifecycle includes several callback methods that are invoked during data processing:
-
on_data_loaded(df): Called when new market data is loaded, providing access to the DataFrame containing OHLC data -
on_calculate_start(): Called before the calculation process begins -
on_calculate_end(signals, drawables): Called after calculation completes, receiving the generated signals and drawable elements -
on_draw_start(): Called before drawing operations begin -
on_draw_end(): Called after drawing operations complete -
on_destroy(): Called when the indicator is being destroyed, for cleanup operations
These methods provide visibility into the indicator's execution flow and enable debugging through optional debug output when the configuration's debug flag is enabled.
The configuration system in TVIndicator provides a comprehensive framework for managing indicator parameters, styles, and persistence. Configuration is managed through the IndicatorConfig class, which supports complex input types, style definitions, and runtime modifications.
The configuration system supports various input parameter types through the InputType enumeration:
- INTEGER: Integer values with optional min/max constraints
- FLOAT: Floating-point values with optional min/max constraints
- BOOLEAN: True/false values
- STRING: Text strings
- COLOR: Color values in hexadecimal format (#RGB or #RRGGBB)
- OPTIONS: Dropdown options with label-value pairs
- SOURCE: Data source selection (open, high, low, close, hl2, hlc3, ohlc4, volume)
Input parameters are defined using the InputDefinition dataclass, which includes properties for default values, validation rules, tooltips, and UI grouping.
Style definitions are managed through the StyleDefinition class, which encapsulates visual properties:
- color: Hexadecimal color code
- line_width: Line width (0-10)
- line_style: 0=solid, 1=dashed, 2=dotted
- transparency: Transparency level (0-100)
- visible: Whether the element is visible
Styles can be grouped in the UI and support validation to ensure values are within acceptable ranges.
The TVIndicator class provides several methods for runtime configuration management:
-
get_config_dict(): Returns the current configuration as a dictionary -
update_config(config_dict): Updates the entire configuration from a dictionary -
update_input_value(input_id, value): Updates a single input parameter -
update_style(style_id, **kwargs): Updates style properties
These methods automatically trigger recalculation when configuration changes, ensuring the indicator's output reflects the new settings.
classDiagram
class IndicatorConfig {
+name : str
+version : str
+description : str
+author : str
+enabled : bool
+debug : bool
+inputs : List[InputDefinition]
+styles : List[StyleDefinition]
+on_config_changed : Callable
+get_input_value(input_id) Any
+set_input_value(input_id, value) Tuple[bool, str]
+set_input_values(values) Tuple[bool, List[str]]
+get_style(style_id) StyleDefinition
+update_style(style_id, **kwargs) Tuple[bool, str]
+validate_all() Tuple[bool, List[str]]
+to_dict() Dict[str, Any]
+from_dict(data) IndicatorConfig
+reset_to_defaults() void
}
class InputDefinition {
+id : str
+display_name : str
+type : InputType
+default_value : Any
+value : Any
+tooltip : str
+options : List[InputOption]
+min_value : Union[int, float]
+max_value : Union[int, float]
+step : Union[int, float]
+visible : bool
+group : str
+validate() Tuple[bool, str]
+to_dict() Dict[str, Any]
}
class StyleDefinition {
+id : str
+display_name : str
+color : str
+line_width : int
+line_style : int
+transparency : int
+visible : bool
+group : str
+validate() Tuple[bool, str]
+to_dict() Dict[str, Any]
}
IndicatorConfig "1" -- "0..*" InputDefinition : contains
IndicatorConfig "1" -- "0..*" StyleDefinition : contains
InputDefinition "1" -- "0..*" InputOption : has options
The TVIndicator class provides a standardized approach to signal generation and visual representation on charts through data structures and drawing methods.
Indicators generate trading signals through the calculate() method, which returns a tuple of TVSignal and TVDrawable objects. The TVSignal class represents trading signals with the following properties:
- signal_type: 'buy', 'sell', or 'neutral'
- timestamp: UNIX timestamp in seconds
- price: Price value for the signal
- metadata: Additional data as key-value pairs
Signals are designed to be independent of DataFrame indices, using timestamps for cross-chart compatibility and persistence.
Visual elements are represented by the TVDrawable class, which encapsulates drawable elements with:
- points: List of (time, price) tuples representing coordinates
- shape: TVSingleShape or TVMultipleShape object containing style information
- metadata: Additional metadata
The drawing system supports both custom and default drawing logic:
- If a subclass overrides the
draw()method, that custom logic is used - Otherwise, the engine applies default drawing logic based on signal types and drawable elements
The base class provides helper methods for managing drawn entities:
-
add_drawn_entity(entity_id): Records a newly created entity -
get_drawn_entities(): Returns all entity IDs created by the indicator -
clear_all_drawings(): Asynchronously removes all drawn entities from the chart
classDiagram
class TVSignal {
+signal_type : str
+timestamp : int
+price : float
+metadata : Dict[str, Any]
}
class TVDrawable {
+points : List[Tuple[int, float]]
+shape : Any
+metadata : Dict[str, Any]
}
class TVIndicator {
+calculate(df) Tuple[List[TVSignal], List[TVDrawable]]
+draw(chart, df, signals, drawables) void
+add_drawn_entity(entity_id) void
+get_drawn_entities() List[str]
+clear_all_drawings() Coroutine
}
TVIndicator "1" -- "0..*" TVSignal : generates
TVIndicator "1" -- "0..*" TVDrawable : generates
The indicator registration system enables discovery, management, and instantiation of custom indicators through the IndicatorRegistry class and @register_indicator decorator.
The IndicatorRegistry implements a singleton pattern to manage all registered indicator classes globally. Key features include:
- Centralized storage of indicator classes
- Support for enabling/disabling indicators
- Runtime creation of indicator instances
- Listing of registered and enabled indicators
The registry provides methods for:
-
register(indicator_class, name, enabled): Register a new indicator class -
unregister(name): Remove an indicator from the registry -
get(name): Retrieve an indicator class by name -
create_instance(name): Create and return an instance of a registered indicator -
list_all(): Return all registered indicator names -
list_enabled(): Return all enabled indicator names
The @register_indicator decorator provides a convenient way to register indicators at definition time:
@register_indicator(name="MyCustomIndicator", enabled=True)
class MyCustomIndicator(TVIndicator):
def get_config(self) -> IndicatorConfig:
# Configuration definition
pass
def calculate(self, df: pd.DataFrame) -> Tuple[List[TVSignal], List[TVDrawable]]:
# Calculation logic
passWhen a class is decorated, it is automatically registered with the global IndicatorRegistry instance, making it available for instantiation by the TVEngine.
classDiagram
class IndicatorRegistry {
-_instance : IndicatorRegistry
-_indicators : Dict[str, Type[TVIndicator]]
-_enabled_indicators : Dict[str, bool]
+get_instance() IndicatorRegistry
+register(indicator_class, name, enabled) void
+unregister(name) bool
+get(name) Type[TVIndicator]
+create_instance(name) TVIndicator
+is_registered(name) bool
+is_enabled(name) bool
+enable(name) bool
+disable(name) bool
+list_all() List[str]
+list_enabled() List[str]
+clear() void
+get_info() Dict[str, Dict[str, Any]]
}
class TVIndicator {
<<abstract>>
+get_config() IndicatorConfig
+calculate(df) Tuple[List[TVSignal], List[TVDrawable]]
}
IndicatorRegistry "1" -- "0..*" TVIndicator : manages
register_indicator --> IndicatorRegistry : uses
A minimal indicator implementation requires overriding the abstract methods get_config() and calculate():
@register_indicator(name="SimpleMA")
class SimpleMAIndicator(TVIndicator):
def get_config(self) -> IndicatorConfig:
config = IndicatorConfig(
name="Simple Moving Average",
description="Simple moving average indicator",
author="Developer"
)
# Add input parameter
length_input = InputDefinition(
id="length",
display_name="Length",
type=InputType.INTEGER,
default_value=20,
min_value=1,
max_value=100
)
config.inputs.append(length_input)
# Add style definition
line_style = StyleDefinition(
id="ma_line",
display_name="MA Line",
color="#FF0000",
line_width=2
)
config.styles.append(line_style)
return config
def calculate(self, df: pd.DataFrame) -> Tuple[List[TVSignal], List[TVDrawable]]:
# Get configuration values
length = self._config.get_input_value("length")
# Calculate moving average
df['ma'] = df['close'].rolling(window=length).mean()
# Generate signals (example: price crossing MA)
signals = []
for i in range(1, len(df)):
if df['close'].iloc[i] > df['ma'].iloc[i] and df['close'].iloc[i-1] <= df['ma'].iloc[i-1]:
signals.append(TVSignal(
signal_type='buy',
timestamp=int(df['time'].iloc[i]),
price=df['close'].iloc[i]
))
elif df['close'].iloc[i] < df['ma'].iloc[i] and df['close'].iloc[i-1] >= df['ma'].iloc[i-1]:
signals.append(TVSignal(
signal_type='sell',
timestamp=int(df['time'].iloc[i]),
price=df['close'].iloc[i]
))
# Create drawable elements
drawables = []
style = self._config.get_style("ma_line")
for i in range(len(df)):
if not pd.isna(df['ma'].iloc[i]):
drawables.append(TVDrawable(
points=[(df['time'].iloc[i], df['ma'].iloc[i])],
shape=style
))
return signals, drawablesFor more complex visualizations, indicators can override the draw() method to implement custom drawing logic:
@register_indicator(name="CustomPattern")
class CustomPatternIndicator(TVIndicator):
def get_config(self) -> IndicatorConfig:
# Configuration setup
pass
def calculate(self, df: pd.DataFrame) -> Tuple[List[TVSignal], List[TVDrawable]]:
# Complex calculation logic
pass
def draw(self, chart: TVChart, df: pd.DataFrame, signals: List[TVSignal], drawables: List[TVDrawable]) -> None:
"""
Custom drawing implementation
This method will be called instead of the default drawing logic
"""
# Clear existing drawings
asyncio.create_task(self.clear_all_drawings())
# Custom drawing logic using TradingView API directly
for signal in signals:
if signal.signal_type == 'buy':
# Custom buy arrow with special styling
point = TVShapePoint(time=signal.timestamp, price=signal.price)
arrow = TVArrowUp()
arrow.overrides = {
"arrowColor": "#00FF00",
"color": "#00FF00",
"size": 2
}
entity_id = asyncio.create_task(chart.createShape(point=point, options=arrow))
self.add_drawn_entity(entity_id.result())The TVIndicator class integrates with the TVEngine through a modular architecture that handles indicator lifecycle management, configuration updates, and drawing operations.
The TVEngine manages indicators through several mixins that handle different aspects of indicator operation:
- TVEngineManager: Handles activation and deactivation of indicators
- TVEngineConfig: Manages configuration updates and recalculation
- TVEngineDrawing: Executes calculation and drawing operations
- TVEngineRuntime: Handles initialization and event processing
When market data is loaded, the engine follows this sequence:
- Calls
on_data_loaded()on each active indicator - Invokes
on_calculate_start()before calculation - Calls the
calculate()method to generate signals and drawables - Calls
on_calculate_end()after calculation completes - Initiates drawing operations through
on_draw_start()and the drawing system - Calls
on_draw_end()after drawing completes
When configuration changes occur, the system automatically handles recalculation through the recalculate_and_redraw() method:
flowchart TD
A[Configuration Update] --> B{Needs Recalculate?}
B --> |Yes| C[Set needs_recalculate flag]
C --> D[External Trigger]
D --> E[recalculate_and_redraw()]
E --> F[on_calculate_start()]
F --> G[calculate() method]
G --> H[on_calculate_end()]
H --> I[clear_all_drawings()]
I --> J{Custom draw method?}
J --> |Yes| K[Call custom draw()]
J --> |No| L[Apply default drawing logic]
K --> M[on_draw_end()]
L --> M
M --> N[mark_recalculate_done()]
N --> O[Update complete]
The update_config(), update_input_value(), and update_style() methods automatically set the needs_recalculate flag, which can be checked with needs_recalculate() and cleared with mark_recalculate_done().