Skip to main content

Algorithm Overview

The Algorithm class is the main interface for algorithmic trading in ProAlgoTrader Core. It provides a clean API for managing trading strategies, market data, and order execution.

Quick Start

# main.py
from project.strategy import Strategy

from proalgotrader_core.start import run_strategy


if __name__ == "__main__":
run_strategy(strategy=Strategy)

Project Structure

When you create a new trading project, you'll have this minimal structure:

your-trading-project/
├── main.py # Entry point (already included)
├── project/
│ ├── strategy.py # Your trading strategy
│ └── position_manager.py # Position management logic
├── requirements.txt # Dependencies (includes proalgotrader_core)
├── .env.example # Environment template
└── .env # Your environment variables (created from .env.example)

The proalgotrader_core framework is installed as a library via pip, so you don't need to manage the framework code directly.

Core Concepts

The Algorithm class provides:

  • 📊 Market Data Management: Add charts and subscribe to real-time data
  • 💼 Order Execution: Place buy/sell orders with simple methods
  • 📈 Position Management: Track positions and P&L
  • Time Management: Check market hours and set intervals
  • 🎯 Strategy Integration: Connect your trading strategies

Public API Reference

Properties

Market Data Properties

# Current market time
algorithm.current_datetime # datetime
algorithm.current_date # date
algorithm.current_time # time

Position & Order Properties

# Get all orders
algorithm.orders # List[Order]

# Get all positions
algorithm.positions # List[Position]

# Get only open positions
algorithm.positions # List[Position]

# Get P&L information
algorithm.unrealized_pnl # PnlCalculatorProtocol - Current open positions P&L
algorithm.net_pnl # PnlCalculatorProtocol - All positions (closed + open) P&L
algorithm.pnl # PnlCalculatorProtocol - Alias for unrealized_pnl

# Access P&L values
algorithm.unrealized_pnl.pnl # float - Unrealized P&L
algorithm.unrealized_pnl.profit # float - Unrealized profit
algorithm.unrealized_pnl.loss # float - Unrealized loss
algorithm.net_pnl.pnl # float - Net P&L (realized + unrealized)

Market Data Methods

Add Charts

# Add equity chart
chart = await algorithm.add_chart(
broker_symbol=equity_symbol,
timeframe=timedelta(minutes=5),
candle_type=CandleType.REGULAR
)

# Add chart with custom parameters
chart = await algorithm.add_chart(
broker_symbol=equity_symbol,
timeframe=timedelta(minutes=1),
candle_type=CandleType.HEIKEN_ASHI
)

Data Access

Charts expose two main ways to access data:

Data Property - Get full DataFrame:

# Get full DataFrame with all available data
chart_data = chart.data
print(chart_data)

# Access specific columns
close_prices = chart.data.select("close")
print(close_prices)

Get Data Method - Get specific values:

# Get current candle close price (row 0 = most recent)
current_close = await chart.get_data(0, "close")
print(f"Current close: {current_close}")

# Get previous candle high price (row 1 = previous candle)
previous_high = await chart.get_data(1, "high")
print(f"Previous high: {previous_high}")

# Get current candle data as Series
current_candle = await chart.get_data(0)
print(current_candle)

Available Columns:

  • current_candle, current_timestamp, current_datetime, symbol
  • open, high, low, close, volume

Debugging Data Structure:

# Print DataFrame structure
print(chart.data)
print(chart.data.columns)
print(chart.data.dtypes)

Add Symbols

# Add equity symbol
equity = await algorithm.add_equity(symbol_name=SymbolType.Stock.RELIANCE)

# Add future symbol
future = await algorithm.add_future(
symbol_name=SymbolType.Index.NIFTY,
expiry_input=("Monthly", 0) # (period, number)
)

# Add option symbol
option = await algorithm.add_option(
symbol_name=SymbolType.Index.NIFTY,
expiry_input=("Weekly", 0), # 0 = current week, 1 = next week
strike_price_input=0, # 0 = ATM, 1 = OTM, -1 = ITM
option_type="CE" # "CE" or "PE"
)

Trading Methods

Place Orders

# Buy equity
await algorithm.buy(
broker_symbol=equity_symbol,
market_type=MarketType.Cash,
product_type=ProductType.CNC,
order_type=OrderType.MARKET_ORDER,
quantities=100
)

# Sell equity
await algorithm.sell(
broker_symbol=equity_symbol,
market_type=MarketType.Cash,
product_type=ProductType.CNC,
order_type=OrderType.MARKET_ORDER,
quantities=100
)

# Buy derivatives (futures/options)
await algorithm.buy(
broker_symbol=option_symbol,
market_type=MarketType.Derivative,
product_type=ProductType.NRML,
order_type=OrderType.MARKET_ORDER,
quantities=50
)

# Exit all positions
await algorithm.exit_all_positions()

Configuration Methods

Time Management

# Set algorithm interval
algorithm.set_interval(timedelta(seconds=1))

# Check if current time is between two times
is_trading_time = algorithm.between_time(
first=time(9, 15), # 9:15 AM
second=time(15, 30) # 3:30 PM
)

Account & Risk Management

# Set account type (determines trading behavior)
algorithm.set_account_type(account_type=AccountType.CASH_POSITIONAL) # Default: Long-term equity
algorithm.set_account_type(account_type=AccountType.CASH_INTRADAY) # Intraday equity trading
algorithm.set_account_type(account_type=AccountType.DERIVATIVE_INTRADAY) # Intraday options/futures
algorithm.set_account_type(account_type=AccountType.DERIVATIVE_POSITIONAL) # Multi-day options/futures

# Set position manager
algorithm.set_position_manager(position_manager_class=MyPositionManager)

Algorithm Lifecycle

Understanding the algorithm lifecycle is crucial for building effective trading strategies. The ProAlgoTrader Core framework calls different methods at specific times during execution.

Method Execution Order

class LifecycleExample(Algorithm):
def __init__(self, *args, **kwargs) -> None:
# 1. FIRST: Constructor called when class is instantiated
super().__init__(*args, **kwargs)
print("Constructor called - setting up basic configuration")

# Configure strategy settings here
self.set_account_type(account_type=AccountType.DERIVATIVE_POSITIONAL)
self.set_position_manager(position_manager=PositionManager)

# Set iteration interval (default is 1 second)
self.set_interval(interval=timedelta(seconds=1)) # High-frequency
# self.set_interval(interval=timedelta(minutes=5)) # Medium-frequency
# self.set_interval(interval=timedelta(hours=1)) # Swing trading

async def initialize(self) -> None:
# 2. SECOND: Called ONCE after market opens and system is ready
print("Initialize called - setting up symbols, charts, and indicators")

# Add symbols, charts, and indicators here
self.symbol = await self.add_equity(symbol_name=SymbolType.Index.NIFTY)
self.chart = await self.add_chart(broker_symbol=self.symbol, timeframe=timedelta(minutes=5))

# This is where you prepare everything for trading

async def next(self) -> None:
# 3. REPEATEDLY: Called every interval (default: every 1 second)
print(f"Next called at {self.current_time}")

# Your trading logic goes here
# This runs continuously during market hours

Method Purposes

MethodWhen CalledPurposeFrequency
__init__()Class instantiationBasic configuration, settingsOnce
initialize()After market opensSetup symbols, charts, indicatorsOnce
next()During market hoursTrading logic executionEvery interval

Timing Control with set_interval()

The next() method frequency can be customized based on your trading strategy:

class TimingExamples(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

# High-frequency trading: Every second (default)
self.set_interval(interval=timedelta(seconds=1))

# Medium-frequency: Every 5 minutes
# self.set_interval(interval=timedelta(minutes=5))

# Swing trading: Every hour
# self.set_interval(interval=timedelta(hours=1))

# Daily signals: Every 6 hours (check 2-3 times per day)
# self.set_interval(interval=timedelta(hours=6))

async def initialize(self) -> None:
# Called once regardless of interval setting
pass

async def next(self) -> None:
# Called at the frequency set by set_interval()
# For swing trading with 1-hour interval, this runs once per hour
# For scalping with 1-second interval, this runs every second
pass

Real-World Strategy Examples

Scalping Strategy (High Frequency)

class ScalpingStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Need quick reactions to price movements
self.set_interval(interval=timedelta(seconds=1))

async def next(self) -> None:
# Executes every second - good for quick entry/exit decisions
pass

Swing Trading Strategy (Low Frequency)

class SwingTradingStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Signals generated hourly, no need for second-by-second checks
self.set_interval(interval=timedelta(hours=1))

async def next(self) -> None:
# Executes every hour - perfect for swing trading signals
# Saves computational resources by not running unnecessarily
pass

Position Monitoring Strategy

class PositionMonitoringStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Check positions every 5 minutes for risk management
self.set_interval(interval=timedelta(minutes=5))

async def next(self) -> None:
# Monitor existing positions, apply stop-loss/take-profit
# Runs every 5 minutes - balances responsiveness with efficiency
pass

Performance Considerations

Optimization Tip: Match your interval to your strategy's signal frequency:

  • 1 second: Scalping, tick-based strategies
  • 1-5 minutes: Intraday momentum strategies
  • 15-30 minutes: Short-term mean reversion
  • 1-4 hours: Swing trading, daily signals
  • Daily: Position-only strategies, long-term signals
# ❌ INEFFICIENT: Hourly signals with second-by-second checking
class InefficientStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.set_interval(interval=timedelta(seconds=1)) # Too frequent!

async def next(self) -> None:
# Only checks signals once per hour, but runs 3600 times per hour
if self.current_time.minute == 0 and self.current_time.second == 0:
self.check_hourly_signals()

# ✅ EFFICIENT: Match interval to signal frequency
class EfficientStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.set_interval(interval=timedelta(hours=1)) # Perfect match!

async def next(self) -> None:
# Runs exactly when needed - once per hour
self.check_hourly_signals()

AccountType Configuration

The AccountType is an optional but recommended configuration for strategy validation. When set, it ensures order parameters match your intended trading style, preventing common mistakes.

Key Benefits:

  • Optional: Strategies work without AccountType (maximum flexibility)
  • ⚙️ Strict Validation: When set, validates all order parameters
  • 🔒 Error Prevention: Catches parameter mismatches before orders
  • 📋 Documentation: Makes your strategy intent clear

Account Type Overview

The AccountType enum combines MarketType and ProductType to define your trading behavior:

from proalgotrader_core.enums.account_type import AccountType

# Available Account Types:
AccountType.CASH_POSITIONAL # Default: Long-term equity trading
AccountType.CASH_INTRADAY # Intraday equity trading
AccountType.DERIVATIVE_INTRADAY # Intraday options/futures trading
AccountType.DERIVATIVE_POSITIONAL # Multi-day options/futures trading

Account Type Selection Guide

🏦 CASH_POSITIONAL (Default)

self.set_account_type(account_type=AccountType.CASH_POSITIONAL)

Best For:

  • ✅ Long-term investment strategies
  • ✅ Swing trading in equity
  • ✅ Buy and hold strategies
  • ✅ Multi-day stock positions

Characteristics:

  • Market: Cash/Equity only
  • Duration: Multi-day (no auto square-off)
  • Settlement: Delivery-based (T+2)

CASH_INTRADAY

self.set_account_type(account_type=AccountType.CASH_INTRADAY)

Best For:

  • ✅ Intraday equity trading
  • ✅ Same-day buy and sell in stocks
  • ✅ Day trading strategies

Characteristics:

  • Market: Cash/Equity only
  • Duration: Same day only (auto square-off at 3:20 PM)
  • Leverage: Higher leverage available

📈 DERIVATIVE_INTRADAY

self.set_account_type(account_type=AccountType.DERIVATIVE_INTRADAY)

Best For:

  • ✅ Intraday options trading
  • ✅ Same-day futures trading
  • ✅ Options buying strategies

Characteristics:

  • Market: Derivatives (options, futures)
  • Duration: Same day only (auto square-off at 3:20 PM)
  • Leverage: High leverage for derivatives

🎯 DERIVATIVE_POSITIONAL

self.set_account_type(account_type=AccountType.DERIVATIVE_POSITIONAL)

Best For:

  • ✅ Multi-day options strategies
  • ✅ Options selling strategies
  • ✅ Futures swing trading

Characteristics:

  • Market: Derivatives (options, futures)
  • Duration: Multi-day (hold until expiry)
  • Flexibility: Can hold positions across multiple days

Important: Validation Only When AccountType is Set

Order validation only happens when you set an AccountType. Without AccountType, you have complete flexibility:

# ✅ NO AccountType = No validation (maximum flexibility)
class FlexibleStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# No account_type set - any combination allowed

async def next(self) -> None:
# Can mix any market_type and product_type freely
await self.buy(
broker_symbol=stock_symbol,
market_type=MarketType.Cash,
product_type=ProductType.MIS, # ✅ Any combo works
order_type=OrderType.MARKET_ORDER,
quantities=100
)

Strict Validation When AccountType is Set

When you do set an AccountType, your order parameters must match:

# ✅ CORRECT: Parameters match DERIVATIVE_POSITIONAL
self.set_account_type(account_type=AccountType.DERIVATIVE_POSITIONAL)

await self.buy(
broker_symbol=option_symbol,
market_type=MarketType.Derivative, # ✅ Matches
product_type=ProductType.NRML, # ✅ Matches
order_type=OrderType.MARKET_ORDER,
quantities=50
)

# ❌ WRONG: Parameters don't match CASH_POSITIONAL
self.set_account_type(account_type=AccountType.CASH_POSITIONAL)

await self.buy(
broker_symbol=option_symbol, # ❌ Options not allowed
market_type=MarketType.Derivative, # ❌ Should be Cash
product_type=ProductType.MIS, # ❌ Should be CNC
order_type=OrderType.MARKET_ORDER,
quantities=50
)

Parameter Matching Reference

Account Typemarket_typeproduct_typeAllowed Symbols
CASH_POSITIONALMarketType.CashProductType.CNCEquity/Stocks only
CASH_INTRADAYMarketType.CashProductType.MISEquity/Stocks only
DERIVATIVE_POSITIONALMarketType.DerivativeProductType.NRMLOptions, Futures
DERIVATIVE_INTRADAYMarketType.DerivativeProductType.MISOptions, Futures

Strategy Integration

Basic Strategy Example

# project/strategy.py
from datetime import timedelta

from proalgotrader_core.algorithm import Algorithm
from proalgotrader_core.enums.account_type import AccountType
from project.position_manager import PositionManager


class Strategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

# Set account type based on your strategy needs:
# CASH_POSITIONAL (default) - Long-term equity trading
# CASH_INTRADAY - Same-day equity trading
# DERIVATIVE_INTRADAY - Same-day options/futures
# DERIVATIVE_POSITIONAL - Multi-day options/futures
self.set_account_type(account_type=AccountType.DERIVATIVE_POSITIONAL)

self.set_position_manager(position_manager=PositionManager)

self.set_interval(interval=timedelta(seconds=1))

async def initialize(self) -> None:
# Add symbols and charts here
pass

async def next(self) -> None:
# Implement your trading logic here
pass

Position Event Methods in Strategy

The Algorithm class provides position event methods that you can override in your strategy:

# project/strategy.py
from proalgotrader_core.algorithm import Algorithm
from proalgotrader_core.protocols.position import PositionProtocol

class Strategy(Algorithm):
async def on_position_open(self, position: PositionProtocol) -> None:
"""Called when a position is opened - override this method in your strategy."""
print(f"Position opened: {position.broker_symbol.symbol_name} | Qty: {position.net_quantities}")

# Set risk-reward for the position
from proalgotrader_core.risk_reward import RiskReward, Stoploss, Target
from proalgotrader_core.enums.risk_reward_unit import RiskRewardUnit

await self.create_risk_reward(
position=position,
stoploss=Stoploss(value=2.0), # 2% stop loss
target=Target(value=4.0), # 4% target
unit=RiskRewardUnit.PERCENTAGE,
)

async def on_position_closed(self, position: PositionProtocol) -> None:
"""Called when a position is closed - override this method in your strategy."""
print(f"Position closed: {position.broker_symbol.symbol_name} | P&L: {position.pnl.profit}")

Note: These methods have default empty implementations in the Algorithm class, so you only need to override them if you want to handle position events.

Position Manager Examples

ProAlgoTrader supports two types of position managers:

Single Position Manager (Global Management)

# project/position_manager.py
from datetime import datetime, timedelta
from proalgotrader_core.protocols.algorithm import AlgorithmProtocol
from proalgotrader_core.protocols.position import PositionProtocol
from proalgotrader_core.protocols.position_manager import PositionManagerProtocol


class PositionManager(PositionManagerProtocol):
def __init__(self, algorithm: AlgorithmProtocol) -> None:
self.algorithm = algorithm
self.exit_time = None

async def initialize(self) -> None:
# Set exit time 5 minutes after strategy starts
self.exit_time = datetime.now().replace(microsecond=0) + timedelta(minutes=5)


async def next(self) -> None:
# Time-based exit strategy
if self.exit_time and self.exit_time <= self.algorithm.current_datetime:
print("Time-based exit: Closing all positions")
await self.algorithm.exit_all_positions()

# Portfolio-level risk management
if self.algorithm.unrealized_pnl.profit >= 1000:
print("Profit target reached: Closing all positions")
await self.algorithm.exit_all_positions()
elif self.algorithm.unrealized_pnl.loss >= 500:
print("Stop loss triggered: Closing all positions")
await self.algorithm.exit_all_positions()

Multiple Position Manager (Per-Position Management)

# project/position_manager.py
from datetime import datetime, timedelta
from proalgotrader_core.protocols.algorithm import AlgorithmProtocol
from proalgotrader_core.protocols.position import PositionProtocol
from proalgotrader_core.protocols.multiple_position_manager import MultiplePositionManagerProtocol


class MultiplePositionManager(MultiplePositionManagerProtocol):
def __init__(self, algorithm: AlgorithmProtocol, position: PositionProtocol) -> None:
self.algorithm = algorithm
self.position = position
self.entry_time = None

async def initialize(self) -> None:
self.entry_time = datetime.now()

async def on_position_open(self, position: PositionProtocol) -> None:
print(f"Position opened: {position.broker_symbol.symbol_name}")

async def on_position_closed(self, position: PositionProtocol) -> None:
print(f"Position closed: {position.broker_symbol.symbol_name}")

async def next(self) -> None:
# Position-specific time-based exit
if self.entry_time and datetime.now() - self.entry_time >= timedelta(minutes=10):
print(f"Time limit reached for {self.position.broker_symbol.symbol_name}")
await self.position.exit()

Setting Up Position Managers

# project/strategy.py
class Strategy(Algorithm):
async def initialize(self) -> None:
# For single position manager (global management)
self.set_position_manager(position_manager_class=PositionManager)

# For multiple position manager (per-position management)
# self.set_position_manager(position_manager_class=MultiplePositionManager)

Advanced Strategy Example

# project/strategy.py
from datetime import time, timedelta
from proalgotrader_core.algorithm import Algorithm
from proalgotrader_core.enums.account_type import AccountType
from proalgotrader_core.enums.candle_type import CandleType
from proalgotrader_core.enums.market_type import MarketType
from proalgotrader_core.enums.order_type import OrderType
from proalgotrader_core.enums.product_type import ProductType
from proalgotrader_core.enums.symbol_type import SymbolType
from proalgotrader_core.indicators import Indicators
from project.position_manager import PositionManager


class AdvancedStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

# Set account type for derivatives trading
self.set_account_type(account_type=AccountType.DERIVATIVE_POSITIONAL)

# Set position manager
self.set_position_manager(position_manager=PositionManager)

# Set strategy interval
self.set_interval(interval=timedelta(seconds=5))

async def initialize(self) -> None:
# Add multiple symbols using correct string format
self.nifty = await self.add_equity(symbol_name=SymbolType.Index.NIFTY)
self.banknifty = await self.add_equity(symbol_name=SymbolType.Index.BANKNIFTY)

# Add options for trading
self.nifty_ce = await self.add_option(
symbol_name=SymbolType.Index.NIFTY,
expiry_input=("Weekly", 0), # Current week
strike_price_input=0, # ATM
option_type="CE"
)

# Add charts with different timeframes
self.nifty_5min = await self.add_chart(
broker_symbol=self.nifty,
timeframe=timedelta(minutes=5),
candle_type=CandleType.REGULAR
)

self.banknifty_1min = await self.add_chart(
broker_symbol=self.banknifty,
timeframe=timedelta(minutes=1),
candle_type=CandleType.HEIKEN_ASHI
)

# Add indicators to charts
# IMPORTANT: Create indicator instance first
self.rsi = Indicators.Momentum.RSI_TV(timeperiod=14)
# MUST register with chart - this initializes the indicator with historical data
await self.nifty_5min.add_indicator(self.rsi)

# Same pattern for all indicators
self.sma = Indicators.Overlap.SMA_TV(timeperiod=20)
await self.nifty_5min.add_indicator(self.sma)

async def next(self) -> None:
# Check trading hours
if not self.between_time(time(9, 15), time(15, 30)):
return

# Get P&L using correct property names
unrealized_pnl = self.unrealized_pnl.pnl
net_pnl = self.net_pnl.pnl

# Risk management
if unrealized_pnl < -5000: # Stop if unrealized loss > 5000
await self.exit_all_positions()
return

# Daily loss limit
if net_pnl < -10000: # Stop if daily loss > 10000
await self.exit_all_positions()
return

# Get indicator values
current_rsi = await self.rsi.get_data(row_number=0, column_name="rsi")
current_sma = await self.sma.get_data(row_number=0, column_name="sma")
current_price = self.nifty.ltp

# Trading logic with indicators
if self.should_buy(current_rsi, current_sma, current_price):
await self.buy(
broker_symbol=self.nifty_ce,
market_type=MarketType.Derivative,
product_type=ProductType.NRML,
order_type=OrderType.MARKET_ORDER,
quantities=50
)

def should_buy(self, rsi: float, sma: float, current_price: float) -> bool:
"""Custom method to determine buy signal using indicators"""
# RSI oversold and price above SMA
return (
rsi < 30 and # RSI oversold
current_price > sma and # Price above SMA
len(self.positions) == 0 # No open positions
)

Best Practices

1. Error Handling

from logzero import logger
from proalgotrader_core.algorithm import Algorithm

class RobustStrategy(Algorithm):
async def next(self) -> None:
try:
# Your trading logic
await self.place_trades()
except Exception as e:
# Log error and continue
logger.error(f"Strategy error: {e}")
# Optionally exit positions
await self.exit_all_positions()

async def place_trades(self):
"""Custom method to place trades"""
# Your trading strategy implementation
pass

2. Risk Management

from proalgotrader_core.algorithm import Algorithm

class RiskManagedStrategy(Algorithm):
async def next(self) -> None:
# Check daily loss limit
if self.net_pnl.pnl < -10000:
logger.warning("Daily loss limit reached")
await self.exit_all_positions()
return

# Check position limits
if len(self.positions) >= 5:
return # Don't open new positions

# Check unrealized loss limit
if self.unrealized_pnl.loss >= 5000:
logger.warning("Unrealized loss limit reached")
await self.exit_all_positions()
return

3. Performance Optimization

from datetime import datetime, timedelta
from proalgotrader_core.algorithm import Algorithm

class OptimizedStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.last_signal_time = None
self.signal_cooldown = timedelta(minutes=5)

async def next(self) -> None:
# Avoid excessive signals
current_time = self.current_datetime
if self.is_in_cooldown(current_time):
return

# Your trading logic here
if await self.should_trade():
await self.place_trades()
self.set_cooldown(current_time)

def is_in_cooldown(self, current_time: datetime) -> bool:
"""Check if we're in cooldown period"""
if self.last_signal_time is None:
return False
return current_time - self.last_signal_time < self.signal_cooldown

def set_cooldown(self, current_time: datetime) -> None:
"""Set cooldown for current signal"""
self.last_signal_time = current_time

async def should_trade(self) -> bool:
"""Custom method to determine if we should trade"""
# Implement your trading conditions
return len(self.positions) == 0

async def place_trades(self):
"""Custom method to place trades"""
# Implement your trading logic
pass

AccountType Strategy Examples

Long-term Equity Investment

class LongTermEquityStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Default CASH_POSITIONAL is perfect for long-term equity
# No need to set explicitly - it's the default
self.set_interval(interval=timedelta(hours=1)) # Check hourly

async def initialize(self) -> None:
# Can only trade equity/stocks with CASH_POSITIONAL
self.reliance = await self.add_equity(symbol_name=SymbolType.Stock.RELIANCE)
self.tcs = await self.add_equity(symbol_name=SymbolType.Stock.TCS)

async def next(self) -> None:
# Buy and hold strategy - positions held for weeks/months
if self.should_buy_and_hold():
await self.buy(
broker_symbol=self.reliance,
market_type=MarketType.Cash, # ✅ Matches CASH_POSITIONAL
product_type=ProductType.CNC, # ✅ Matches CASH_POSITIONAL
order_type=OrderType.MARKET_ORDER,
quantities=100
)

def should_buy_and_hold(self) -> bool:
"""Custom logic for long-term investment signals"""
return False # Implement your logic

Intraday Equity Trading

class IntradayEquityStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Set for intraday equity trading
self.set_account_type(account_type=AccountType.CASH_INTRADAY)
self.set_interval(interval=timedelta(minutes=1)) # Active monitoring

async def initialize(self) -> None:
self.nifty_stocks = [
await self.add_equity(symbol_name=SymbolType.Stock.RELIANCE),
await self.add_equity(symbol_name=SymbolType.Stock.TCS),
await self.add_equity(symbol_name=SymbolType.Stock.INFY)
]

async def next(self) -> None:
# All positions automatically squared off by 3:20 PM
for stock in self.nifty_stocks:
if self.should_buy_intraday(stock):
await self.buy(
broker_symbol=stock,
market_type=MarketType.Cash, # ✅ Matches CASH_INTRADAY
product_type=ProductType.MIS, # ✅ Matches CASH_INTRADAY
order_type=OrderType.MARKET_ORDER,
quantities=100
)

def should_buy_intraday(self, stock) -> bool:
"""Custom logic for intraday signals"""
return False # Implement your logic

Intraday Options Strategy

class IntradayOptionsStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Set for intraday options trading
self.set_account_type(account_type=AccountType.DERIVATIVE_INTRADAY)
self.set_interval(interval=timedelta(seconds=30)) # High frequency

async def initialize(self) -> None:
# Can trade options with DERIVATIVE account types
self.nifty_ce = await self.add_option(
symbol_name=SymbolType.Index.NIFTY,
expiry_input=("Weekly", 0),
strike_price_input=0, # ATM
option_type="CE"
)

async def next(self) -> None:
# Quick options buying/selling - auto squared off by 3:20 PM
if self.should_buy_options():
await self.buy(
broker_symbol=self.nifty_ce,
market_type=MarketType.Derivative, # ✅ Matches DERIVATIVE_INTRADAY
product_type=ProductType.MIS, # ✅ Matches DERIVATIVE_INTRADAY
order_type=OrderType.MARKET_ORDER,
quantities=50 # NIFTY lot size
)

def should_buy_options(self) -> bool:
"""Custom logic for options signals"""
return False # Implement your logic

Options Selling Strategy

class OptionsSellingStrategy(Algorithm):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# Set for positional options trading
self.set_account_type(account_type=AccountType.DERIVATIVE_POSITIONAL)
self.set_interval(interval=timedelta(minutes=15)) # Monitor regularly

async def initialize(self) -> None:
# Set up options for selling strategies
self.nifty_ce_otm = await self.add_option(
symbol_name=SymbolType.Index.NIFTY,
expiry_input=("Weekly", 0),
strike_price_input=2, # 2 strikes OTM
option_type="CE"
)
self.nifty_pe_otm = await self.add_option(
symbol_name=SymbolType.Index.NIFTY,
expiry_input=("Weekly", 0),
strike_price_input=-2, # 2 strikes OTM
option_type="PE"
)

async def next(self) -> None:
# Can hold short options positions for multiple days
if self.should_sell_straddle():
# Sell CE
await self.sell(
broker_symbol=self.nifty_ce_otm,
market_type=MarketType.Derivative, # ✅ Matches DERIVATIVE_POSITIONAL
product_type=ProductType.NRML, # ✅ Matches DERIVATIVE_POSITIONAL
order_type=OrderType.MARKET_ORDER,
quantities=50
)
# Sell PE
await self.sell(
broker_symbol=self.nifty_pe_otm,
market_type=MarketType.Derivative, # ✅ Matches DERIVATIVE_POSITIONAL
product_type=ProductType.NRML, # ✅ Matches DERIVATIVE_POSITIONAL
order_type=OrderType.MARKET_ORDER,
quantities=50
)

def should_sell_straddle(self) -> bool:
"""Custom logic for options selling signals"""
return False # Implement your logic

Common Patterns

Multi-Timeframe Analysis

from datetime import timedelta
from proalgotrader_core.algorithm import Algorithm
from proalgotrader_core.enums.symbol_type import SymbolType
from proalgotrader_core.enums.market_type import MarketType
from proalgotrader_core.enums.order_type import OrderType
from proalgotrader_core.enums.product_type import ProductType
from proalgotrader_core.indicators import Indicators

class MultiTimeframeStrategy(Algorithm):
async def initialize(self) -> None:
self.symbol = await self.add_equity(symbol_name=SymbolType.Index.NIFTY)

# Multiple timeframes
self.chart_1min = await self.add_chart(
broker_symbol=self.symbol,
timeframe=timedelta(minutes=1)
)
self.chart_5min = await self.add_chart(
broker_symbol=self.symbol,
timeframe=timedelta(minutes=5)
)
self.chart_15min = await self.add_chart(
broker_symbol=self.symbol,
timeframe=timedelta(minutes=15)
)

# Add indicators to different timeframes
self.sma_1min = Indicators.Overlap.SMA_TV(timeperiod=20)
await self.chart_1min.add_indicator(self.sma_1min)

self.sma_5min = Indicators.Overlap.SMA_TV(timeperiod=20)
await self.chart_5min.add_indicator(self.sma_5min)

self.sma_15min = Indicators.Overlap.SMA_TV(timeperiod=20)
await self.chart_15min.add_indicator(self.sma_15min)

async def next(self) -> None:
# Analyze multiple timeframes using indicators
trend_1min = await self.get_trend(self.sma_1min)
trend_5min = await self.get_trend(self.sma_5min)
trend_15min = await self.get_trend(self.sma_15min)

# Only trade when all timeframes align
if trend_1min == "UP" and trend_5min == "UP" and trend_15min == "UP":
if len(self.positions) == 0: # No existing positions
await self.buy(
broker_symbol=self.symbol,
market_type=MarketType.Cash,
product_type=ProductType.CNC,
order_type=OrderType.MARKET_ORDER,
quantities=100
)

async def get_trend(self, sma_indicator) -> str:
"""Determine trend direction based on SMA"""
try:
current_sma = await sma_indicator.get_data(row_number=0, column_name="sma")
current_price = self.symbol.ltp

return "UP" if current_price > current_sma else "DOWN"
except:
return "NEUTRAL"

Options Trading

from proalgotrader_core.algorithm import Algorithm
from proalgotrader_core.enums.symbol_type import SymbolType
from proalgotrader_core.enums.market_type import MarketType
from proalgotrader_core.enums.order_type import OrderType
from proalgotrader_core.enums.product_type import ProductType
from proalgotrader_core.indicators import Indicators

class OptionsStrategy(Algorithm):
async def initialize(self) -> None:
# Add underlying
self.nifty = await self.add_equity(symbol_name=SymbolType.Index.NIFTY)

# Add options
self.ce_option = await self.add_option(
symbol_name=SymbolType.Index.NIFTY,
expiry_input=("Weekly", 0), # 0 = current week, 1 = next week
strike_price_input=0, # 0 = ATM, 1 = OTM, -1 = ITM
option_type="CE"
)
self.pe_option = await self.add_option(
symbol_name=SymbolType.Index.NIFTY,
expiry_input=("Weekly", 0), # 0 = current week, 1 = next week
strike_price_input=0, # 0 = ATM, 1 = OTM, -1 = ITM
option_type="PE"
)

# Add chart and indicators for underlying
self.nifty_chart = await self.add_chart(
broker_symbol=self.nifty,
timeframe=timedelta(minutes=5)
)

self.rsi = Indicators.Momentum.RSI_TV(timeperiod=14)
await self.nifty_chart.add_indicator(self.rsi)

async def next(self) -> None:
# Options trading logic with indicators
if await self.should_buy_call():
await self.buy(
broker_symbol=self.ce_option,
market_type=MarketType.Derivative,
product_type=ProductType.NRML,
order_type=OrderType.MARKET_ORDER,
quantities=50 # Options lot size (NIFTY lot size is 50)
)
elif await self.should_buy_put():
await self.buy(
broker_symbol=self.pe_option,
market_type=MarketType.Derivative,
product_type=ProductType.NRML,
order_type=OrderType.MARKET_ORDER,
quantities=50
)

async def should_buy_call(self) -> bool:
"""Determine call option buy signal using RSI"""
try:
if len(self.positions) > 0:
return False # Already have positions

rsi_value = await self.rsi.get_data(row_number=0, column_name="rsi")
return rsi_value < 30 # Oversold, expect upward move
except:
return False

async def should_buy_put(self) -> bool:
"""Determine put option buy signal using RSI"""
try:
if len(self.positions) > 0:
return False # Already have positions

rsi_value = await self.rsi.get_data(row_number=0, column_name="rsi")
return rsi_value > 70 # Overbought, expect downward move
except:
return False

Using Indicators

Indicators in ProAlgoTrader Core follow a two-step process:

  1. Create: Create the indicator instance with desired parameters
  2. Register: Add the indicator to a chart to initialize it with data

⚠️ Important: Until an indicator is registered with chart.add_indicator(), it has no data and cannot be used for analysis.

from datetime import timedelta
from proalgotrader_core.algorithm import Algorithm
from proalgotrader_core.enums.symbol_type import SymbolType
from proalgotrader_core.enums.candle_type import CandleType
from proalgotrader_core.enums.market_type import MarketType
from proalgotrader_core.enums.order_type import OrderType
from proalgotrader_core.enums.product_type import ProductType
from proalgotrader_core.indicators import Indicators

class IndicatorStrategy(Algorithm):
async def initialize(self) -> None:
# Add symbol and chart
self.symbol = await self.add_equity(symbol_name=SymbolType.Stock.RELIANCE)
self.chart = await self.add_chart(
broker_symbol=self.symbol,
timeframe=timedelta(minutes=5),
candle_type=CandleType.REGULAR
)

# STEP 1: Create indicator instances (not usable yet)
self.rsi = Indicators.Momentum.RSI_TV(timeperiod=14)
self.macd = Indicators.Momentum.MACD_TV(fastperiod=12, slowperiod=26, signalperiod=9)
self.sma = Indicators.Overlap.SMA_TV(timeperiod=20)
self.ema = Indicators.Overlap.EMA_TV(timeperiod=20)
self.bbands = Indicators.Overlap.BBANDS_TV(timeperiod=20, nbdevup=2, nbdevdn=2)
self.atr = Indicators.Volatility.ATR_TV(timeperiod=14)

# STEP 2: Register indicators with chart (now they become usable)
# This initializes them with historical data and enables tick-by-tick updates
await self.chart.add_indicator(self.rsi)
await self.chart.add_indicator(self.macd)
await self.chart.add_indicator(self.sma)
await self.chart.add_indicator(self.ema)
await self.chart.add_indicator(self.bbands)
await self.chart.add_indicator(self.atr)

# Alternative: You can also do both steps in one line
# self.supertrend = await self.chart.add_indicator(
# Indicators.Trend.Supertrend(period=10, multiplier=3.0)
# )

async def next(self) -> None:
try:
# Get current indicator values
rsi_value = await self.rsi.get_data(row_number=0, column_name="rsi")
macd_line = await self.macd.get_data(row_number=0, column_name="macd")
macd_signal = await self.macd.get_data(row_number=0, column_name="macdsignal")
sma_value = await self.sma.get_data(row_number=0, column_name="sma")
ema_value = await self.ema.get_data(row_number=0, column_name="ema")
bb_upper = await self.bbands.get_data(row_number=0, column_name="upperband")
bb_lower = await self.bbands.get_data(row_number=0, column_name="lowerband")
atr_value = await self.atr.get_data(row_number=0, column_name="atr")

current_price = self.symbol.ltp

# Trading logic using multiple indicators
bullish_signal = (
rsi_value < 30 and # RSI oversold
macd_line > macd_signal and # MACD bullish crossover
current_price > ema_value and # Price above EMA
current_price <= bb_lower # Price at lower Bollinger Band
)

if bullish_signal and len(self.positions) == 0:
await self.buy(
broker_symbol=self.symbol,
market_type=MarketType.Cash,
product_type=ProductType.CNC,
order_type=OrderType.MARKET_ORDER,
quantities=100
)

except Exception as e:
# Handle indicator data not available yet
pass

Custom Indicator Usage

from datetime import timedelta
from proalgotrader_core.algorithm import Algorithm
from proalgotrader_core.enums.symbol_type import SymbolType
from proalgotrader_core.enums.market_type import MarketType
from proalgotrader_core.enums.order_type import OrderType
from proalgotrader_core.enums.product_type import ProductType
from proalgotrader_core.indicators import CustomIndicator
import polars_talib as pta
import polars as pl

class MyCustomRSI(CustomIndicator):
def __init__(self, timeperiod: int = 14):
super().__init__()
self.timeperiod = timeperiod

def build(self) -> pl.Expr:
return pta.rsi(pl.col("close"), timeperiod=self.timeperiod).alias("custom_rsi")

def window_size(self) -> int:
return self.timeperiod

class CustomIndicatorStrategy(Algorithm):
async def initialize(self) -> None:
self.symbol = await self.add_equity(symbol_name=SymbolType.Stock.TCS)
self.chart = await self.add_chart(
broker_symbol=self.symbol,
timeframe=timedelta(minutes=5)
)

# STEP 1: Create custom indicator instance
self.custom_rsi = MyCustomRSI(timeperiod=21)

# STEP 2: Register with chart (critical for initialization)
await self.chart.add_indicator(self.custom_rsi)

async def next(self) -> None:
try:
rsi_value = await self.custom_rsi.get_data(row_number=0, column_name="custom_rsi")

if rsi_value < 25 and len(self.positions) == 0:
await self.buy(
broker_symbol=self.symbol,
market_type=MarketType.Cash,
product_type=ProductType.CNC,
order_type=OrderType.MARKET_ORDER,
quantities=50
)
except:
pass

Indicator Lifecycle and Best Practices

Understanding Indicator Registration

The indicator system works in two distinct phases:

# ❌ WRONG: This indicator is created but NOT usable
self.rsi = Indicators.Momentum.RSI_TV(timeperiod=14)
# At this point, self.rsi has no data and calling get_data() will fail

# ✅ CORRECT: Register the indicator to make it usable
await self.chart.add_indicator(self.rsi)
# Now self.rsi is initialized with historical data and will update on every tick

What happens during add_indicator()?

  1. Historical Data Initialization: The indicator processes all existing chart data
  2. Data Structure Setup: Internal data storage is configured
  3. Tick Updates Enabled: The indicator will now update with each new price tick
  4. Return Value: add_indicator() returns the same indicator instance

Indicator Data Access

Indicators expose the same data access methods as charts:

Data Property - Get full DataFrame:

# Get full indicator DataFrame
indicator_data = self.rsi.data
print(indicator_data)

# Access specific indicator columns
rsi_values = self.rsi.data.select("rsi")
print(rsi_values)

Get Data Method - Get specific values:

# Get current RSI value (row 0 = most recent)
current_rsi = await self.rsi.get_data(0, "rsi")
print(f"Current RSI: {current_rsi}")

# Get previous RSI value (row 1 = previous value)
previous_rsi = await self.rsi.get_data(1, "rsi")
print(f"Previous RSI: {previous_rsi}")

# Get current indicator data as Series
current_data = await self.rsi.get_data(0)
print(current_data)

Common Usage Patterns:

async def analyze_rsi(self):
# Get RSI values
current_rsi = await self.rsi.get_data(0, "rsi")
previous_rsi = await self.rsi.get_data(1, "rsi")

# Check for RSI signals
if current_rsi > 70 and previous_rsi <= 70:
print("RSI crossed above 70 - Overbought signal")
elif current_rsi < 30 and previous_rsi >= 30:
print("RSI crossed below 30 - Oversold signal")

Common Patterns

class IndicatorLifecycleExample(Algorithm):
async def initialize(self) -> None:
self.chart = await self.add_chart(
broker_symbol=await self.add_equity(symbol_name=SymbolType.Stock.TCS),
timeframe=timedelta(minutes=5)
)

# Pattern 1: Two-step approach (recommended for multiple indicators)
self.rsi = Indicators.Momentum.RSI_TV(timeperiod=14)
self.sma = Indicators.Overlap.SMA_TV(timeperiod=20)

await self.chart.add_indicator(self.rsi)
await self.chart.add_indicator(self.sma)

# Pattern 2: One-line approach (good for single indicators)
self.macd = await self.chart.add_indicator(
Indicators.Momentum.MACD_TV(fastperiod=12, slowperiod=26, signalperiod=9)
)

async def next(self) -> None:
# All indicators are now ready to use
rsi_val = await self.rsi.get_data(row_number=0, column_name="rsi")
sma_val = await self.sma.get_data(row_number=0, column_name="sma")
macd_val = await self.macd.get_data(row_number=0, column_name="macd")

Common Mistakes to Avoid

# ❌ DON'T: Try to use indicator before registration
class BadExample(Algorithm):
async def initialize(self) -> None:
self.rsi = Indicators.Momentum.RSI_TV(timeperiod=14)
# This will fail - indicator has no data yet!
rsi_value = await self.rsi.get_data(row_number=0, column_name="rsi")

# ✅ DO: Always register first, then use
class GoodExample(Algorithm):
async def initialize(self) -> None:
self.chart = await self.add_chart(...)
self.rsi = Indicators.Momentum.RSI_TV(timeperiod=14)
await self.chart.add_indicator(self.rsi)

# Now it's safe to use in next() method

async def next(self) -> None:
rsi_value = await self.rsi.get_data(row_number=0, column_name="rsi")

Troubleshooting

Common Issues

  1. Algorithm not starting

    • Check broker credentials in environment variables
    • Verify market hours
    • Check network connectivity
  2. Orders not executing

    • Verify symbol is tradeable (broker_symbol.can_trade)
    • Check account type and position type compatibility
    • Ensure quantities match lot size
  3. Charts not updating

    • Verify symbol is added correctly
    • Check timeframe settings
    • Ensure market data subscription is active
  4. Indicators not working

    • Ensure await chart.add_indicator(indicator) was called
    • Check if indicator has sufficient historical data
    • Verify correct column names when calling get_data()
    • Use try-except blocks in next() for indicator data access
  5. Strategy running too frequently/slowly

    • Check self.set_interval() setting in __init__()
    • Match interval to your strategy's signal frequency
    • For swing trading, use timedelta(hours=1) instead of default timedelta(seconds=1)
    • Monitor CPU usage - very short intervals may impact performance
  6. Logic not executing

    • Ensure trading logic is in next() method, not __init__() or initialize()
    • Check if market hours validation is blocking execution
    • Verify next() method is async: async def next(self) -> None:

Debug Information

# Check current state
print(f"Current time: {algorithm.current_time}")
print(f"Open positions: {len(algorithm.positions)}")
print(f"Unrealized P&L: {algorithm.unrealized_pnl.pnl}")
print(f"Net P&L: {algorithm.net_pnl.pnl}")

# Check symbol status
print(f"Symbol tradeable: {equity_symbol.can_trade}")
print(f"Current LTP: {equity_symbol.ltp}")

# Check position details
for position in algorithm.positions:
print(f"Position: {position.symbol_name} | Qty: {position.quantity} | P&L: {position.pnl}")

# Check order status
for order in algorithm.orders:
print(f"Order: {order.symbol_name} | Status: {order.status} | Qty: {order.quantity}")

Quick Reference

Algorithm Lifecycle Cheat Sheet

class MyStrategy(Algorithm):
def __init__(self, *args, **kwargs):
# 🔧 SETUP: Called once during class instantiation
super().__init__(*args, **kwargs)
self.set_account_type(...) # ✅ Do this here
self.set_position_manager(...) # ✅ Do this here
self.set_interval(...) # ✅ Do this here

async def initialize(self):
# 🏗️ PREPARATION: Called once after market opens
self.symbol = await self.add_equity(...) # ✅ Do this here
self.chart = await self.add_chart(...) # ✅ Do this here
self.rsi = await self.chart.add_indicator(...) # ✅ Do this here

async def next(self):
# 🔄 EXECUTION: Called repeatedly at set interval
# ✅ Trading logic goes here
# ✅ Position monitoring goes here
# ✅ Risk management goes here

Interval Guidelines

Strategy TypeRecommended IntervalUse Case
Scalpingtimedelta(seconds=1-5)Quick entries/exits
Day Tradingtimedelta(minutes=1-5)Intraday momentum
Swing Tradingtimedelta(hours=1-4)Daily/weekly signals
Position Tradingtimedelta(hours=6-24)Long-term holds

Common Mistakes

Don't put trading logic in __init__() or initialize()

def __init__(self):
# ❌ WRONG: This runs before market opens
if some_condition:
self.buy(...) # This will fail!

Do put trading logic in next()

async def next(self):
# ✅ CORRECT: This runs during market hours
if some_condition:
await self.buy(...)

Next Steps


Need help? Check our API Reference or Contact Support.