
Openalgo
- 115 installs
- 7 repo stars
- Updated May 24, 2026
- marketcalls/openalgo-skills
Helps with ai & agent building tasks.
About
openalgo is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- openalgo
- AI & Agent Building
- AI-coding skill
Openalgo by the numbers
- 115 all-time installs (skills.sh)
- +22 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #3,942 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/marketcalls/openalgo-skills --skill openalgoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 115 |
|---|---|
| repo stars | ★ 7 |
| Last updated | May 24, 2026 |
| Repository | marketcalls/openalgo-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
OpenAlgo — Trading Skill for Indian Markets
OpenAlgo is a broker-agnostic, self-hosted trading platform. One Python SDK (pip install openalgo) talks to 30+ Indian brokers behind a unified REST + WebSocket interface. This skill covers the complete SDK surface plus production-ready helpers and examples for the seven core workflows traders ask for:
1. Order execution — equity, F&O, options-by-offset, multi-leg, basket, split, smart 2. Custom execution algos — limit-order chasing, auto-modify, time/price triggered cancel 3. Scanners — multiquotes + history + filter pipelines 4. Visualization — heatmaps, OI charts, seasonality, gainers/losers, PCR dashboards 5. Backtesting — vectorbt glue with realistic Indian fees, NIFTY benchmark 6. Charting — candles (category x-axis, no weekend gaps), depth ladder, option-chain OI, IV smile 7. Real-time streaming — LTP / Quote / Depth WebSocket, reconnect loop, callback routing
Setup
pip install -U "openalgo[indicators]"
pip install -r requirements.txt # includes vectorbt, TA-Lib, plotly, duckdb, dotenv
cp .env.sample .env # fill in OPENALGO_API_KEY and host/ws URLsMinimal init (every script in this skill starts the same way):
import os
from dotenv import find_dotenv, load_dotenv
from openalgo import api
load_dotenv(find_dotenv(), override=False)
client = api(
api_key=os.environ["OPENALGO_API_KEY"],
host=os.environ.get("OPENALGO_HOST", "http://127.0.0.1:5000"),
ws_url=os.environ.get("OPENALGO_WS_URL", "ws://127.0.0.1:8765"),
)For repo-resident scripts prefer the shared helper:
from scripts.openalgo_client import get_client
client = get_client()Safety Rules — Always Enforce
1. Iterate in analyzer mode first. Toggle client.analyzertoggle(mode=True) so the SDK simulates responses without hitting the broker. Switch off only after the strategy is reviewed. 2. Confirm before live orders. Print a readable preview (symbol, side, qty, product, price, notional) and wait for user confirmation unless the user has explicitly authorized auto-execution for the current session. 3. Default to `LIMIT` over `MARKET`. Quote the symbol first and place a marketable-limit at LTP ± a few ticks. MARKET only when the user explicitly asks. 4. Validate F&O lot-size multiples. Load the bundled assets/LotSize.csv (or call client.symbol() for the current lotsize) and reject non-multiples before placement. 5. Warn on notional > Rs 50,000. For F&O, use lotsize × strike as a worst-case proxy when price is unknown. 6. Never `CNC` on F&O / commodity / currency. Only MIS (intraday) or NRML (overnight) for those segments. CNC is equity-delivery only. 7. Never hardcode API keys. Always read from .env via find_dotenv(). Reject scripts that contain literal 64-char hex keys. 8. Multi-leg execution needs explicit per-leg confirmation when run live. optionsmultiorder and basketorder route to the broker as separate orders that can partially fail — handle the results[] array, don't trust the top-level status. 9. Rate limits matter. Order APIs are capped at 10/sec (smart orders 2/sec), data APIs at 50/sec. Use the retry-with-backoff helper in scripts/orders.py rather than tight loops. 10. WebSocket reconnect is the user's responsibility. Use the subscribe() context manager in scripts/stream.py — it handles auth, heartbeat, and re-subscription on disconnect.
File-Output Convention
When this skill generates code for a specific action, write outputs into a per-action subfolder, created on-demand (never pre-created):
openalgo_workspace/
├── execution/
│ ├── atm_straddle/ # straddle.py, run.log, trade_journal.csv
│ └── iron_condor/
├── execution_algos/
│ ├── limit_chaser_reliance/ # chaser.py, fills.csv
│ └── twap_slicer_sbin/
├── scanners/
│ ├── rsi_oversold/ # scan.py, results_2026-05-24.csv
│ └── breakout/
├── visualization/
│ └── sector_heatmap/ # heatmap.py, heatmap_2026-05-24.html
├── backtesting/
│ ├── supertrend_sbin/ # backtest.py, trades.csv, equity.html
│ └── ema_crossover_nifty50/
├── charting/
│ └── nifty_option_chain_oi/ # chart.py, oi_27jan26.html
└── streaming/
└── nifty_depth_stream/ # stream.py, ticks.parquetEach subfolder is self-contained — script, generated data, plots, logs. The user can rm -rf any folder without affecting others.
Constants — Order Surface
| Category | Values |
|---|---|
| Exchange | NSE BSE (equity); NFO BFO (F&O); CDS BCD (currency); MCX NCDEX NCO (commodity); NSE_INDEX BSE_INDEX MCX_INDEX GLOBAL_INDEX (quote-only) |
| Action | BUY SELL |
| Product | CNC (equity delivery only), MIS (intraday all segments), NRML (F&O / commodity overnight) |
| Price type | MARKET, LIMIT, SL (stop-loss limit), SL-M (stop-loss market) |
| Validity | DAY (default), IOC |
| Option offset | ATM, ITM1..ITM20, OTM1..OTM20 (resolved against ATM strike by the SDK) |
| WS mode | 1 = LTP, 2 = Quote (OHLC+vol), 3 = Depth (with depth_level 5/20/30/50) |
| WS verbose | 0/False silent, 1/True connection logs, 2 all data updates |
Full grammar in references/order-constants.md and references/symbol-format.md. F&O lot sizes ship as a CSV at assets/LotSize.csv (see references/lot-sizes.md).
Symbol Format Quick-Reference
Equity: RELIANCE (just the base symbol)
Futures: NIFTY30JUN26FUT [base][DDMMMYY]FUT
Options: NIFTY30JUN2626500CE [base][DDMMMYY][strike][CE/PE]Index quote-only symbols (no trading, use for quotes/history/ws): NIFTY BANKNIFTY FINNIFTY MIDCPNIFTY NIFTYNXT50 SENSEX BANKEX (and 80+ more — see references/symbol-format.md)
Complete SDK Method Map
| Group | Method | Doc |
|---|---|---|
| Order placement | placeorder | order-management |
placesmartorder | "" — position-aware sizing | |
optionsorder | "" — by offset (ATM/ITMn/OTMn) | |
optionsmultiorder | "" — multi-leg (iron condor, straddle, diagonal) | |
basketorder | "" — list of orders, results[] | |
splitorder | "" — slice large qty into N chunks | |
| Order management | modifyorder | "" |
cancelorder | "" | |
cancelallorder | "" | |
closeposition | "" — square off all | |
| GTT (REST-only) | placegttorder / modifygttorder / cancelgttorder / gttorderbook | order-management |
| Order info | orderstatus | order-information |
openposition | "" — for a specific symbol | |
| Market data | quotes | market-data |
multiquotes | "" — up to many symbols, used by scanners | |
depth | "" — full Level-2 book | |
history | "" — source="api" (broker) or source="db" (Historify DuckDB) | |
intervals | "" | |
| Symbol services | symbol | symbol-services |
search | "" — fuzzy lookup | |
expiry | "" — F&O expiry dates | |
instruments | "" — full master | |
| Options analytics | optionsymbol | options-services |
optionchain | "" — full CE/PE chain with OI | |
syntheticfuture | "" | |
optiongreeks | "" — delta/gamma/theta/vega/rho + IV | |
| Account | funds | account-services |
margin | "" — multi-leg margin calculator | |
orderbook | "" | |
tradebook | "" | |
positionbook | "" | |
holdings | "" | |
| Calendar | holidays(year) | market-calendar |
timings(date) | "" | |
checkholiday(date) | "" | |
| Analyzer | analyzerstatus / analyzertoggle(mode=True) | analyzer-services |
| Alerts | telegram(username, message) | alerts |
whatsapp(text, to=..., image=..., document=...) | "" | |
| WebSocket | connect() / disconnect() | websocket-streaming |
subscribe_ltp / subscribe_quote / subscribe_depth (+ unsubscribe variants) | "" | |
get_quotes() — pulls latest cached snapshot | "" | |
| Indicators | from openalgo import ta → ta.supertrend, ta.donchian, ta.ichimoku, ta.hma, ta.kama, ta.alma, ta.zlema, ta.vwma, ta.exrem, ta.crossover, ta.crossunder, ta.flip | indicators |
The Python SDK doesn't expose every kwarg in its docstrings — when a parameter is missing or unclear, fall back to the per-endpoint REST docs at /Users/openalgo/test-zerodha/openalgo/docs/api/<group>/<endpoint>.md. That tree is parameter-complete.
Quick Template — Place an Order with Preview + Analyzer Safety
import os
from dotenv import find_dotenv, load_dotenv
from openalgo import api
load_dotenv(find_dotenv(), override=False)
client = api(
api_key=os.environ["OPENALGO_API_KEY"],
host=os.environ.get("OPENALGO_HOST", "http://127.0.0.1:5000"),
)
SYMBOL, EXCHANGE = "RELIANCE", "NSE"
ACTION, QTY, PRODUCT = "BUY", 1, "MIS"
# 1. Quote to anchor a marketable limit price (safer than MARKET)
q = client.quotes(symbol=SYMBOL, exchange=EXCHANGE)["data"]
limit_price = round(q["ltp"] * 1.001, 2) if ACTION == "BUY" else round(q["ltp"] * 0.999, 2)
notional = limit_price * QTY
print(f"--- Order Preview ---")
print(f" {ACTION} {QTY} {SYMBOL} @ LIMIT {limit_price} notional Rs {notional:,.2f}")
print(f" Product: {PRODUCT} LTP: {q['ltp']}")
if input("Proceed? [y/N] ").strip().lower() != "y":
raise SystemExit("aborted")
response = client.placeorder(
strategy=os.environ.get("OPENALGO_DEFAULT_STRATEGY", "python"),
symbol=SYMBOL,
exchange=EXCHANGE,
action=ACTION,
price_type="LIMIT",
product=PRODUCT,
quantity=str(QTY),
price=str(limit_price),
)
print("ORDER:", response)Quick Template — Stream LTP with Reconnect
import os, time
from dotenv import find_dotenv, load_dotenv
from openalgo import api
load_dotenv(find_dotenv(), override=False)
client = api(
api_key=os.environ["OPENALGO_API_KEY"],
host=os.environ.get("OPENALGO_HOST", "http://127.0.0.1:5000"),
ws_url=os.environ.get("OPENALGO_WS_URL", "ws://127.0.0.1:8765"),
verbose=True,
)
instruments = [
{"exchange": "NSE_INDEX", "symbol": "NIFTY"},
{"exchange": "NSE", "symbol": "RELIANCE"},
]
def on_ltp(msg):
d = msg["data"]
print(f"{d['symbol']:<12} LTP {d['ltp']} @ {d['timestamp']}")
client.connect()
client.subscribe_ltp(instruments, on_data_received=on_ltp)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
client.unsubscribe_ltp(instruments)
client.disconnect()Quick Template — History from Direct DuckDB (Historify)
client.history(..., source="db") routes through REST. For bulk multi-symbol pulls or backtesting, hit the DuckDB file directly:
import os, duckdb, pandas as pd
from dotenv import find_dotenv, load_dotenv
load_dotenv(find_dotenv(), override=False)
DB = os.environ["HISTORIFY_DUCKDB_PATH"] # e.g. /srv/openalgo/db/historify.duckdb
con = duckdb.connect(DB, read_only=True)
# Historify schema: table `market_data` with epoch timestamps
df = con.execute("""
SELECT
symbol,
exchange,
to_timestamp(timestamp) AT TIME ZONE 'Asia/Kolkata' AS ts,
open, high, low, close, volume
FROM market_data
WHERE symbol = ?
AND exchange = ?
AND timestamp >= EXTRACT(EPOCH FROM TIMESTAMP '2024-01-01')
ORDER BY timestamp
""", ["SBIN", "NSE"]).fetchdf()
con.close()
df["ts"] = pd.to_datetime(df["ts"]).dt.tz_localize(None)
df = df.set_index("ts")
print(df.tail())Full Historify usage, multi-symbol joins, and resampling alignment with NSE 09:15 IST in references/duckdb-historify.md.
Indicator Rule (matches vectorbt-backtesting-skills)
- TA-Lib for the standard set:
EMA,SMA,RSI,MACD,ATR,BBANDS,ADX,STDDEV,MOM. - `openalgo.ta` for:
supertrend,donchian,ichimoku,hma,kama,alma,zlema,vwma. - `openalgo.ta` for signal cleaning:
exrem,crossover,crossunder,flip— always.fillna(False)beforeexrem.
Never use VectorBT's built-in indicators (vbt.MA.run etc.).
Helper Scripts (scripts/)
| File | Purpose |
|---|---|
openalgo_client.py | get_client() — bootstraps from .env with find_dotenv() |
symbols.py | resolve_symbol, build_fut_symbol, build_opt_symbol, parse_opt_symbol |
lotsize.py | load_lot_sizes(), nearest_lot(symbol, quantity), validate_fno_lot() |
orders.py | preview_order, place_with_confirmation, retry_on_rate_limit |
execution.py | LimitChaser (peg the touch), TWAPSlicer, IcebergSlicer, OrderManager |
option_analytics.py | atm_strike, pcr, max_pain, iv_skew, payoff_diagram |
scanner.py | Scanner — multi-symbol filter pipeline over multiquotes + history |
stream.py | subscribe() context manager — auth, heartbeat, auto-reconnect |
plotting.py | candlestick_no_gaps, oi_histogram, heatmap, depth_ladder |
duckdb_data.py | load_ohlcv(symbol, ...) from Historify, multi-symbol bulk pull, resample |
fees.py | Indian market cost model (equity / F&O / intraday / delivery) |
ta_helpers.py | Ergonomic wrappers — TA-Lib + openalgo.ta combined |
trade_logger.py | Persistent CSV/SQLite trade journal |
Examples Catalog (examples/)
| Folder | Coverage |
|---|---|
01_execution/ | Equity, ATM straddle, iron condor, basket rebalance, smart-order sizing, supertrend live, GTT OCO |
02_scanners/ | Gainers/losers, breakout, RSI oversold, volume surge, OI change, pre-open gap |
03_visualization/ | Sector heatmap, YTD heatmap, CAGR heatmap, seasonality, OI histogram, PCR dashboard |
04_backtesting/ | EMA crossover, Supertrend, Opening Range Breakout, multi-symbol screener backtest |
05_charting/ | Candlestick with indicators, option chain OI chart, max pain, IV smile, depth ladder |
06_streaming/ | LTP, Quote, Depth (20-level), callback router, stream → Telegram alert, reconnect loop |
07_execution_algos/ | Limit-order chaser, TWAP slicer, iceberg via splitorder, time-based cancel, price-based cancel-and-replace, conditional bracket |
Reference Files (references/)
| Need | File |
|---|---|
| Order placement / modification / cancellation + GTT | order-management.md |
| Order status & open positions | order-information.md |
| Quotes, depth, history, intervals | market-data.md |
| Symbol, search, expiry, instruments | symbol-services.md |
| Option chain, Greeks, synthetic future, ATM/ITM/OTM offsets | options-services.md |
| Funds, margin, books, holdings | account-services.md |
| Holidays, timings, holiday check | market-calendar.md |
| Sandbox / analyzer mode | analyzer-services.md |
| WebSocket protocol, modes, depth_level, verbose | websocket-streaming.md |
| Telegram + WhatsApp alerts | alerts.md |
openalgo.ta complete reference | indicators.md |
| Custom limit-order execution algos (chaser, TWAP, iceberg) | execution-algos.md |
| Direct DuckDB access to Historify market data | duckdb-historify.md |
| Equity / Futures / Options symbol grammar + index lists | symbol-format.md |
| F&O lot sizes (Apr/May/Jun 2026 + how to update) | lot-sizes.md |
| Constants (exchange, product, price type, action) | order-constants.md |
| Rate limits & retry guidance | rate-limits.md |
| Common multi-step recipes | common-workflows.md |
| Error patterns & troubleshooting | error-codes.md |
How to Pick Live vs Analyzer Mode
status = client.analyzerstatus()["data"]
if status["analyze_mode"]:
print(f"[ANALYZER] simulated mode — orders will not reach broker. logs: {status['total_logs']}")
else:
print("[LIVE] orders will execute on the broker")While developing a new strategy: client.analyzertoggle(mode=True). When the user is satisfied: ask for explicit go-live confirmation, then client.analyzertoggle(mode=False).
Output Encoding Rules
- Never put emojis in generated code or log output. Plain ASCII only.
- Plotly charts use
template="plotly_dark"and candlesticks usexaxis_type="category"to skip weekend gaps. - Trade journals / scan results write to CSV with a date-stamped filename inside the action's workspace folder.
- All datetime indexes are tz-naive after dropping
Asia/Kolkata(matches the vectorbt skill's convention so dataframes round-trip cleanly).
Symbol,Lot Size (Apr 2026),Lot Size (May 2026),Lot Size (Jun 2026)
BANKNIFTY,30,30,30
FINNIFTY,60,60,60
MIDCPNIFTY,120,120,120
NIFTY,65,65,65
NIFTYNXT50,25,25,25
360ONE,500,500,500
ABB,125,125,125
ABCAPITAL,3100,3100,3100
ADANIENSOL,675,675,675
ADANIENT,309,309,309
ADANIGREEN,600,600,600
ADANIPORTS,475,475,475
ADANIPOWER,3550,3550,3550
ALKEM,125,125,125
AMBER,100,100,100
AMBUJACEM,1050,1050,1050
ANGELONE,2500,2500,2500
APLAPOLLO,350,350,350
APOLLOHOSP,125,125,125
ASHOKLEY,5000,5000,5000
ASIANPAINT,250,250,250
ASTRAL,425,425,425
AUBANK,1000,1000,1000
AUROPHARMA,550,550,550
AXISBANK,625,625,625
BAJAJ-AUTO,75,75,75
BAJAJFINSV,250,250,250
BAJAJHLDNG,50,50,50
BAJFINANCE,750,750,750
BANDHANBNK,3600,3600,3600
BANKBARODA,2925,2925,2925
BANKINDIA,5200,5200,5200
BDL,350,350,350
BEL,1425,1425,1425
BHARATFORG,500,500,500
BHARTIARTL,475,475,475
BHEL,2625,2625,2625
BIOCON,2500,2500,2500
BLUESTARCO,325,325,325
BOSCHLTD,25,25,25
BPCL,1975,1975,1975
BRITANNIA,125,125,125
BSE,375,375,375
CAMS,750,750,750
CANBK,6750,6750,6750
CDSL,475,475,475
CGPOWER,850,850,850
CHOLAFIN,625,625,625
CIPLA,375,375,375
COALINDIA,1350,1350,1350
COCHINSHIP,400,400,400
COFORGE,375,375,375
COLPAL,225,225,225
CONCOR,1250,1250,1250
CROMPTON,1800,1800,1800
CUMMINSIND,200,200,200
DABUR,1250,1250,1250
DALBHARAT,325,325,325
DELHIVERY,2075,2075,2075
DIVISLAB,100,100,100
DIXON,50,50,50
DLF,825,825,825
DMART,150,150,150
DRREDDY,625,625,625
EICHERMOT,100,100,100
ETERNAL,2425,2425,2425
EXIDEIND,1800,1800,1800
FEDERALBNK,5000,5000,5000
FORCEMOT,25,25,25
FORTIS,775,775,775
GAIL,3150,3150,3150
GLENMARK,375,375,375
GMRAIRPORT,6975,6975,6975
GODFRYPHLP,275,275,275
GODREJCP,500,500,500
GODREJPROP,275,275,275
GRASIM,250,250,250
HAL,150,150,150
HAVELLS,500,500,500
HCLTECH,350,350,350
HDFCAMC,300,300,300
HDFCBANK,550,550,550
HDFCLIFE,1100,1100,1100
HEROMOTOCO,150,150,150
HINDALCO,700,700,700
HINDPETRO,2025,2025,2025
HINDUNILVR,300,300,300
HINDZINC,1225,1225,1225
HUDCO,2775,-,-
HYUNDAI,275,275,275
ICICIBANK,700,700,700
ICICIGI,325,325,325
ICICIPRULI,925,925,925
IDEA,71475,71475,71475
IDFCFIRSTB,9275,9275,9275
IEX,3750,3750,3750
INDHOTEL,1000,1000,1000
INDIANB,1000,1000,1000
INDIGO,150,150,150
INDUSINDBK,700,700,700
INDUSTOWER,1700,1700,1700
INFY,400,400,400
INOXWIND,3575,3575,3575
IOC,4875,4875,4875
IREDA,3450,3450,3450
IRFC,4250,4250,4250
ITC,1600,1600,1600
JINDALSTEL,625,625,625
JIOFIN,2350,2350,2350
JSWENERGY,1000,1000,1000
JSWSTEEL,675,675,675
JUBLFOOD,1250,1250,1250
KALYANKJIL,1175,1175,1175
KAYNES,100,100,100
KEI,175,175,175
KFINTECH,500,500,500
KOTAKBANK,2000,2000,2000
KPITTECH,425,425,425
LAURUSLABS,850,850,850
LICHSGFIN,1000,1000,1000
LICI,700,700,700
LODHA,450,450,450
LT,175,175,175
LTF,2250,2250,2250
LTM,150,150,150
LUPIN,425,425,425
M&M,200,200,200
MANAPPURAM,3000,3000,3000
MANKIND,225,225,225
MARICO,1200,1200,1200
MARUTI,50,50,50
MAXHEALTH,525,525,525
MAZDOCK,200,200,200
MCX,625,625,625
MFSL,400,400,400
MOTHERSON,6150,6150,6150
MOTILALOFS,775,775,775
MPHASIS,275,275,275
MUTHOOTFIN,275,275,275
NAM-INDIA,625,625,625
NATIONALUM,3750,3750,3750
NAUKRI,375,375,375
NBCC,6500,6500,6500
NESTLEIND,500,500,500
NHPC,6400,6400,6400
NMDC,6750,6750,6750
NTPC,1500,1500,1500
NUVAMA,500,500,500
NYKAA,3125,3125,3125
OBEROIRLTY,350,350,350
OFSS,75,75,75
OIL,1400,1400,1400
ONGC,2250,2250,2250
PAGEIND,15,15,15
PATANJALI,900,900,900
PAYTM,725,725,725
PERSISTENT,100,100,100
PETRONET,1900,1900,1900
PFC,1300,1300,1300
PGEL,950,950,950
PHOENIXLTD,350,350,350
PIDILITIND,500,500,500
PIIND,175,175,175
PNB,8000,8000,8000
PNBHOUSING,650,650,650
POLICYBZR,350,350,350
POLYCAB,125,125,125
POWERGRID,1900,1900,1900
POWERINDIA,50,50,50
PPLPHARMA,2625,-,-
PREMIERENE,575,575,575
PRESTIGE,450,450,450
RBLBANK,3175,3175,3175
RECLTD,1400,1400,1400
RELIANCE,500,500,500
RVNL,1525,1525,1525
SAIL,4700,4700,4700
SAMMAANCAP,4300,4300,4300
SBICARD,800,800,800
SBILIFE,375,375,375
SBIN,750,750,750
SHREECEM,25,25,25
SHRIRAMFIN,825,825,825
SIEMENS,175,175,175
SOLARINDS,50,50,50
SONACOMS,1225,1225,1225
SRF,200,200,200
SUNPHARMA,350,350,350
SUPREMEIND,175,175,175
SUZLON,9025,9025,9025
SWIGGY,1300,1300,1300
TATACONSUM,550,550,550
TATAELXSI,100,100,100
TATAPOWER,1450,1450,1450
TATASTEEL,5500,5500,5500
TATATECH,800,-,-
TCS,175,175,175
TECHM,600,600,600
TIINDIA,200,200,200
TITAN,175,175,175
TMPV,800,800,800
TORNTPHARM,250,250,250
TORNTPOWER,425,-,-
TRENT,100,100,100
TVSMOTOR,175,175,175
ULTRACEMCO,50,50,50
UNIONBANK,4425,4425,4425
UNITDSPR,400,400,400
UNOMINDA,550,550,550
UPL,1355,1355,1355
VBL,1125,1125,1125
VEDL,1150,1150,1150
VMM,4850,4850,4850
VOLTAS,375,375,375
WAAREEENER,175,175,175
WIPRO,3000,3000,3000
YESBANK,31100,31100,31100
ZYDUSLIFE,900,900,900
"""Rebalance a basket of equity positions to a target weight.
Workflow:
1. Read current holdings via `client.holdings()`
2. Compute target rupee allocation per symbol (equal-weight or custom)
3. For each symbol: quote -> compute delta qty (target - current)
4. Place all rebalancing orders in one `basketorder` call
5. Verify per-leg success; alert summary
Output folder: openalgo_workspace/execution/basket_rebalance/
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.responses import (
available_cash, ensure_success, extract_ltp, extract_orderids_basket,
)
from scripts.trade_logger import open_journal
# ---- config --------------------------------------------------------------
TARGET_WEIGHTS = { # symbol -> portfolio weight (must sum <= 1.0)
"RELIANCE": 0.20,
"TCS": 0.15,
"INFY": 0.15,
"HDFCBANK": 0.20,
"ICICIBANK": 0.15,
"SBIN": 0.15,
}
EXCHANGE = "NSE"
PRODUCT = "CNC"
TARGET_DEPLOYED_PCT = 90 # use 90% of available cash; keep 10% buffer
ALERTS = ("telegram",)
# ---- bootstrap -----------------------------------------------------------
client = get_client()
strategy = f"{default_strategy_tag()}_rebalance"
workdir = Path("openalgo_workspace/execution/basket_rebalance")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / "journal.csv")
# ---- 1. Current holdings -------------------------------------------------
holdings = client.holdings()
holdings_data = (holdings.get("data") or {}).get("holdings", []) if isinstance(holdings, dict) else []
current_qty = {h["symbol"].upper(): int(h["quantity"]) for h in holdings_data
if h.get("exchange") == EXCHANGE and h.get("product") == PRODUCT}
print(f"Current holdings on {EXCHANGE}/{PRODUCT}: {current_qty}")
# ---- 2. Compute target rupee allocation ---------------------------------
cash = available_cash(client.funds())
holdings_value = sum(float(h.get("pnl", 0)) + float(h.get("quantity", 0)) * float(h.get("average_price", 0))
for h in holdings_data)
deployable = (cash + holdings_value) * TARGET_DEPLOYED_PCT / 100
print(f"Available cash: Rs {cash:,.2f} estimated total: Rs {cash + holdings_value:,.2f}")
print(f"Deploying: Rs {deployable:,.2f}")
# ---- 3. Build per-symbol orders -----------------------------------------
orders_to_place = []
for symbol, weight in TARGET_WEIGHTS.items():
target_rs = deployable * weight
quote = client.quotes(symbol=symbol, exchange=EXCHANGE)
ltp = extract_ltp(quote)
target_qty = int(target_rs // ltp)
current = current_qty.get(symbol, 0)
delta = target_qty - current
print(f" {symbol:<10} LTP {ltp:>8.2f} current {current:>5d} target {target_qty:>5d} delta {delta:>+5d}")
if delta == 0:
continue
orders_to_place.append({
"symbol": symbol,
"exchange": EXCHANGE,
"action": "BUY" if delta > 0 else "SELL",
"quantity": abs(delta),
"pricetype": "MARKET",
"product": PRODUCT,
})
journal.write(strategy=strategy, symbol=symbol, exchange=EXCHANGE,
action="BUY" if delta > 0 else "SELL", event="planned",
quantity=abs(delta), price=ltp)
# ---- 4. Confirm + place as a basket -------------------------------------
if not orders_to_place:
print("\nPortfolio already at target weights. Nothing to do.")
raise SystemExit()
print(f"\nPlacing {len(orders_to_place)} orders as a basket.")
if input("Confirm? [y/N] ").strip().lower() != "y":
raise SystemExit("aborted")
resp = client.basketorder(orders=orders_to_place)
ensure_success(resp, action="basketorder")
success_ids = extract_orderids_basket(resp)
print(f"\n{len(success_ids)}/{len(orders_to_place)} legs placed successfully")
# ---- 5. Per-leg journal + alert -----------------------------------------
for leg, oid in zip(orders_to_place, success_ids):
journal.write(strategy=strategy, symbol=leg["symbol"], exchange=EXCHANGE,
action=leg["action"], event="placed", order_id=oid,
quantity=leg["quantity"])
failed = [r for r in resp.get("results", []) if r.get("status") != "success"]
summary = (
f"[REBALANCE COMPLETE]\n"
f"Symbols changed: {len(orders_to_place)}\n"
f"Legs filled OK: {len(success_ids)}\n"
f"Legs failed: {len(failed)}\n"
f"Deployed: Rs {deployable:,.2f}"
)
notify(client, summary, via=ALERTS)
print("\n" + summary)
print(f"Journal: {workdir / 'journal.csv'}")
journal.close()
"""Place an iron condor via `optionsmultiorder` and verify each leg.
Iron condor: sell an OTM CE spread + sell an OTM PE spread. Net
short premium, defined risk on both sides.
Workflow:
1. Pre-flight: margin check via `client.margin`
2. Pre-flight: cash check vs. `client.funds`
3. Place 4 legs in one call via `optionsmultiorder`
4. Inspect `results[]` — each leg can independently fail
5. For each successfully placed leg, poll for fill and journal it
6. Send a WhatsApp summary
Output folder: openalgo_workspace/execution/iron_condor_nifty/
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.responses import (
avg_fill_price, available_cash, extract_orderids_basket,
poll_until_filled, total_margin_required,
)
from scripts.trade_logger import open_journal
# ---- config --------------------------------------------------------------
UNDERLYING = "NIFTY"
UNDERLYING_EXCHANGE = "NSE_INDEX"
EXPIRY = "30JUN26"
QUANTITY = 75 # 1 lot per leg
# Wing widths (offsets from ATM, in strikes)
SHORT_OFFSET = "OTM4" # short call/put close to spot
LONG_OFFSET = "OTM6" # long wings further out
ALERTS = ("telegram", "whatsapp")
# ---- bootstrap -----------------------------------------------------------
client = get_client()
strategy = f"{default_strategy_tag()}_iron_condor"
workdir = Path(f"openalgo_workspace/execution/iron_condor_{UNDERLYING.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / "journal.csv")
# ---- 1 + 2. pre-flight margin + cash check -----------------------------
# Build the four legs
LEGS = [
{"offset": LONG_OFFSET, "option_type": "CE", "action": "BUY", "quantity": QUANTITY},
{"offset": LONG_OFFSET, "option_type": "PE", "action": "BUY", "quantity": QUANTITY},
{"offset": SHORT_OFFSET, "option_type": "CE", "action": "SELL", "quantity": QUANTITY},
{"offset": SHORT_OFFSET, "option_type": "PE", "action": "SELL", "quantity": QUANTITY},
]
# Margin calculator wants explicit symbols. Resolve via optionsymbol first.
priced_legs = []
for leg in LEGS:
sym_resp = client.optionsymbol(
underlying=UNDERLYING, exchange=UNDERLYING_EXCHANGE,
expiry_date=EXPIRY, offset=leg["offset"], option_type=leg["option_type"],
)
priced_legs.append({
"symbol": sym_resp["symbol"],
"exchange": sym_resp["exchange"],
"action": leg["action"],
"product": "NRML",
"pricetype": "MARKET",
"quantity": str(leg["quantity"]),
})
margin_resp = client.margin(positions=priced_legs)
margin_needed = total_margin_required(margin_resp)
cash = available_cash(client.funds())
print(f"Margin required: Rs {margin_needed:,.2f}")
print(f"Available cash: Rs {cash:,.2f}")
if margin_needed > cash * 0.9:
raise SystemExit(f"insufficient buffer: need Rs {margin_needed:,.2f}, have Rs {cash:,.2f}")
# ---- 3. Place the multi-leg order ---------------------------------------
print(f"\nPlacing iron condor: SELL {SHORT_OFFSET}, BUY {LONG_OFFSET}")
for leg in priced_legs:
journal.write(strategy=strategy, symbol=leg["symbol"], exchange=leg["exchange"],
action=leg["action"], event="planned", quantity=int(leg["quantity"]))
resp = client.optionsmultiorder(
strategy=strategy,
underlying=UNDERLYING,
exchange=UNDERLYING_EXCHANGE,
expiry_date=EXPIRY,
legs=LEGS,
)
# ---- 4 + 5. Per-leg verification ---------------------------------------
print(f"\nMulti-order response: status={resp.get('status')}, "
f"underlying_ltp={resp.get('underlying_ltp')}")
success_legs = [r for r in resp.get("results", []) if r.get("status") == "success"]
failed_legs = [r for r in resp.get("results", []) if r.get("status") != "success"]
for leg in success_legs:
print(f" leg{leg.get('leg')} {leg.get('action')} {leg.get('symbol')} -> id {leg.get('orderid')}")
journal.write(strategy=strategy, symbol=leg.get("symbol"), exchange="NFO",
action=leg.get("action"), event="placed",
order_id=leg.get("orderid"))
if failed_legs:
print(f"\nWARNING: {len(failed_legs)} legs failed:")
for f in failed_legs:
print(f" leg{f.get('leg')} {f.get('action')} -> {f.get('message')}")
# Poll each successful leg for fill
filled_premium = 0.0
for leg in success_legs:
try:
final = poll_until_filled(
client, order_id=str(leg["orderid"]), strategy=strategy,
interval_sec=1.0, timeout_sec=30.0,
)
px = avg_fill_price(final)
signed = px * QUANTITY if leg["action"] == "SELL" else -px * QUANTITY
filled_premium += signed
journal.write(strategy=strategy, symbol=leg.get("symbol"), exchange="NFO",
action=leg.get("action"), event="filled",
order_id=leg.get("orderid"),
average_price=px, quantity=QUANTITY)
print(f" filled {leg['symbol']} @ Rs {px}")
except Exception as exc:
print(f" {leg['symbol']} not filled within timeout: {exc}")
# ---- 6. Alert summary ---------------------------------------------------
summary = (
f"[IRON CONDOR PLACED]\n"
f"Underlying: {UNDERLYING} {EXPIRY}\n"
f"Short wing: {SHORT_OFFSET} Long wing: {LONG_OFFSET}\n"
f"Legs placed: {len(success_legs)}/4 filled premium: Rs {filled_premium:,.2f}\n"
f"Margin used: Rs {margin_needed:,.2f}"
)
notify(client, summary, via=ALERTS)
print("\n" + summary)
print(f"Journal: {workdir / 'journal.csv'}")
journal.close()
"""Place an entry order and chain SL + target based on the actual fill.
The canonical response-aware execution example. Uses
`scripts.workflows.place_with_sl_target` which internally:
1. placeorder(MARKET or LIMIT)
2. orderstatus(...) — poll until 'complete'
3. read data.average_price from the fill
4. compute SL = fill * (1 - sl_pct/100)
compute TGT = fill * (1 + target_pct/100)
5. placeorder(SL-M, action=opposite, trigger_price=SL)
6. placeorder(LIMIT, action=opposite, price=TGT)
7. journal all three events + alert phone
Output folder: openalgo_workspace/execution/<symbol>_with_sl_target/
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.trade_logger import open_journal
from scripts.workflows import place_with_sl_target
# ---- config --------------------------------------------------------------
SYMBOL = "SBIN"
EXCHANGE = "NSE"
ACTION = "BUY"
QUANTITY = 10
PRODUCT = "MIS"
SL_PCT = 1.0 # 1% below fill
TGT_PCT = 2.0 # 2% above fill (1:2 R/R)
ALERTS = ("telegram", "whatsapp")
# ---- bootstrap -----------------------------------------------------------
client = get_client()
strategy = default_strategy_tag()
workdir = Path(f"openalgo_workspace/execution/{SYMBOL.lower()}_with_sl_target")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / "journal.sqlite")
# ---- analyzer banner ----------------------------------------------------
mode = client.analyzerstatus().get("data", {}).get("mode", "unknown")
print(f"[{mode.upper()}] {ACTION} {QUANTITY} {SYMBOL} @ {EXCHANGE} "
f"product={PRODUCT} SL={SL_PCT}% target={TGT_PCT}%")
if mode != "analyze":
if input("Confirm LIVE entry with auto SL+target? [y/N] ").strip().lower() != "y":
raise SystemExit("aborted")
# ---- execute ------------------------------------------------------------
result = place_with_sl_target(
client,
strategy=strategy,
symbol=SYMBOL,
exchange=EXCHANGE,
action=ACTION,
quantity=QUANTITY,
product=PRODUCT,
price_type="MARKET",
sl_pct=SL_PCT,
target_pct=TGT_PCT,
fill_poll_interval_sec=0.5,
fill_timeout_sec=30.0,
journal=journal,
alert_via=ALERTS,
)
# ---- summary -----------------------------------------------------------
print("\n--- Workflow result ---")
print(f" Entry order: {result.entry_order_id}")
print(f" Filled qty: {result.entry_qty}")
print(f" Fill price: Rs {result.entry_avg_price}")
print(f" SL order: {result.sl_order_id} trigger Rs {result.sl_trigger}")
print(f" Target ord: {result.target_order_id} price Rs {result.target_price}")
print(f" Journal: {workdir / 'journal.sqlite'}")
journal.close()
"""Place a short ATM straddle on NIFTY with auto-SL on each leg.
Strategy: sell ATM CE + sell ATM PE for the same expiry. Profit if
NIFTY stays near the strike at expiry; loss if it moves either way
past the breakevens. The SL on each leg adapts to the *actual* fill
premium — not the intended quantity — using the response-aware
workflow `enter_options_atm_with_sl`.
Output folder: openalgo_workspace/execution/atm_straddle_nifty/
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.responses import ResponseError
from scripts.trade_logger import open_journal
from scripts.workflows import enter_options_atm_with_sl
# ---- config --------------------------------------------------------------
UNDERLYING = "NIFTY"
UNDERLYING_EXCHANGE = "NSE_INDEX"
EXPIRY = "30JUN26"
QUANTITY = 75 # 1 NIFTY lot at current SEBI lot size
PRODUCT = "NRML"
SL_PCT = 30.0 # SL placed 30% above each filled premium
ALERTS = ("telegram", "whatsapp")
# ---- bootstrap -----------------------------------------------------------
client = get_client()
strategy = f"{default_strategy_tag()}_atm_straddle"
workdir = Path("openalgo_workspace/execution/atm_straddle_nifty")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / "journal.csv")
# ---- pre-flight: confirm analyzer or live mode --------------------------
mode_info = client.analyzerstatus().get("data", {})
banner = "[ANALYZER]" if mode_info.get("analyze_mode") else "[LIVE]"
print(f"{banner} short ATM straddle on {UNDERLYING} {EXPIRY} qty {QUANTITY} per leg")
if not mode_info.get("analyze_mode"):
if input("Confirm LIVE placement? [y/N] ").strip().lower() != "y":
raise SystemExit("aborted")
# ---- Leg 1: SELL ATM CE --------------------------------------------------
print("\n--- CE LEG ---")
try:
ce_state = enter_options_atm_with_sl(
client,
underlying=UNDERLYING,
underlying_exchange=UNDERLYING_EXCHANGE,
expiry_date=EXPIRY,
option_type="CE",
offset="ATM",
quantity=QUANTITY,
product=PRODUCT,
strategy=strategy + "_ce",
sl_pct=SL_PCT,
alert_via=ALERTS,
journal=journal,
)
print(f"CE filled @ Rs {ce_state.entry_avg_price} SL trigger Rs {ce_state.sl_trigger}")
except ResponseError as exc:
print(f"CE LEG FAILED: {exc}")
raise
# ---- Leg 2: SELL ATM PE --------------------------------------------------
print("\n--- PE LEG ---")
try:
pe_state = enter_options_atm_with_sl(
client,
underlying=UNDERLYING,
underlying_exchange=UNDERLYING_EXCHANGE,
expiry_date=EXPIRY,
option_type="PE",
offset="ATM",
quantity=QUANTITY,
product=PRODUCT,
strategy=strategy + "_pe",
sl_pct=SL_PCT,
alert_via=ALERTS,
journal=journal,
)
print(f"PE filled @ Rs {pe_state.entry_avg_price} SL trigger Rs {pe_state.sl_trigger}")
except ResponseError as exc:
print(f"PE LEG FAILED: {exc} -- consider cancelling CE leg manually")
raise
# ---- Summary ------------------------------------------------------------
total_premium = (ce_state.entry_avg_price + pe_state.entry_avg_price) * QUANTITY
max_loss_proxy = total_premium * (SL_PCT / 100) * 2 # both SLs hit
print(f"\nStraddle entered.")
print(f" premium collected: Rs {total_premium:,.2f}")
print(f" worst-case (both SLs hit): Rs {max_loss_proxy:,.2f}")
print(f" journal: {workdir / 'journal.csv'}")
journal.close()
"""Place a single equity LIMIT order with quote-anchored pricing.
Workflow:
1. Quote the symbol to get current LTP
2. Compute a marketable LIMIT a few ticks past LTP (safer than MARKET)
3. Show a preview, ask for confirmation
4. Place via place_with_retry (handles transient rate limits)
5. Print the resulting orderid
Output folder: openalgo_workspace/execution/place_equity_<SYMBOL>/
"""
from __future__ import annotations
import sys
from pathlib import Path
# Make scripts/ importable when running this file directly
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.orders import OrderPreview, confirm_interactive, place_with_retry
from scripts.responses import extract_ltp, extract_orderid
# ---- config --------------------------------------------------------------
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
ACTION = "BUY"
QUANTITY = 1
PRODUCT = "MIS" # MIS (intraday) | CNC (delivery)
SLIPPAGE_PCT = 0.15 # marketable-limit cushion past LTP
# ---- bootstrap -----------------------------------------------------------
client = get_client()
strategy = default_strategy_tag()
# ---- 1. Quote ------------------------------------------------------------
quote_resp = client.quotes(symbol=SYMBOL, exchange=EXCHANGE)
ltp = extract_ltp(quote_resp)
print(f"LTP {SYMBOL}@{EXCHANGE} = Rs {ltp}")
# ---- 2. Compute limit price ---------------------------------------------
if ACTION == "BUY":
raw_price = ltp * (1 + SLIPPAGE_PCT / 100)
else:
raw_price = ltp * (1 - SLIPPAGE_PCT / 100)
limit_price = round(raw_price * 20) / 20 # NSE tick = 0.05
# ---- 3. Preview + confirm -----------------------------------------------
preview = OrderPreview(
strategy=strategy,
symbol=SYMBOL,
exchange=EXCHANGE,
action=ACTION,
quantity=QUANTITY,
price_type="LIMIT",
product=PRODUCT,
price=limit_price,
notional=limit_price * QUANTITY,
note=f"LTP {ltp}; slippage cushion {SLIPPAGE_PCT}%",
)
if not confirm_interactive(preview):
raise SystemExit("aborted by user")
# ---- 4. Place ------------------------------------------------------------
response = place_with_retry(
client,
strategy=strategy,
symbol=SYMBOL,
exchange=EXCHANGE,
action=ACTION,
price_type="LIMIT",
product=PRODUCT,
quantity=QUANTITY,
price=limit_price,
)
# ---- 5. Read the response -----------------------------------------------
try:
order_id = extract_orderid(response)
print(f"\nORDER PLACED id={order_id} {ACTION} {QUANTITY} {SYMBOL} @ Rs {limit_price}")
except Exception as exc:
print(f"\nORDER FAILED: {exc}\nRaw response: {response}")
raise
"""Live Supertrend strategy on a single symbol.
Strategy:
- 5-minute bars
- Supertrend(10, 3.0) line + direction
- BUY when direction flips +1, SELL when it flips -1
- Position-aware: uses placesmartorder so the SDK reconciles current state
Workflow each tick (15 s sleep):
1. history(...) -> last 7 days of 5m bars
2. supertrend(...) -> latest direction
3. extract signal on the last fully closed bar (iloc[-2])
4. if signal flips, placesmartorder + alert
Output folder: openalgo_workspace/execution/supertrend_<SYMBOL>/
"""
from __future__ import annotations
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.ta_helpers import supertrend
from scripts.trade_logger import open_journal
# ---- config --------------------------------------------------------------
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
PRODUCT = "MIS"
INTERVAL = "5m"
QUANTITY = 1
ATR_PERIOD = 10
ATR_MULT = 3.0
POLL_SECONDS = 15
ALERTS = ("telegram",)
# ---- bootstrap -----------------------------------------------------------
client = get_client()
strategy = f"{default_strategy_tag()}_supertrend"
workdir = Path(f"openalgo_workspace/execution/supertrend_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / "journal.csv")
mode = client.analyzerstatus().get("data", {}).get("mode", "unknown")
print(f"[{mode.upper()}] Supertrend({ATR_PERIOD}, {ATR_MULT}) on {SYMBOL} {INTERVAL} {EXCHANGE}")
position = 0 # tracked locally; placesmartorder uses position_size to reconcile
# ---- main loop -----------------------------------------------------------
try:
while True:
try:
end = datetime.now().strftime("%Y-%m-%d")
start = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
df = client.history(symbol=SYMBOL, exchange=EXCHANGE,
interval=INTERVAL,
start_date=start, end_date=end)
if df is None or len(df) < ATR_PERIOD + 2:
print("not enough bars; sleeping")
time.sleep(POLL_SECONDS)
continue
# Normalize timestamp index
if "timestamp" in df.columns:
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
else:
df.index = pd.to_datetime(df.index)
if df.index.tz is not None:
df.index = df.index.tz_convert(None)
df = df.sort_index()
# Compute supertrend
st_line, st_dir = supertrend(df["high"], df["low"], df["close"],
period=ATR_PERIOD, multiplier=ATR_MULT)
# Signal on last fully-closed bar
cur_dir = int(st_dir.iloc[-2])
prev_dir = int(st_dir.iloc[-3])
ltp = float(df["close"].iloc[-1])
print(f"{df.index[-2].strftime('%H:%M')} close={ltp} "
f"st_line={st_line.iloc[-2]:.2f} dir={cur_dir:+d} pos={position}")
# Detect flip and act
if cur_dir > 0 and prev_dir <= 0 and position <= 0:
position = QUANTITY
resp = client.placesmartorder(
strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action="BUY", price_type="MARKET", product=PRODUCT,
quantity=QUANTITY, position_size=position,
)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action="BUY", event="signal_flip_up",
price=ltp, quantity=QUANTITY,
order_id=str(resp.get("orderid", "")))
notify(client, f"SUPERTREND BUY {SYMBOL} @ Rs {ltp} qty {QUANTITY}", via=ALERTS)
elif cur_dir < 0 and prev_dir >= 0 and position >= 0:
position = -QUANTITY
resp = client.placesmartorder(
strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action="SELL", price_type="MARKET", product=PRODUCT,
quantity=QUANTITY, position_size=position,
)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action="SELL", event="signal_flip_down",
price=ltp, quantity=QUANTITY,
order_id=str(resp.get("orderid", "")))
notify(client, f"SUPERTREND SELL {SYMBOL} @ Rs {ltp} qty {QUANTITY}", via=ALERTS)
except Exception as exc:
print(f"[loop] {exc!r}")
notify(client, f"Supertrend error: {exc}", via=ALERTS)
time.sleep(POLL_SECONDS)
except KeyboardInterrupt:
print("\nstopped by user")
journal.close()
"""20-day breakout scanner across a universe.
For each symbol:
1. Fetch daily history (60 days lookback)
2. Compute 20-day rolling max of close (excluding today)
3. Flag if current LTP > rolling max
4. Compute breakout strength (% above resistance)
5. Add volume confirmation (today's vol > 1.5x 20d avg)
Output folder: openalgo_workspace/scanners/breakout/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
from typing import Any
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import fmt_scanner_results, notify
from scripts.openalgo_client import get_client
from scripts.scanner import Scanner
UNIVERSE = [
"RELIANCE", "TCS", "INFY", "HDFCBANK", "ICICIBANK", "SBIN", "BAJFINANCE",
"BHARTIARTL", "ITC", "HINDUNILVR", "LT", "AXISBANK", "ASIANPAINT", "MARUTI",
"TITAN", "ULTRACEMCO", "NESTLEIND", "WIPRO", "TECHM", "ADANIENT", "ADANIPORTS",
"JSWSTEEL", "TATASTEEL", "HINDALCO", "COALINDIA", "ONGC", "NTPC", "POWERGRID",
]
LOOKBACK_DAYS = 60
BREAKOUT_WINDOW = 20
VOL_MULT = 1.5
client = get_client()
def enrich_breakout(symbol: str, exchange: str, df: pd.DataFrame) -> dict[str, Any]:
"""For one symbol's daily OHLCV history, compute breakout flags."""
if len(df) < BREAKOUT_WINDOW + 1:
return {"symbol": symbol, "exchange": exchange, "skip": "insufficient_history"}
close = df["close"]
high = df["high"]
volume = df["volume"]
# Resistance = max close over previous N bars (excluding today)
resistance = high.iloc[:-1].rolling(BREAKOUT_WINDOW).max().iloc[-1]
today_high = high.iloc[-1]
today_close = close.iloc[-1]
avg_vol_20 = volume.iloc[:-1].rolling(BREAKOUT_WINDOW).mean().iloc[-1]
today_vol = volume.iloc[-1]
breakout = today_close > resistance and today_high > resistance
vol_confirm = avg_vol_20 > 0 and today_vol >= avg_vol_20 * VOL_MULT
return {
"symbol": symbol,
"exchange": exchange,
"today_close": round(float(today_close), 2),
"resistance": round(float(resistance), 2),
"breakout_pct": round(float((today_close / resistance - 1) * 100), 2),
"today_vol": int(today_vol),
"avg_vol_20": int(avg_vol_20) if pd.notna(avg_vol_20) else 0,
"vol_x": round(float(today_vol / avg_vol_20), 2) if avg_vol_20 else 0.0,
"breakout": bool(breakout),
"vol_confirm": bool(vol_confirm),
}
# ---- Run scan -----------------------------------------------------------
print(f"Scanning {len(UNIVERSE)} symbols for {BREAKOUT_WINDOW}-day breakout + volume confirmation")
df = (
Scanner(client)
.add_many(UNIVERSE, exchange="NSE")
.history_scan(
interval="D",
lookback_days=LOOKBACK_DAYS,
enrich=enrich_breakout,
max_workers=6,
)
)
# Filter to confirmed breakouts
hits = df[(df.get("breakout", False)) & (df.get("vol_confirm", False))].copy()
hits = hits.sort_values("breakout_pct", ascending=False).reset_index(drop=True)
print(f"\n{len(hits)} confirmed breakouts:")
if not hits.empty:
print(hits[["symbol", "today_close", "resistance", "breakout_pct", "vol_x"]].to_string(index=False))
# ---- Persist + alert ----------------------------------------------------
workdir = Path("openalgo_workspace/scanners/breakout")
workdir.mkdir(parents=True, exist_ok=True)
today = date.today().isoformat()
df.to_csv(workdir / f"all_{today}.csv", index=False)
hits.to_csv(workdir / f"hits_{today}.csv", index=False)
if not hits.empty:
summary = fmt_scanner_results(
f"{BREAKOUT_WINDOW}-Day Breakout + Volume Confirm",
hits.head(10).to_dict("records"),
fields=["symbol", "today_close", "resistance", "breakout_pct", "vol_x"],
max_rows=10,
)
notify(client, summary, via=("telegram",))
print("\n" + summary)
print(f"\nSaved to {workdir}")
"""Top gainers and top losers in NIFTY 50 via `multiquotes`.
Single REST call returns LTP + prev_close for all 50 constituents.
Sorted by % change. Writes CSVs and optionally alerts the top 5.
Output folder: openalgo_workspace/scanners/nifty50_gainers_losers/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import fmt_scanner_results, notify
from scripts.openalgo_client import get_client
from scripts.scanner import Scanner, gainers, losers
NIFTY50 = [
"ADANIENT", "ADANIPORTS", "APOLLOHOSP", "ASIANPAINT", "AXISBANK", "BAJAJ-AUTO",
"BAJFINANCE", "BAJAJFINSV", "BEL", "BHARTIARTL", "CIPLA", "COALINDIA", "DRREDDY",
"EICHERMOT", "ETERNAL", "GRASIM", "HCLTECH", "HDFCBANK", "HDFCLIFE", "HEROMOTOCO",
"HINDALCO", "HINDUNILVR", "ICICIBANK", "INDUSINDBK", "INFY", "ITC", "JIOFIN",
"JSWSTEEL", "KOTAKBANK", "LT", "M&M", "MARUTI", "NESTLEIND", "NTPC", "ONGC",
"POWERGRID", "RELIANCE", "SBILIFE", "SBIN", "SHRIRAMFIN", "SUNPHARMA", "TATACONSUM",
"TATAMOTORS", "TATASTEEL", "TCS", "TECHM", "TITAN", "TRENT", "ULTRACEMCO", "WIPRO",
]
ALERT_TOP = 5
ALERTS = ("telegram",)
client = get_client()
# ---- Gainers ------------------------------------------------------------
print("Running scan: gainers >= 1%")
gainers_df = (
Scanner(client)
.add_many(NIFTY50, exchange="NSE")
.with_filter(gainers(threshold_pct=1.0))
.quote_scan()
)
gainers_df = gainers_df.head(15).reset_index(drop=True)
print(gainers_df[["symbol", "ltp", "prev_close", "pct_change", "volume"]])
# ---- Losers -------------------------------------------------------------
print("\nRunning scan: losers <= -1%")
losers_df = (
Scanner(client)
.add_many(NIFTY50, exchange="NSE")
.with_filter(losers(threshold_pct=1.0))
.quote_scan()
)
losers_df = losers_df.head(15).reset_index(drop=True)
print(losers_df[["symbol", "ltp", "prev_close", "pct_change", "volume"]])
# ---- Persist + alert ----------------------------------------------------
workdir = Path("openalgo_workspace/scanners/nifty50_gainers_losers")
workdir.mkdir(parents=True, exist_ok=True)
today = date.today().isoformat()
gainers_df.to_csv(workdir / f"gainers_{today}.csv", index=False)
losers_df.to_csv(workdir / f"losers_{today}.csv", index=False)
summary_top_gainers = fmt_scanner_results(
f"NIFTY 50 Top Gainers ({today})",
gainers_df.head(ALERT_TOP).to_dict("records"),
fields=["symbol", "ltp", "pct_change"],
max_rows=ALERT_TOP,
)
summary_top_losers = fmt_scanner_results(
f"NIFTY 50 Top Losers ({today})",
losers_df.head(ALERT_TOP).to_dict("records"),
fields=["symbol", "ltp", "pct_change"],
max_rows=ALERT_TOP,
)
notify(client, summary_top_gainers + "\n\n" + summary_top_losers, via=ALERTS)
print(f"\nSaved to {workdir}")
"""Pre-open gap scanner — flags symbols with significant gap up/down at open.
Run this within the first 5 minutes of the session. Compares today's
open to yesterday's close.
Workflow:
1. multiquotes -> ltp, open, prev_close for the universe
2. compute gap = (open / prev_close - 1) * 100
3. flag |gap| >= threshold
4. sort and alert
Output folder: openalgo_workspace/scanners/pre_open_gap/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import fmt_scanner_results, notify
from scripts.openalgo_client import get_client
from scripts.scanner import Scanner, gap_up, gap_down
UNIVERSE = [
"RELIANCE", "TCS", "INFY", "HDFCBANK", "ICICIBANK", "SBIN", "BAJFINANCE",
"BHARTIARTL", "ITC", "HINDUNILVR", "LT", "AXISBANK", "ASIANPAINT", "MARUTI",
"TITAN", "ULTRACEMCO", "NESTLEIND", "WIPRO", "TECHM", "ADANIENT", "ADANIPORTS",
"JSWSTEEL", "TATASTEEL", "HINDALCO", "COALINDIA", "ONGC", "NTPC", "POWERGRID",
"TATAMOTORS", "M&M", "EICHERMOT", "HEROMOTOCO", "BAJAJ-AUTO",
]
GAP_THRESHOLD_PCT = 1.5
client = get_client()
# ---- Gap up scan --------------------------------------------------------
up = (
Scanner(client)
.add_many(UNIVERSE, exchange="NSE")
.with_filter(gap_up(min_pct=GAP_THRESHOLD_PCT))
.quote_scan()
)
print(f"\nGap UP >= {GAP_THRESHOLD_PCT}% ({len(up)} symbols)")
if not up.empty:
print(up[["symbol", "open", "prev_close", "gap_pct", "ltp"]].to_string(index=False))
# ---- Gap down scan ------------------------------------------------------
down = (
Scanner(client)
.add_many(UNIVERSE, exchange="NSE")
.with_filter(gap_down(min_pct=GAP_THRESHOLD_PCT))
.quote_scan()
)
print(f"\nGap DOWN >= {GAP_THRESHOLD_PCT}% ({len(down)} symbols)")
if not down.empty:
print(down[["symbol", "open", "prev_close", "gap_pct", "ltp"]].to_string(index=False))
# ---- Persist + alert ----------------------------------------------------
workdir = Path("openalgo_workspace/scanners/pre_open_gap")
workdir.mkdir(parents=True, exist_ok=True)
today = date.today().isoformat()
up.to_csv(workdir / f"gap_up_{today}.csv", index=False)
down.to_csv(workdir / f"gap_down_{today}.csv", index=False)
if not up.empty or not down.empty:
msg = ""
if not up.empty:
msg += fmt_scanner_results(
f"Gap UP >= {GAP_THRESHOLD_PCT}%",
up.head(10).to_dict("records"),
fields=["symbol", "open", "prev_close", "gap_pct"], max_rows=10,
)
if not down.empty:
if msg:
msg += "\n\n"
msg += fmt_scanner_results(
f"Gap DOWN >= {GAP_THRESHOLD_PCT}%",
down.head(10).to_dict("records"),
fields=["symbol", "open", "prev_close", "gap_pct"], max_rows=10,
)
notify(client, msg, via=("telegram",))
print(f"\nSaved to {workdir}")
"""RSI oversold scanner — finds stocks with RSI(14) <= 30.
Daily timeframe. Useful for mean-reversion setups and dip-buying.
Output folder: openalgo_workspace/scanners/rsi_oversold/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
from typing import Any
import pandas as pd
import talib as tl
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import fmt_scanner_results, notify
from scripts.openalgo_client import get_client
from scripts.scanner import Scanner
UNIVERSE = [
"RELIANCE", "TCS", "INFY", "HDFCBANK", "ICICIBANK", "SBIN", "BAJFINANCE",
"BHARTIARTL", "ITC", "HINDUNILVR", "LT", "AXISBANK", "ASIANPAINT", "MARUTI",
"TITAN", "ULTRACEMCO", "NESTLEIND", "WIPRO", "TECHM", "ADANIENT", "ADANIPORTS",
"JSWSTEEL", "TATASTEEL", "HINDALCO", "COALINDIA", "ONGC", "NTPC", "POWERGRID",
"TATAMOTORS", "M&M", "EICHERMOT", "HEROMOTOCO", "BAJAJ-AUTO",
]
RSI_PERIOD = 14
OVERSOLD_THRESHOLD = 30
BEAR_TREND_FILTER = True # only flag if 50DMA is below 200DMA (downtrending mean revert)
client = get_client()
def enrich_rsi(symbol: str, exchange: str, df: pd.DataFrame) -> dict[str, Any]:
if len(df) < 220:
return {"symbol": symbol, "exchange": exchange, "skip": "need_220_bars"}
close = df["close"]
rsi = pd.Series(tl.RSI(close.values, timeperiod=RSI_PERIOD), index=close.index)
sma50 = pd.Series(tl.SMA(close.values, timeperiod=50), index=close.index)
sma200 = pd.Series(tl.SMA(close.values, timeperiod=200), index=close.index)
return {
"symbol": symbol,
"ltp": round(float(close.iloc[-1]), 2),
"rsi": round(float(rsi.iloc[-1]), 2),
"rsi_5d_min": round(float(rsi.iloc[-5:].min()), 2),
"sma50": round(float(sma50.iloc[-1]), 2),
"sma200": round(float(sma200.iloc[-1]), 2),
"trend": "down" if sma50.iloc[-1] < sma200.iloc[-1] else "up",
}
# ---- Scan ---------------------------------------------------------------
print(f"Scanning {len(UNIVERSE)} symbols for RSI({RSI_PERIOD}) <= {OVERSOLD_THRESHOLD}")
df = (
Scanner(client)
.add_many(UNIVERSE, exchange="NSE")
.history_scan(interval="D", lookback_days=400, enrich=enrich_rsi, max_workers=6)
)
df = df[df.get("skip").isna() if "skip" in df.columns else True]
oversold = df[df["rsi"] <= OVERSOLD_THRESHOLD].copy()
if BEAR_TREND_FILTER:
oversold = oversold[oversold["trend"] == "down"]
oversold = oversold.sort_values("rsi").reset_index(drop=True)
print(f"\n{len(oversold)} oversold matches:")
if not oversold.empty:
print(oversold[["symbol", "ltp", "rsi", "rsi_5d_min", "sma50", "sma200", "trend"]].to_string(index=False))
# ---- Persist + alert ----------------------------------------------------
workdir = Path("openalgo_workspace/scanners/rsi_oversold")
workdir.mkdir(parents=True, exist_ok=True)
today = date.today().isoformat()
df.to_csv(workdir / f"all_{today}.csv", index=False)
oversold.to_csv(workdir / f"oversold_{today}.csv", index=False)
if not oversold.empty:
notify(
client,
fmt_scanner_results(
f"RSI({RSI_PERIOD}) <= {OVERSOLD_THRESHOLD}",
oversold.head(10).to_dict("records"),
fields=["symbol", "ltp", "rsi"], max_rows=10,
),
via=("telegram",),
)
print(f"\nSaved to {workdir}")
"""Volume-surge scanner — symbols trading at > N x their 20-day average.
For each symbol:
1. Pull 30 days of daily history
2. Compute 20-day average volume (excluding today)
3. Flag if today's volume >= average * threshold
4. Optional: filter to symbols making new 20d high
Output folder: openalgo_workspace/scanners/volume_surge/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
from typing import Any
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import fmt_scanner_results, notify
from scripts.openalgo_client import get_client
from scripts.scanner import Scanner
UNIVERSE = [
"RELIANCE", "TCS", "INFY", "HDFCBANK", "ICICIBANK", "SBIN", "BAJFINANCE",
"BHARTIARTL", "ITC", "HINDUNILVR", "LT", "AXISBANK", "ASIANPAINT", "MARUTI",
"TITAN", "ULTRACEMCO", "NESTLEIND", "WIPRO", "TECHM", "ADANIENT", "ADANIPORTS",
"JSWSTEEL", "TATASTEEL", "HINDALCO", "COALINDIA", "ONGC", "NTPC", "POWERGRID",
]
VOLUME_MULTIPLIER = 3.0
AVG_PERIOD = 20
REQUIRE_NEW_HIGH = False
client = get_client()
def enrich_volume(symbol: str, exchange: str, df: pd.DataFrame) -> dict[str, Any]:
if len(df) < AVG_PERIOD + 1:
return {"symbol": symbol, "exchange": exchange, "skip": "need_history"}
avg = df["volume"].iloc[-AVG_PERIOD - 1:-1].mean()
today_vol = df["volume"].iloc[-1]
today_close = df["close"].iloc[-1]
new_high = today_close >= df["high"].iloc[-AVG_PERIOD - 1:-1].max()
return {
"symbol": symbol,
"ltp": round(float(today_close), 2),
"today_vol": int(today_vol),
"avg_vol": int(avg) if pd.notna(avg) else 0,
"vol_x": round(float(today_vol / avg), 2) if avg else 0.0,
"pct_change": round(float((today_close / df["close"].iloc[-2] - 1) * 100), 2)
if len(df) > 1 else 0.0,
"new_high": bool(new_high),
}
# ---- Scan ---------------------------------------------------------------
print(f"Scanning {len(UNIVERSE)} symbols for vol >= {VOLUME_MULTIPLIER}x 20-day avg")
df = (
Scanner(client)
.add_many(UNIVERSE, exchange="NSE")
.history_scan(interval="D", lookback_days=AVG_PERIOD + 10,
enrich=enrich_volume, max_workers=6)
)
hits = df[df.get("vol_x", 0) >= VOLUME_MULTIPLIER].copy()
if REQUIRE_NEW_HIGH:
hits = hits[hits["new_high"]]
hits = hits.sort_values("vol_x", ascending=False).reset_index(drop=True)
print(f"\n{len(hits)} surge matches:")
if not hits.empty:
print(hits[["symbol", "ltp", "pct_change", "vol_x", "new_high"]].to_string(index=False))
# ---- Persist + alert ---------------------------------------------------
workdir = Path("openalgo_workspace/scanners/volume_surge")
workdir.mkdir(parents=True, exist_ok=True)
today = date.today().isoformat()
df.to_csv(workdir / f"all_{today}.csv", index=False)
hits.to_csv(workdir / f"hits_{today}.csv", index=False)
if not hits.empty:
notify(
client,
fmt_scanner_results(
f"Volume surge >= {VOLUME_MULTIPLIER}x 20d avg",
hits.head(10).to_dict("records"),
fields=["symbol", "ltp", "pct_change", "vol_x", "new_high"], max_rows=10,
),
via=("telegram",),
)
print(f"\nSaved to {workdir}")
"""Side-by-side CE / PE Open Interest histogram for an option chain.
Output folder: openalgo_workspace/visualization/oi_histogram_<UNDERLYING>_<EXPIRY>/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.option_analytics import chain_to_df, pcr, max_pain
from scripts.plotting import oi_histogram
# ---- config ------------------------------------------------------------
UNDERLYING = "NIFTY"
UNDERLYING_EXCHANGE = "NSE_INDEX"
EXPIRY = "30JUN26"
STRIKE_COUNT = 30 # 30 strikes either side of ATM
STRIKE_FILTER = 100 # filter to round strikes (NIFTY 100-point grid)
client = get_client()
# ---- Fetch chain --------------------------------------------------------
print(f"Fetching {UNDERLYING} {EXPIRY} option chain (±{STRIKE_COUNT} strikes around ATM)")
chain_resp = client.optionchain(
underlying=UNDERLYING,
exchange=UNDERLYING_EXCHANGE,
expiry_date=EXPIRY,
strike_count=STRIKE_COUNT,
)
df = chain_to_df(chain_resp)
print(f" underlying: {df.attrs['underlying']} @ {df.attrs['underlying_ltp']}")
print(f" ATM strike: {df.attrs['atm_strike']}")
print(f" strikes: {len(df['strike'].unique())}")
# Filter to round strikes only (no 50-point in-between)
if STRIKE_FILTER:
df = df[df["strike"] % STRIKE_FILTER == 0].copy()
print(f" after {STRIKE_FILTER}-point filter: {len(df['strike'].unique())} strikes")
# ---- Compute PCR + max-pain --------------------------------------------
pcr_oi = pcr(df, basis="oi")
pcr_vol = pcr(df, basis="volume")
mp = max_pain(df)
print(f"\n PCR (OI): {pcr_oi:.2f}")
print(f" PCR (volume): {pcr_vol:.2f}")
print(f" Max pain: {mp['strike']:.0f}")
# ---- Plot --------------------------------------------------------------
workdir = Path(f"openalgo_workspace/visualization/oi_histogram_{UNDERLYING.lower()}_{EXPIRY.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
out = workdir / f"oi_{date.today()}.html"
oi_histogram(
df,
title=f"{UNDERLYING} {EXPIRY} — OI Histogram "
f"PCR(OI)={pcr_oi:.2f} MaxPain={mp['strike']:.0f} "
f"Spot={df.attrs['underlying_ltp']}",
out=out,
)
# Also save the raw chain data
df.to_csv(workdir / f"chain_{date.today()}.csv", index=False)
print(f"\nSaved: {out}")
"""Put-Call Ratio dashboard across multiple underlyings.
For each underlying-expiry combination:
1. Fetch option chain
2. Compute PCR(OI) and PCR(volume)
3. Render a comparison bar chart
Output folder: openalgo_workspace/visualization/pcr_dashboard/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
import pandas as pd
import plotly.graph_objects as go
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.option_analytics import chain_to_df, pcr
UNDERLYINGS = [
("NIFTY", "NSE_INDEX"),
("BANKNIFTY", "NSE_INDEX"),
("FINNIFTY", "NSE_INDEX"),
("MIDCPNIFTY", "NSE_INDEX"),
]
EXPIRY = "30JUN26"
STRIKE_COUNT = 30
client = get_client()
rows = []
for underlying, exch in UNDERLYINGS:
try:
resp = client.optionchain(
underlying=underlying, exchange=exch,
expiry_date=EXPIRY, strike_count=STRIKE_COUNT,
)
df = chain_to_df(resp)
rows.append({
"underlying": underlying,
"spot": df.attrs["underlying_ltp"],
"atm": df.attrs["atm_strike"],
"pcr_oi": round(pcr(df, basis="oi"), 3),
"pcr_volume": round(pcr(df, basis="volume"), 3),
"n_strikes": len(df["strike"].unique()),
})
print(f" {underlying:<12} spot={df.attrs['underlying_ltp']} "
f"PCR(OI)={rows[-1]['pcr_oi']:.2f} "
f"PCR(vol)={rows[-1]['pcr_volume']:.2f}")
except Exception as exc:
print(f" {underlying:<12} ERROR: {exc}")
continue
dashboard = pd.DataFrame(rows)
# ---- Bar chart ---------------------------------------------------------
fig = go.Figure([
go.Bar(name="PCR (OI)", x=dashboard["underlying"], y=dashboard["pcr_oi"],
marker_color="#42a5f5"),
go.Bar(name="PCR (volume)", x=dashboard["underlying"], y=dashboard["pcr_volume"],
marker_color="#ef5350"),
])
fig.add_hline(y=1.0, line_dash="dash", line_color="white",
annotation_text="Neutral PCR = 1.0", annotation_position="top right")
fig.update_layout(
title=f"Put-Call Ratio Dashboard — {EXPIRY} expiry — {date.today()}",
template="plotly_dark", height=550, barmode="group",
xaxis_title="Underlying", yaxis_title="PCR",
)
# ---- Save -------------------------------------------------------------
workdir = Path("openalgo_workspace/visualization/pcr_dashboard")
workdir.mkdir(parents=True, exist_ok=True)
out = workdir / f"pcr_{date.today()}.html"
fig.write_html(str(out), include_plotlyjs="cdn")
dashboard.to_csv(workdir / f"data_{date.today()}.csv", index=False)
print(f"\nSaved: {out}")
fig.show()
"""Sector heatmap of NIFTY 50 — % change of each constituent on one chart.
Single `multiquotes` call + a Plotly treemap-style heatmap.
Output folder: openalgo_workspace/visualization/sector_heatmap/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
import pandas as pd
import plotly.express as px
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
NIFTY50 = [
("ADANIENT", "Energy"), ("ADANIPORTS", "Infra"), ("APOLLOHOSP", "Pharma"),
("ASIANPAINT", "Consumer"), ("AXISBANK", "Banking"), ("BAJAJ-AUTO", "Auto"),
("BAJFINANCE", "Finance"), ("BAJAJFINSV", "Finance"), ("BEL", "Defence"),
("BHARTIARTL", "Telecom"), ("CIPLA", "Pharma"), ("COALINDIA", "Energy"),
("DRREDDY", "Pharma"), ("EICHERMOT", "Auto"), ("ETERNAL", "Consumer"),
("GRASIM", "Materials"), ("HCLTECH", "IT"), ("HDFCBANK", "Banking"),
("HDFCLIFE", "Finance"), ("HEROMOTOCO", "Auto"), ("HINDALCO", "Metals"),
("HINDUNILVR", "Consumer"), ("ICICIBANK", "Banking"), ("INDUSINDBK", "Banking"),
("INFY", "IT"), ("ITC", "Consumer"), ("JIOFIN", "Finance"),
("JSWSTEEL", "Metals"), ("KOTAKBANK", "Banking"), ("LT", "Infra"),
("M&M", "Auto"), ("MARUTI", "Auto"), ("NESTLEIND", "Consumer"),
("NTPC", "Power"), ("ONGC", "Energy"), ("POWERGRID", "Power"),
("RELIANCE", "Energy"), ("SBILIFE", "Finance"), ("SBIN", "Banking"),
("SHRIRAMFIN", "Finance"), ("SUNPHARMA", "Pharma"), ("TATACONSUM", "Consumer"),
("TATAMOTORS", "Auto"), ("TATASTEEL", "Metals"), ("TCS", "IT"),
("TECHM", "IT"), ("TITAN", "Consumer"), ("TRENT", "Consumer"),
("ULTRACEMCO", "Materials"), ("WIPRO", "IT"),
]
client = get_client()
print(f"Fetching quotes for {len(NIFTY50)} symbols")
resp = client.multiquotes(symbols=[{"symbol": s, "exchange": "NSE"} for s, _ in NIFTY50])
if resp.get("status") != "success":
raise SystemExit(f"multiquotes failed: {resp}")
sector_map = dict(NIFTY50)
rows = []
for item in resp["results"]:
data = item.get("data") or {}
ltp = data.get("ltp")
prev = data.get("prev_close")
if not ltp or not prev:
continue
rows.append({
"symbol": item["symbol"],
"sector": sector_map.get(item["symbol"], "Other"),
"ltp": float(ltp),
"prev": float(prev),
"pct": (float(ltp) / float(prev) - 1) * 100,
"weight": float(ltp), # use price as size proxy; replace with market cap if available
})
df = pd.DataFrame(rows)
print(df.sort_values("pct", ascending=False).head(10))
# ---- Treemap ------------------------------------------------------------
fig = px.treemap(
df,
path=["sector", "symbol"],
values="weight",
color="pct",
color_continuous_scale="RdYlGn",
color_continuous_midpoint=0,
title=f"NIFTY 50 Heatmap — {date.today()}",
)
fig.update_layout(template="plotly_dark", height=700)
fig.update_traces(textinfo="label+value+percent parent")
# ---- Save --------------------------------------------------------------
workdir = Path("openalgo_workspace/visualization/sector_heatmap")
workdir.mkdir(parents=True, exist_ok=True)
out = workdir / f"heatmap_{date.today()}.html"
fig.write_html(str(out), include_plotlyjs="cdn")
df.to_csv(workdir / f"data_{date.today()}.csv", index=False)
print(f"\nSaved: {out}")
fig.show()
"""Year-to-date % change heatmap.
For each symbol: pull daily history from Jan 1 to today, compute YTD
return, render a Plotly treemap colored by performance.
Output folder: openalgo_workspace/visualization/ytd_heatmap/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
import pandas as pd
import plotly.express as px
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
UNIVERSE = [
("RELIANCE", "Energy"), ("TCS", "IT"), ("INFY", "IT"), ("WIPRO", "IT"),
("HCLTECH", "IT"), ("HDFCBANK", "Banking"), ("ICICIBANK", "Banking"),
("AXISBANK", "Banking"), ("KOTAKBANK", "Banking"), ("SBIN", "Banking"),
("INDUSINDBK", "Banking"), ("BAJFINANCE", "Finance"), ("BAJAJFINSV", "Finance"),
("HDFCLIFE", "Finance"), ("SBILIFE", "Finance"), ("MARUTI", "Auto"),
("M&M", "Auto"), ("TATAMOTORS", "Auto"), ("BAJAJ-AUTO", "Auto"),
("EICHERMOT", "Auto"), ("HEROMOTOCO", "Auto"), ("ITC", "Consumer"),
("HINDUNILVR", "Consumer"), ("NESTLEIND", "Consumer"), ("TITAN", "Consumer"),
("TATASTEEL", "Metals"), ("JSWSTEEL", "Metals"), ("HINDALCO", "Metals"),
("ONGC", "Energy"), ("COALINDIA", "Energy"), ("NTPC", "Power"),
("POWERGRID", "Power"), ("LT", "Infra"), ("ULTRACEMCO", "Materials"),
("ASIANPAINT", "Consumer"), ("BHARTIARTL", "Telecom"), ("APOLLOHOSP", "Pharma"),
("SUNPHARMA", "Pharma"), ("CIPLA", "Pharma"), ("DRREDDY", "Pharma"),
]
client = get_client()
today = date.today()
start = date(today.year, 1, 1)
rows = []
for symbol, sector in UNIVERSE:
try:
df = client.history(
symbol=symbol, exchange="NSE", interval="D",
start_date=start.isoformat(), end_date=today.isoformat(),
)
if df is None or len(df) < 2:
continue
first_close = float(df["close"].iloc[0])
last_close = float(df["close"].iloc[-1])
pct = (last_close / first_close - 1) * 100
rows.append({
"symbol": symbol,
"sector": sector,
"start": first_close,
"current": last_close,
"ytd_pct": pct,
"size": last_close,
})
print(f" {symbol:<10} {pct:+7.2f}%")
except Exception as exc:
print(f" {symbol:<10} skipped ({exc})")
continue
df = pd.DataFrame(rows)
# ---- Treemap -----------------------------------------------------------
fig = px.treemap(
df,
path=["sector", "symbol"],
values="size",
color="ytd_pct",
color_continuous_scale="RdYlGn",
color_continuous_midpoint=0,
title=f"YTD Returns {start.year} (through {today})",
)
fig.update_layout(template="plotly_dark", height=750)
fig.update_traces(textinfo="label+percent parent")
# ---- Save -------------------------------------------------------------
workdir = Path("openalgo_workspace/visualization/ytd_heatmap")
workdir.mkdir(parents=True, exist_ok=True)
out = workdir / f"ytd_{today}.html"
fig.write_html(str(out), include_plotlyjs="cdn")
df.to_csv(workdir / f"data_{today}.csv", index=False)
print(f"\nSaved: {out}")
fig.show()
"""EMA(10) / EMA(20) crossover backtest with realistic Indian-market costs.
Loads OHLCV either via `client.history` or direct DuckDB (Historify).
Uses TA-Lib for EMA, openalgo.ta.exrem to clean signals, vectorbt for
portfolio simulation. Benchmarks against NIFTY 50.
Output folder: openalgo_workspace/backtesting/ema_crossover_<SYMBOL>/
"""
from __future__ import annotations
import sys
from datetime import date, timedelta
from pathlib import Path
import pandas as pd
import vectorbt as vbt
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.fees import fees_pct, fixed_fees_inr
from scripts.openalgo_client import get_client, historify_duckdb_path
from scripts.ta_helpers import clean_signals, ema
# ---- config ------------------------------------------------------------
SYMBOL = "SBIN"
EXCHANGE = "NSE"
INTERVAL = "D"
LOOKBACK_YEARS = 3
INIT_CASH = 1_000_000
ALLOCATION = 0.75
FAST = 10
SLOW = 20
BENCHMARK = ("NIFTY", "NSE_INDEX")
USE_DUCKDB = bool(historify_duckdb_path()) # auto-detect
client = get_client()
# ---- Load data ---------------------------------------------------------
end = date.today()
start = end - timedelta(days=365 * LOOKBACK_YEARS)
def load(symbol: str, exchange: str) -> pd.DataFrame:
if USE_DUCKDB:
from scripts.duckdb_data import load_ohlcv
return load_ohlcv(symbol, exchange, start, end)
df = client.history(
symbol=symbol, exchange=exchange, interval=INTERVAL,
start_date=start.isoformat(), end_date=end.isoformat(),
)
if "timestamp" in df.columns:
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
else:
df.index = pd.to_datetime(df.index)
if df.index.tz is not None:
df.index = df.index.tz_convert(None)
return df.sort_index()
print(f"Loading {SYMBOL} ({EXCHANGE}) from {start} to {end} (source={'DuckDB' if USE_DUCKDB else 'REST'})")
df = load(SYMBOL, EXCHANGE)
print(f" {len(df)} bars")
close = df["close"]
# ---- Signals -----------------------------------------------------------
ema_fast = ema(close, FAST)
ema_slow = ema(close, SLOW)
buy_raw = (ema_fast > ema_slow) & (ema_fast.shift(1) <= ema_slow.shift(1))
sell_raw = (ema_fast < ema_slow) & (ema_fast.shift(1) >= ema_slow.shift(1))
entries, exits = clean_signals(buy_raw, sell_raw)
print(f" Entries: {entries.sum()} Exits: {exits.sum()}")
# ---- Backtest ----------------------------------------------------------
pf = vbt.Portfolio.from_signals(
close, entries, exits,
init_cash=INIT_CASH, size=ALLOCATION, size_type="percent",
fees=fees_pct("equity_delivery"), fixed_fees=fixed_fees_inr("equity_delivery"),
direction="longonly", min_size=1, size_granularity=1, freq="1D",
)
# ---- Benchmark ---------------------------------------------------------
df_bench = load(*BENCHMARK)
bench_close = df_bench["close"].reindex(close.index).ffill().bfill()
pf_bench = vbt.Portfolio.from_holding(
bench_close, init_cash=INIT_CASH,
fees=fees_pct("equity_delivery"), freq="1D",
)
# ---- Report ------------------------------------------------------------
print(f"\n--- {SYMBOL} EMA({FAST}/{SLOW}) Backtest ---")
print(pf.stats())
comparison = pd.DataFrame({
"Strategy": [
f"{pf.total_return() * 100:.2f}%",
f"{pf.sharpe_ratio():.2f}",
f"{pf.sortino_ratio():.2f}",
f"{pf.max_drawdown() * 100:.2f}%",
f"{pf.trades.win_rate() * 100:.1f}%",
f"{pf.trades.count()}",
f"{pf.trades.profit_factor():.2f}",
],
f"Benchmark ({BENCHMARK[0]})": [
f"{pf_bench.total_return() * 100:.2f}%",
f"{pf_bench.sharpe_ratio():.2f}",
f"{pf_bench.sortino_ratio():.2f}",
f"{pf_bench.max_drawdown() * 100:.2f}%",
"-", "-", "-",
],
}, index=["Total Return", "Sharpe Ratio", "Sortino Ratio", "Max Drawdown",
"Win Rate", "Total Trades", "Profit Factor"])
print("\n", comparison.to_string())
# ---- Plain-language summary --------------------------------------------
print(f"\nIn plain English:")
print(f"* You started with Rs {INIT_CASH:,}.")
print(f"* The strategy ended at Rs {pf.value().iloc[-1]:,.0f} "
f"(return {pf.total_return() * 100:+.2f}%).")
print(f"* Buy-and-hold {BENCHMARK[0]} would have ended at "
f"Rs {pf_bench.value().iloc[-1]:,.0f} ({pf_bench.total_return() * 100:+.2f}%).")
print(f"* Worst temporary loss was Rs {abs(pf.max_drawdown()) * INIT_CASH:,.0f} "
f"({pf.max_drawdown() * 100:.2f}%).")
print(f"* {pf.trades.count()} trades; you won {pf.trades.win_rate() * 100:.1f}% of them.")
# ---- Save --------------------------------------------------------------
workdir = Path(f"openalgo_workspace/backtesting/ema_crossover_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
pf.positions.records_readable.to_csv(workdir / "trades.csv", index=False)
fig = pf.plot(subplots=["value", "underwater", "cum_returns"], template="plotly_dark")
fig.write_html(str(workdir / "equity.html"), include_plotlyjs="cdn")
print(f"\nSaved trades + equity to {workdir}")
"""Multi-symbol screener-style backtest using direct DuckDB.
Pulls daily close for an entire universe via `load_multi` (one query,
all symbols), runs an EMA crossover on each independently, aggregates
results into a ranked DataFrame.
Use this template to evaluate a strategy across a basket and identify
which symbols it works best on.
Output folder: openalgo_workspace/backtesting/screener_<strategy>/
"""
from __future__ import annotations
import sys
from datetime import date, timedelta
from pathlib import Path
import pandas as pd
import vectorbt as vbt
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.duckdb_data import load_multi
from scripts.fees import fees_pct, fixed_fees_inr
from scripts.openalgo_client import historify_duckdb_path
from scripts.ta_helpers import clean_signals, ema
UNIVERSE = [
"RELIANCE", "TCS", "INFY", "HDFCBANK", "ICICIBANK", "SBIN", "BAJFINANCE",
"BHARTIARTL", "ITC", "HINDUNILVR", "LT", "AXISBANK", "ASIANPAINT", "MARUTI",
"TITAN", "ULTRACEMCO", "NESTLEIND", "WIPRO", "TECHM", "ADANIENT",
]
LOOKBACK_YEARS = 5
INIT_CASH = 1_000_000
ALLOCATION = 0.75
FAST = 10
SLOW = 20
if not historify_duckdb_path():
raise SystemExit(
"This screener requires HISTORIFY_DUCKDB_PATH in .env.\n"
"Set it to your local Historify duckdb file."
)
end = date.today()
start = end - timedelta(days=365 * LOOKBACK_YEARS)
print(f"Loading {len(UNIVERSE)} symbols {start} -> {end}")
close = load_multi(UNIVERSE, exchange="NSE", start=start, end=end, field="close")
print(f" wide DataFrame: {close.shape}")
rows = []
for sym in UNIVERSE:
if sym not in close.columns:
continue
s = close[sym].dropna()
if len(s) < SLOW + 10:
continue
ema_f = ema(s, FAST)
ema_s = ema(s, SLOW)
buy_raw = (ema_f > ema_s) & (ema_f.shift(1) <= ema_s.shift(1))
sell_raw = (ema_f < ema_s) & (ema_f.shift(1) >= ema_s.shift(1))
entries, exits = clean_signals(buy_raw, sell_raw)
pf = vbt.Portfolio.from_signals(
s, entries, exits,
init_cash=INIT_CASH, size=ALLOCATION, size_type="percent",
fees=fees_pct("equity_delivery"), fixed_fees=fixed_fees_inr("equity_delivery"),
direction="longonly", min_size=1, size_granularity=1, freq="1D",
)
rows.append({
"symbol": sym,
"total_pct": pf.total_return() * 100,
"sharpe": pf.sharpe_ratio(),
"max_dd": pf.max_drawdown() * 100,
"trades": pf.trades.count(),
"win_rate": pf.trades.win_rate() * 100,
"pf": pf.trades.profit_factor(),
})
results = pd.DataFrame(rows).sort_values("sharpe", ascending=False)
print("\n--- Top 5 by Sharpe ---")
print(results.head(5).to_string(index=False))
print("\n--- Bottom 5 by Sharpe ---")
print(results.tail(5).to_string(index=False))
workdir = Path("openalgo_workspace/backtesting/screener_ema_crossover")
workdir.mkdir(parents=True, exist_ok=True)
results.to_csv(workdir / f"results_{end}.csv", index=False)
print(f"\nSaved {len(results)} rows to {workdir}")
"""Supertrend(10, 3.0) trend-following backtest.
Same realistic-cost + NIFTY-benchmark recipe as the EMA crossover
example, applied to Supertrend signals on daily bars.
Output folder: openalgo_workspace/backtesting/supertrend_<SYMBOL>/
"""
from __future__ import annotations
import sys
from datetime import date, timedelta
from pathlib import Path
import pandas as pd
import vectorbt as vbt
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.fees import fees_pct, fixed_fees_inr
from scripts.openalgo_client import get_client, historify_duckdb_path
from scripts.ta_helpers import clean_signals, supertrend
# ---- config ------------------------------------------------------------
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
INTERVAL = "D"
LOOKBACK_YEARS = 3
INIT_CASH = 1_000_000
ALLOCATION = 0.75
ATR_PERIOD = 10
ATR_MULT = 3.0
BENCHMARK = ("NIFTY", "NSE_INDEX")
USE_DUCKDB = bool(historify_duckdb_path())
client = get_client()
# ---- Load --------------------------------------------------------------
end = date.today()
start = end - timedelta(days=365 * LOOKBACK_YEARS)
def load(symbol: str, exchange: str) -> pd.DataFrame:
if USE_DUCKDB:
from scripts.duckdb_data import load_ohlcv
return load_ohlcv(symbol, exchange, start, end)
df = client.history(
symbol=symbol, exchange=exchange, interval=INTERVAL,
start_date=start.isoformat(), end_date=end.isoformat(),
)
if "timestamp" in df.columns:
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
else:
df.index = pd.to_datetime(df.index)
if df.index.tz is not None:
df.index = df.index.tz_convert(None)
return df.sort_index()
print(f"Loading {SYMBOL} {EXCHANGE} from {start} to {end}")
df = load(SYMBOL, EXCHANGE)
print(f" {len(df)} bars")
# ---- Supertrend signals ------------------------------------------------
st_line, st_dir = supertrend(df["high"], df["low"], df["close"],
period=ATR_PERIOD, multiplier=ATR_MULT)
prev_dir = st_dir.shift(1)
buy_raw = (st_dir > 0) & (prev_dir <= 0)
sell_raw = (st_dir < 0) & (prev_dir >= 0)
entries, exits = clean_signals(buy_raw, sell_raw)
print(f" Entries: {entries.sum()} Exits: {exits.sum()}")
# ---- Backtest ----------------------------------------------------------
pf = vbt.Portfolio.from_signals(
df["close"], entries, exits,
init_cash=INIT_CASH, size=ALLOCATION, size_type="percent",
fees=fees_pct("equity_delivery"), fixed_fees=fixed_fees_inr("equity_delivery"),
direction="longonly", min_size=1, size_granularity=1, freq="1D",
)
bench = load(*BENCHMARK)
bench_close = bench["close"].reindex(df.index).ffill().bfill()
pf_bench = vbt.Portfolio.from_holding(
bench_close, init_cash=INIT_CASH, fees=fees_pct("equity_delivery"), freq="1D",
)
# ---- Report ------------------------------------------------------------
print(f"\n--- {SYMBOL} Supertrend({ATR_PERIOD}, {ATR_MULT}) Backtest ---")
comparison = pd.DataFrame({
"Strategy": [
f"{pf.total_return() * 100:.2f}%", f"{pf.sharpe_ratio():.2f}",
f"{pf.sortino_ratio():.2f}", f"{pf.max_drawdown() * 100:.2f}%",
f"{pf.trades.win_rate() * 100:.1f}%", f"{pf.trades.count()}",
f"{pf.trades.profit_factor():.2f}",
],
f"Benchmark ({BENCHMARK[0]})": [
f"{pf_bench.total_return() * 100:.2f}%", f"{pf_bench.sharpe_ratio():.2f}",
f"{pf_bench.sortino_ratio():.2f}", f"{pf_bench.max_drawdown() * 100:.2f}%",
"-", "-", "-",
],
}, index=["Total Return", "Sharpe", "Sortino", "Max DD",
"Win Rate", "Total Trades", "Profit Factor"])
print(comparison.to_string())
# ---- Save --------------------------------------------------------------
workdir = Path(f"openalgo_workspace/backtesting/supertrend_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
pf.positions.records_readable.to_csv(workdir / "trades.csv", index=False)
fig = pf.plot(subplots=["value", "underwater", "cum_returns"], template="plotly_dark")
fig.write_html(str(workdir / "equity.html"), include_plotlyjs="cdn")
print(f"\nSaved to {workdir}")
"""Candlestick chart with EMA + Supertrend overlays, no weekend gaps.
Output folder: openalgo_workspace/charting/candlestick_<SYMBOL>/
"""
from __future__ import annotations
import sys
from datetime import date, timedelta
from pathlib import Path
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.plotting import candlestick_no_gaps
from scripts.ta_helpers import ema, supertrend
# ---- config ------------------------------------------------------------
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
INTERVAL = "D"
LOOKBACK_DAYS = 180
client = get_client()
# ---- Fetch -------------------------------------------------------------
today = date.today()
start = today - timedelta(days=LOOKBACK_DAYS)
print(f"Fetching {SYMBOL} {INTERVAL} candles from {start} to {today}")
df = client.history(
symbol=SYMBOL, exchange=EXCHANGE,
interval=INTERVAL,
start_date=start.isoformat(),
end_date=today.isoformat(),
)
if "timestamp" in df.columns:
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
else:
df.index = pd.to_datetime(df.index)
if df.index.tz is not None:
df.index = df.index.tz_convert(None)
df = df.sort_index()
print(f" {len(df)} bars loaded")
# ---- Indicators --------------------------------------------------------
ema20 = ema(df["close"], 20)
ema50 = ema(df["close"], 50)
st_line, st_dir = supertrend(df["high"], df["low"], df["close"], period=10, multiplier=3.0)
# ---- Plot -------------------------------------------------------------
workdir = Path(f"openalgo_workspace/charting/candlestick_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
out = workdir / f"chart_{today}.html"
candlestick_no_gaps(
df,
title=f"{SYMBOL} {INTERVAL} EMA 20/50 + Supertrend(10, 3.0)",
overlays={
"EMA 20": ema20,
"EMA 50": ema50,
"Supertrend": st_line,
},
out=out,
)
df.to_csv(workdir / f"bars_{today}.csv")
print(f"\nSaved: {out}")
"""Market depth ladder — visualize the bid/ask book for one symbol.
Output folder: openalgo_workspace/charting/depth_<SYMBOL>/
"""
from __future__ import annotations
import sys
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.plotting import depth_ladder
from scripts.responses import ensure_success
# ---- config ------------------------------------------------------------
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
client = get_client()
# ---- Fetch -------------------------------------------------------------
resp = client.depth(symbol=SYMBOL, exchange=EXCHANGE)
ensure_success(resp, action="depth")
data = resp["data"]
print(f"\n{SYMBOL}@{EXCHANGE} LTP {data.get('ltp')} "
f"LTQ {data.get('ltq')} Vol {data.get('volume'):,}")
print(f"Total buy qty: {data.get('totalbuyqty'):,}")
print(f"Total sell qty: {data.get('totalsellqty'):,}")
# Print top of book
print("\n--- Top of Book ---")
print(f" BEST BID: {data['bids'][0]['price']} x {data['bids'][0]['quantity']}")
print(f" BEST ASK: {data['asks'][0]['price']} x {data['asks'][0]['quantity']}")
print(f" SPREAD: {data['asks'][0]['price'] - data['bids'][0]['price']:.2f}")
# ---- Plot --------------------------------------------------------------
workdir = Path(f"openalgo_workspace/charting/depth_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
out = workdir / f"depth_{ts}.html"
depth_ladder(
data,
title=f"{SYMBOL}@{EXCHANGE} Depth Ladder LTP {data['ltp']} ({ts})",
out=out,
)
print(f"\nSaved: {out}")
"""Max-pain chart — total option-writer pain by strike.
Max pain = strike at which option writers collectively pay the
smallest total payout if the underlying expires there. Often used
as a magnet for where price might pin on expiry day.
Output folder: openalgo_workspace/charting/max_pain_<UNDERLYING>_<EXPIRY>/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
import plotly.graph_objects as go
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.option_analytics import chain_to_df, max_pain
# ---- config ------------------------------------------------------------
UNDERLYING = "NIFTY"
UNDERLYING_EXCHANGE = "NSE_INDEX"
EXPIRY = "30JUN26"
STRIKE_COUNT = 40
STRIKE_FILTER = 100
client = get_client()
# ---- Fetch + compute ---------------------------------------------------
print(f"Fetching {UNDERLYING} {EXPIRY} chain (±{STRIKE_COUNT} strikes)")
chain_resp = client.optionchain(
underlying=UNDERLYING, exchange=UNDERLYING_EXCHANGE,
expiry_date=EXPIRY, strike_count=STRIKE_COUNT,
)
df = chain_to_df(chain_resp)
if STRIKE_FILTER:
df = df[df["strike"] % STRIKE_FILTER == 0].copy()
df.attrs.update(chain_to_df(chain_resp).attrs)
mp = max_pain(df)
spot = df.attrs["underlying_ltp"]
print(f" spot: {spot}")
print(f" max pain: {mp['strike']:.0f}")
print(f" total pain: Rs {mp['total_pain']:,.0f}")
# ---- Plot -------------------------------------------------------------
series = mp["series"]
fig = go.Figure(go.Bar(
x=series.index, y=series.values,
marker_color=["#42a5f5" if k != mp["strike"] else "#ef5350" for k in series.index],
name="Pain at strike",
))
fig.add_vline(x=spot, line_dash="dash", line_color="white",
annotation_text=f"Spot {spot}", annotation_position="top right")
fig.add_vline(x=mp["strike"], line_dash="dot", line_color="orange",
annotation_text=f"Max Pain {mp['strike']:.0f}", annotation_position="top left")
fig.update_layout(
title=f"{UNDERLYING} {EXPIRY} Max Pain Profile "
f"Spot={spot} MaxPain={mp['strike']:.0f}",
template="plotly_dark", height=550,
xaxis_title="Strike", yaxis_title="Total Option-Writer Pain (Rs)",
)
# ---- Save -------------------------------------------------------------
workdir = Path(f"openalgo_workspace/charting/max_pain_{UNDERLYING.lower()}_{EXPIRY.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
out = workdir / f"max_pain_{date.today()}.html"
fig.write_html(str(out), include_plotlyjs="cdn")
series.to_csv(workdir / f"pain_series_{date.today()}.csv", header=["pain"])
print(f"\nSaved: {out}")
fig.show()
"""Option chain OI chart — CE/PE side-by-side bars.
The "go-to" chart traders refer to when picking strikes for a
straddle / strangle. Marks the spot price and ATM strike clearly.
Output folder: openalgo_workspace/charting/option_chain_oi_<UNDERLYING>_<EXPIRY>/
"""
from __future__ import annotations
import sys
from datetime import date
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.option_analytics import chain_to_df
from scripts.plotting import oi_histogram
# ---- config ------------------------------------------------------------
UNDERLYING = "NIFTY"
UNDERLYING_EXCHANGE = "NSE_INDEX"
EXPIRY = "30JUN26"
STRIKE_COUNT = 25
STRIKE_FILTER = 100 # NIFTY 100-point grid; use 200 for BANKNIFTY
client = get_client()
# ---- Fetch + filter ----------------------------------------------------
print(f"Fetching {UNDERLYING} {EXPIRY} chain (±{STRIKE_COUNT} strikes)")
chain_resp = client.optionchain(
underlying=UNDERLYING, exchange=UNDERLYING_EXCHANGE,
expiry_date=EXPIRY, strike_count=STRIKE_COUNT,
)
df = chain_to_df(chain_resp)
if STRIKE_FILTER:
df = df[df["strike"] % STRIKE_FILTER == 0].copy()
# Preserve metadata after filter
df.attrs.update(chain_to_df(chain_resp).attrs)
print(f" underlying LTP: {df.attrs['underlying_ltp']}")
print(f" ATM strike: {df.attrs['atm_strike']}")
print(f" strikes shown: {len(df['strike'].unique())}")
# ---- Plot --------------------------------------------------------------
workdir = Path(
f"openalgo_workspace/charting/option_chain_oi_{UNDERLYING.lower()}_{EXPIRY.lower()}"
)
workdir.mkdir(parents=True, exist_ok=True)
out = workdir / f"oi_{date.today()}.html"
oi_histogram(
df,
title=f"{UNDERLYING} {EXPIRY} Option Chain OI "
f"Spot={df.attrs['underlying_ltp']} ATM={df.attrs['atm_strike']}",
out=out,
)
df.to_csv(workdir / f"chain_{date.today()}.csv", index=False)
print(f"\nSaved: {out}")
"""20-level market-depth stream — full book ticks for a single symbol.
Mode 3 with `depth_level=20`. Renders the top-5 of each side per
update; persists every tick to a parquet file for offline analysis.
Output folder: openalgo_workspace/streaming/depth_<SYMBOL>/
"""
from __future__ import annotations
import sys
import time
from datetime import datetime
from pathlib import Path
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.stream import subscribe
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
DEPTH_LEVEL = 20
PARQUET_BATCH = 100 # flush every N ticks
client = get_client(verbose=True)
instruments = [{"exchange": EXCHANGE, "symbol": SYMBOL}]
workdir = Path(f"openalgo_workspace/streaming/depth_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
out_path = workdir / f"depth_{ts}.parquet"
print(f"Persisting ticks to {out_path}")
ticks: list[dict] = []
last_print = 0.0
def on_depth(msg):
global last_print
d = msg["data"]
depth = d.get("depth", {})
buy = depth.get("buy", [])
sell = depth.get("sell", [])
row = {
"ts": d["timestamp"],
"symbol": d["symbol"],
"ltp": d["ltp"],
"best_bid": buy[0]["price"] if buy else None,
"best_ask": sell[0]["price"] if sell else None,
"bid_qty": buy[0]["quantity"] if buy else None,
"ask_qty": sell[0]["quantity"] if sell else None,
"spread": ((sell[0]["price"] - buy[0]["price"]) if buy and sell else None),
"imbalance": (
sum(b["quantity"] for b in buy) / sum(s["quantity"] for s in sell)
if buy and sell and sum(s["quantity"] for s in sell) else None
),
}
ticks.append(row)
# Print top-of-book every second
now = time.monotonic()
if now - last_print >= 1.0:
if buy and sell:
print(f"{d['symbol']} LTP {d['ltp']} "
f"BID {row['best_bid']}x{row['bid_qty']} "
f"ASK {row['best_ask']}x{row['ask_qty']} "
f"spread {row['spread']:.2f}")
last_print = now
# Persist in batches
if len(ticks) >= PARQUET_BATCH:
pd.DataFrame(ticks).to_parquet(
out_path,
engine="pyarrow",
compression="snappy",
index=False,
)
print(f" flushed {len(ticks)} ticks")
print(f"Subscribing to {SYMBOL}@{EXCHANGE} depth (level {DEPTH_LEVEL})")
with subscribe(client, instruments, mode="depth", on_data=on_depth, depth_level=DEPTH_LEVEL):
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
if ticks:
pd.DataFrame(ticks).to_parquet(out_path, engine="pyarrow", index=False)
print(f"\nfinal flush: {len(ticks)} ticks -> {out_path}")
"""Basic LTP stream with the subscribe() context manager.
Subscribes to a small list of instruments in Mode 1 (LTP) and prints
each tick. Cleanly unsubscribes on Ctrl-C.
Output folder: openalgo_workspace/streaming/ltp_basic/
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.stream import subscribe
INSTRUMENTS = [
{"exchange": "NSE_INDEX", "symbol": "NIFTY"},
{"exchange": "NSE_INDEX", "symbol": "BANKNIFTY"},
{"exchange": "NSE", "symbol": "RELIANCE"},
{"exchange": "NSE", "symbol": "SBIN"},
]
client = get_client(verbose=True)
def on_ltp(msg):
d = msg["data"]
print(f"{d['symbol']:<12} {d['exchange']:<10} LTP {d['ltp']} @ {d['timestamp']}")
print(f"Streaming LTP for {len(INSTRUMENTS)} instruments. Ctrl-C to stop.\n")
with subscribe(client, INSTRUMENTS, mode="ltp", on_data=on_ltp):
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nstopping")
"""Long-running stream with auto-reconnect.
Uses `scripts.stream.reconnect_loop` — wraps `subscribe()` in an
exponential-backoff retry loop. Survives broker WebSocket drops and
network blips that would otherwise kill a naive `client.connect()`
script.
Output folder: openalgo_workspace/streaming/reconnect/
"""
from __future__ import annotations
import sys
import time
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.openalgo_client import get_client
from scripts.stream import reconnect_loop
INSTRUMENTS = [
{"exchange": "NSE_INDEX", "symbol": "NIFTY"},
{"exchange": "NSE_INDEX", "symbol": "BANKNIFTY"},
{"exchange": "NSE", "symbol": "RELIANCE"},
{"exchange": "NSE", "symbol": "SBIN"},
{"exchange": "NSE", "symbol": "INFY"},
]
workdir = Path("openalgo_workspace/streaming/reconnect")
workdir.mkdir(parents=True, exist_ok=True)
log_path = workdir / f"ticks_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
def on_quote(msg):
d = msg["data"]
line = (f"{d['timestamp']} {d['symbol']:<12} "
f"O {d.get('open'):>9} H {d.get('high'):>9} L {d.get('low'):>9} "
f"LTP {d['ltp']:>9} V {d.get('volume')}")
with log_path.open("a") as f:
f.write(line + "\n")
# `client_factory` returns a fresh client each retry — useful if the
# broker token rotated. Here we just rebuild from .env.
def factory():
return get_client(verbose=True)
print(f"Persistent stream of {len(INSTRUMENTS)} symbols ({log_path})")
print("Ctrl-C to stop.\n")
reconnect_loop(
client_factory=factory,
instruments=INSTRUMENTS,
mode="quote",
on_data=on_quote,
max_retries=100,
backoff_sec=[1, 2, 5, 10, 30],
)
"""Stream LTP and fire Telegram alerts on price breaches.
Demonstrates the response-aware streaming pattern: subscribe -> alert
on threshold cross -> deduplicate so we don't spam.
Output folder: openalgo_workspace/streaming/alert_breakouts/
"""
from __future__ import annotations
import os
import sys
import time
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.openalgo_client import get_client
from scripts.stream import CallbackRouter, subscribe
# ---- config: which symbols, which thresholds ---------------------------
WATCHLIST = [
# (symbol, exchange, level_up, level_down)
("NIFTY", "NSE_INDEX", 26500.0, 25500.0),
("BANKNIFTY", "NSE_INDEX", 58000.0, 56000.0),
("RELIANCE", "NSE", 1400.0, 1300.0),
]
ALERT_COOLDOWN_SEC = 300 # don't re-fire for the same level inside 5 min
client = get_client()
router = CallbackRouter()
workdir = Path("openalgo_workspace/streaming/alert_breakouts")
workdir.mkdir(parents=True, exist_ok=True)
log_path = workdir / f"events_{datetime.now().strftime('%Y%m%d')}.log"
last_alert: dict[str, float] = {}
def make_handler(symbol, up, down):
def _h(tick):
d = tick["data"]
ltp = float(d["ltp"])
now = time.monotonic()
key_up = f"{symbol}_UP"
key_down = f"{symbol}_DOWN"
if ltp >= up and now - last_alert.get(key_up, 0) > ALERT_COOLDOWN_SEC:
msg = f"[ALERT] {symbol} crossed UP {up} LTP {ltp}"
notify(client, msg, via=("telegram",))
print(msg)
with log_path.open("a") as f:
f.write(f"{datetime.now().isoformat()} UP {symbol} {ltp}\n")
last_alert[key_up] = now
elif ltp <= down and now - last_alert.get(key_down, 0) > ALERT_COOLDOWN_SEC:
msg = f"[ALERT] {symbol} crossed DOWN {down} LTP {ltp}"
notify(client, msg, via=("telegram",))
print(msg)
with log_path.open("a") as f:
f.write(f"{datetime.now().isoformat()} DOWN {symbol} {ltp}\n")
last_alert[key_down] = now
return _h
for symbol, exchange, up, down in WATCHLIST:
router.register(symbol, make_handler(symbol, up, down))
instruments = [{"exchange": e, "symbol": s} for s, e, _, _ in WATCHLIST]
if not os.environ.get("ALERT_TELEGRAM_USERNAME"):
print("WARNING: ALERT_TELEGRAM_USERNAME not set in .env — alerts will not deliver")
print(f"Watching {len(WATCHLIST)} symbols. Ctrl-C to stop.")
for symbol, exchange, up, down in WATCHLIST:
print(f" {symbol:<10} UP>= {up} DOWN<= {down}")
with subscribe(client, instruments, mode="ltp", on_data=router.handle):
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print(f"\nstopping ({log_path})")
"""Bulk-cancel: cancel all open orders, alert the result.
Useful as a panic button or an end-of-day cleanup. Wraps
`cancelallorder` with a journal entry per cancelled order and a
summary alert.
Output folder: openalgo_workspace/execution_algos/cancel_all/
"""
from __future__ import annotations
import sys
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.trade_logger import open_journal
client = get_client()
strategy = default_strategy_tag()
workdir = Path("openalgo_workspace/execution_algos/cancel_all")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / f"journal_{datetime.now().strftime('%Y%m%d')}.csv")
mode = client.analyzerstatus().get("data", {}).get("mode", "unknown")
print(f"[{mode.upper()}] about to cancel ALL open orders under strategy={strategy}")
if mode != "analyze":
if input("Confirm cancel-all? [y/N] ").strip().lower() != "y":
raise SystemExit("aborted")
resp = client.cancelallorder(strategy=strategy)
status = resp.get("status")
canceled = resp.get("canceled_orders", [])
failed = resp.get("failed_cancellations", [])
for oid in canceled:
journal.write(strategy=strategy, event="cancelled", order_id=oid)
for oid in failed:
journal.write(strategy=strategy, event="cancel_failed", order_id=oid)
msg = (
f"[CANCEL ALL]\n"
f"strategy: {strategy}\n"
f"status: {status}\n"
f"cancelled: {len(canceled)}\n"
f"failed: {len(failed)}\n"
f"{resp.get('message', '')}"
)
print("\n" + msg)
notify(client, msg, via=("telegram",))
journal.close()
"""Conditional bracket — fill first, attach SL only if move > threshold.
A delayed-bracket pattern. After filling, wait until the LTP has
moved at least X% from the fill before attaching the SL. Avoids
getting wicked out by normal entry-bar noise.
Workflow:
1. placeorder MARKET -> orderstatus poll -> avg_fill_price
2. quote loop: wait until |LTP - fill| / fill >= MOVE_TRIGGER_PCT
3. compute SL relative to fill, place SL-M
4. optionally compute target relative to fill, place LIMIT
5. alert on each phase
Output folder: openalgo_workspace/execution_algos/conditional_bracket_<SYMBOL>/
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.orders import place_with_retry
from scripts.responses import (
avg_fill_price, extract_ltp, extract_orderid, poll_until_filled,
)
from scripts.trade_logger import open_journal
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
ACTION = "BUY"
QUANTITY = 5
PRODUCT = "MIS"
SL_PCT = 0.8 # SL placed 0.8% below fill
TARGET_PCT = 1.6 # target placed 1.6% above fill
MOVE_TRIGGER_PCT = 0.3 # don't attach SL until LTP has moved 0.3% from fill
MAX_WAIT_SEC = 600
POLL_INTERVAL = 5
client = get_client()
strategy = f"{default_strategy_tag()}_cond_bracket"
workdir = Path(f"openalgo_workspace/execution_algos/conditional_bracket_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / "journal.csv")
mode = client.analyzerstatus().get("data", {}).get("mode", "unknown")
print(f"[{mode.upper()}] conditional bracket {ACTION} {QUANTITY} {SYMBOL}")
print(f" fill via MARKET")
print(f" wait for |LTP - fill| / fill >= {MOVE_TRIGGER_PCT}%")
print(f" then attach SL @ -{SL_PCT}% target @ +{TARGET_PCT}%")
# ---- 1. Entry MARKET ---------------------------------------------------
resp = place_with_retry(
client, strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, price_type="MARKET", product=PRODUCT,
quantity=QUANTITY, price=0,
)
entry_id = extract_orderid(resp)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="entry_placed", order_id=entry_id,
quantity=QUANTITY)
final = poll_until_filled(
client, order_id=entry_id, strategy=strategy,
interval_sec=1.0, timeout_sec=30.0,
)
fill = avg_fill_price(final)
qty = int(final["data"].get("quantity") or QUANTITY)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="entry_filled", order_id=entry_id,
average_price=fill, quantity=qty)
print(f"\nfilled {qty} @ Rs {fill}")
# ---- 2. Wait for the threshold move -----------------------------------
print(f"\nwaiting for {MOVE_TRIGGER_PCT}% move from Rs {fill}...")
deadline = time.monotonic() + MAX_WAIT_SEC
triggered = False
while time.monotonic() < deadline:
time.sleep(POLL_INTERVAL)
ltp = extract_ltp(client.quotes(symbol=SYMBOL, exchange=EXCHANGE))
move_pct = abs(ltp - fill) / fill * 100
print(f" LTP {ltp} move {move_pct:+.3f}% from fill")
if move_pct >= MOVE_TRIGGER_PCT:
triggered = True
break
if not triggered:
msg = f"[CONDITIONAL] {SYMBOL} no move within {MAX_WAIT_SEC}s — no SL attached"
print("\n" + msg)
notify(client, msg, via=("telegram",))
journal.close()
raise SystemExit()
# ---- 3. Attach SL + target --------------------------------------------
sl_action = "SELL" if ACTION == "BUY" else "BUY"
if ACTION == "BUY":
sl_trigger = round((fill * (1 - SL_PCT / 100)) * 20) / 20
target_px = round((fill * (1 + TARGET_PCT / 100)) * 20) / 20
else:
sl_trigger = round((fill * (1 + SL_PCT / 100)) * 20) / 20
target_px = round((fill * (1 - TARGET_PCT / 100)) * 20) / 20
sl_resp = place_with_retry(
client, strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=sl_action, price_type="SL-M", product=PRODUCT,
quantity=qty, price=0, trigger_price=sl_trigger,
)
sl_id = extract_orderid(sl_resp)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=sl_action, event="sl_placed", order_id=sl_id,
trigger_price=sl_trigger)
tgt_resp = place_with_retry(
client, strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=sl_action, price_type="LIMIT", product=PRODUCT,
quantity=qty, price=target_px,
)
tgt_id = extract_orderid(tgt_resp)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=sl_action, event="target_placed", order_id=tgt_id,
price=target_px)
msg = (
f"[CONDITIONAL BRACKET ARMED]\n"
f"{ACTION} {qty} {SYMBOL} filled @ Rs {fill}\n"
f"SL-M trigger: Rs {sl_trigger} id {sl_id}\n"
f"Target: Rs {target_px} id {tgt_id}"
)
print("\n" + msg)
notify(client, msg, via=("telegram", "whatsapp"))
journal.close()
"""Iceberg slicer — show only `display_quantity` at a fixed limit price.
Workflow: place a child of `display_quantity` at the fixed limit. When
it fills, place the next child. Continue until the parent quantity is
filled or the overall timeout expires.
Use when:
- you have a strong view on a price you're willing to pay (the limit)
- visible quantity must stay small to avoid moving the market
Note: this is a "synthetic" iceberg — the broker sees a sequence of
small orders, not a real venue-native iceberg with reserve quantity.
Anonymity benefit is zero. Queue-position discipline at one price is
the value.
Output folder: openalgo_workspace/execution_algos/iceberg_<SYMBOL>/
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.execution import IcebergConfig, IcebergSlicer
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.responses import extract_ltp
SYMBOL = "HDFCBANK"
EXCHANGE = "NSE"
ACTION = "BUY"
TOTAL_QTY = 200
DISPLAY_QTY = 25
PRODUCT = "CNC"
LIMIT_PRICE = None # if None, anchor to current LTP - 0.5% as a "good price"
OVERALL_TIMEOUT_SEC = 600
client = get_client()
strategy = f"{default_strategy_tag()}_iceberg"
workdir = Path(f"openalgo_workspace/execution_algos/iceberg_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
# Anchor the limit price if not explicitly set
if LIMIT_PRICE is None:
ltp = extract_ltp(client.quotes(symbol=SYMBOL, exchange=EXCHANGE))
if ACTION == "BUY":
limit = round((ltp * 0.995) * 20) / 20
else:
limit = round((ltp * 1.005) * 20) / 20
print(f"LTP {ltp}, anchored {ACTION} limit at Rs {limit}")
else:
limit = LIMIT_PRICE
mode = client.analyzerstatus().get("data", {}).get("mode", "unknown")
print(f"[{mode.upper()}] iceberg {ACTION} {TOTAL_QTY} {SYMBOL} "
f"display {DISPLAY_QTY} @ Rs {limit}")
if mode != "analyze":
if input("Confirm LIVE iceberg? [y/N] ").strip().lower() != "y":
raise SystemExit("aborted")
# ---- Run ---------------------------------------------------------------
ice = IcebergSlicer(client, IcebergConfig(
symbol=SYMBOL,
exchange=EXCHANGE,
action=ACTION,
total_quantity=TOTAL_QTY,
display_quantity=DISPLAY_QTY,
price=limit,
product=PRODUCT,
strategy=strategy,
poll_interval_sec=0.5,
overall_timeout_sec=OVERALL_TIMEOUT_SEC,
))
result = ice.run()
# ---- Summary -----------------------------------------------------------
print("\n--- Iceberg result ---")
print(f"Filled: {result['filled_qty']} / {result['target_qty']}")
print(f"Children placed: {len(result['children'])}")
print(f"Complete: {result['complete']}")
# Persist child order ids
with (workdir / "child_orders.txt").open("w") as f:
for c in result["children"]:
f.write(c + "\n")
msg = (
f"[ICEBERG {'COMPLETE' if result['complete'] else 'PARTIAL'}]\n"
f"{ACTION} {SYMBOL} @ Rs {limit}\n"
f"Filled: {result['filled_qty']} / {result['target_qty']}\n"
f"Children: {len(result['children'])}"
)
notify(client, msg, via=("telegram",))
print(f"\nLogs: {workdir}")
"""Limit-order chaser — peg the touch, modify on move, MARKET on timeout.
The flagship execution algo. Uses `scripts.execution.LimitChaser`:
1. Read best_bid (BUY) or best_ask (SELL) via client.depth(...)
2. placeorder(LIMIT, price=touch) -> entry order id
3. loop every poll_interval_sec:
orderstatus(orderid) -> filled? terminal? continue.
depth(...) -> new touch
if touch moved 1 tick against us AND within max_chase_ticks:
modifyorder(price=new_touch)
4. on timeout: cancel OR convert to MARKET (configurable)
Output folder: openalgo_workspace/execution_algos/limit_chaser_<SYMBOL>/
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.execution import ChaserConfig, LimitChaser
from scripts.openalgo_client import default_strategy_tag, get_client
# ---- config ------------------------------------------------------------
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
ACTION = "BUY" # BUY chases the bid; SELL chases the ask
QUANTITY = 5
PRODUCT = "MIS"
TICK_SIZE = 0.05 # NSE equity default
TIMEOUT_SEC = 120
MAX_CHASE_TICKS = 8 # bail if touch moves > 0.40 from initial
ON_TIMEOUT = "market" # "cancel" or "market"
ALERTS = ("telegram",)
client = get_client()
strategy = f"{default_strategy_tag()}_chaser"
workdir = Path(f"openalgo_workspace/execution_algos/limit_chaser_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
mode = client.analyzerstatus().get("data", {}).get("mode", "unknown")
print(f"[{mode.upper()}] chaser {ACTION} {QUANTITY} {SYMBOL} "
f"tick={TICK_SIZE} timeout={TIMEOUT_SEC}s on_timeout={ON_TIMEOUT}")
# ---- Run ---------------------------------------------------------------
chaser = LimitChaser(
client,
ChaserConfig(
symbol=SYMBOL,
exchange=EXCHANGE,
action=ACTION,
quantity=QUANTITY,
product=PRODUCT,
strategy=strategy,
tick_size=TICK_SIZE,
poll_interval_sec=1.0,
timeout_sec=TIMEOUT_SEC,
max_chase_ticks=MAX_CHASE_TICKS,
on_timeout=ON_TIMEOUT,
journal_path=str(workdir / "fills.csv"),
confirm=(mode != "analyze"),
),
)
state = chaser.run()
# ---- Summary + alert ---------------------------------------------------
if state.filled:
msg = (
f"[CHASER FILLED]\n"
f"{ACTION} {state.filled_qty} {SYMBOL}\n"
f"Avg price: Rs {state.average_price}\n"
f"Initial touch: Rs {state.initial_price}\n"
f"Last touch: Rs {state.current_price}\n"
f"Order id: {state.order_id}"
)
else:
msg = (
f"[CHASER UNFILLED]\n"
f"{ACTION} {QUANTITY} {SYMBOL}\n"
f"Initial touch: Rs {state.initial_price}\n"
f"Last touch: Rs {state.current_price}\n"
f"Timed out after {TIMEOUT_SEC}s (on_timeout={ON_TIMEOUT})"
)
print("\n" + msg)
notify(client, msg, via=ALERTS)
print(f"\nJournal: {workdir / 'fills.csv'}")
"""Cancel and replace if LTP moves past a re-anchor threshold.
Different from `LimitChaser`:
- Chaser uses depth (touch) — modifies aggressively every tick move.
- This algo uses LTP — only cancels and *replaces* if LTP runs away
more than a chosen %, and then re-anchors to the new LTP for the
next attempt.
Use when you want to participate without paying the touch — willing
to wait at a sticky limit but unwilling to chase if the market runs.
Output folder: openalgo_workspace/execution_algos/price_replace_<SYMBOL>/
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.orders import cancel_with_retry, place_with_retry
from scripts.responses import (
avg_fill_price, extract_ltp, extract_orderid, is_filled, is_terminal,
)
from scripts.trade_logger import open_journal
SYMBOL = "RELIANCE"
EXCHANGE = "NSE"
ACTION = "BUY"
QUANTITY = 5
PRODUCT = "MIS"
INITIAL_OFFSET_PCT = -0.20 # try to BUY at LTP - 0.20%
REPLACE_TRIGGER_PCT = 0.30 # re-anchor when LTP runs 0.30% from our last limit
MAX_REPLACEMENTS = 5
TIMEOUT_SEC = 300
POLL_INTERVAL = 2
client = get_client()
strategy = f"{default_strategy_tag()}_replace"
workdir = Path(f"openalgo_workspace/execution_algos/price_replace_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / "journal.csv")
mode = client.analyzerstatus().get("data", {}).get("mode", "unknown")
print(f"[{mode.upper()}] {ACTION} {QUANTITY} {SYMBOL} "
f"initial offset {INITIAL_OFFSET_PCT}% replace if LTP moves {REPLACE_TRIGGER_PCT}%")
def anchor(target_price: float) -> float:
"""Round to NSE tick = 0.05."""
return round(target_price * 20) / 20
# ---- Initial place -----------------------------------------------------
ltp = extract_ltp(client.quotes(symbol=SYMBOL, exchange=EXCHANGE))
limit = anchor(ltp * (1 + INITIAL_OFFSET_PCT / 100))
print(f"LTP {ltp} initial LIMIT @ Rs {limit}")
resp = place_with_retry(
client, strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, price_type="LIMIT", product=PRODUCT,
quantity=QUANTITY, price=limit,
)
order_id = extract_orderid(resp)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="placed", order_id=order_id,
price=limit, quantity=QUANTITY)
replacements = 0
deadline = time.monotonic() + TIMEOUT_SEC
# ---- Watch / replace loop ---------------------------------------------
while time.monotonic() < deadline and replacements < MAX_REPLACEMENTS:
time.sleep(POLL_INTERVAL)
status = client.orderstatus(order_id=order_id, strategy=strategy)
if is_filled(status):
px = avg_fill_price(status)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="filled", order_id=order_id,
average_price=px)
msg = (f"[REPLACE FILLED] {ACTION} {QUANTITY} {SYMBOL} @ Rs {px}\n"
f"replacements used: {replacements}")
print("\n" + msg)
notify(client, msg, via=("telegram",))
journal.close()
raise SystemExit()
if is_terminal(status):
print("order terminal:", status["data"].get("order_status"))
break
cur_ltp = extract_ltp(client.quotes(symbol=SYMBOL, exchange=EXCHANGE))
move_pct = (cur_ltp / limit - 1) * 100
# For BUY: bad direction = LTP rising past our limit by trigger pct
if ACTION == "BUY" and move_pct >= REPLACE_TRIGGER_PCT:
print(f"LTP {cur_ltp} ran {move_pct:+.2f}% past limit {limit} -- re-anchoring")
cancel_with_retry(client, order_id=order_id, strategy=strategy)
limit = anchor(cur_ltp * (1 + INITIAL_OFFSET_PCT / 100))
resp = place_with_retry(
client, strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, price_type="LIMIT", product=PRODUCT,
quantity=QUANTITY, price=limit,
)
order_id = extract_orderid(resp)
replacements += 1
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="replaced", order_id=order_id,
price=limit)
print(f" new LIMIT @ Rs {limit} order {order_id}")
# For SELL: bad direction = LTP falling past
elif ACTION == "SELL" and move_pct <= -REPLACE_TRIGGER_PCT:
print(f"LTP {cur_ltp} ran {move_pct:+.2f}% past limit {limit} -- re-anchoring")
cancel_with_retry(client, order_id=order_id, strategy=strategy)
limit = anchor(cur_ltp * (1 + INITIAL_OFFSET_PCT / 100))
resp = place_with_retry(
client, strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, price_type="LIMIT", product=PRODUCT,
quantity=QUANTITY, price=limit,
)
order_id = extract_orderid(resp)
replacements += 1
# ---- Timed out without fill --------------------------------------------
cancel_with_retry(client, order_id=order_id, strategy=strategy)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="abandoned", order_id=order_id,
extra=f"replacements={replacements}")
msg = (f"[REPLACE ABANDONED] {ACTION} {QUANTITY} {SYMBOL}\n"
f"replacements used: {replacements}/{MAX_REPLACEMENTS}\n"
f"last limit: Rs {limit}")
print("\n" + msg)
notify(client, msg, via=("telegram",))
journal.close()
"""Place a LIMIT order; cancel if not filled within N seconds.
The simplest "auto-cancel" pattern. Useful for opportunistic entries
where you only want the trade at the specified price within a short
window — beyond that, the conditions that prompted the entry no
longer apply.
Output folder: openalgo_workspace/execution_algos/time_cancel_<SYMBOL>/
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from scripts.alerts import notify
from scripts.openalgo_client import default_strategy_tag, get_client
from scripts.orders import cancel_with_retry, place_with_retry
from scripts.responses import (
avg_fill_price, extract_orderid, is_filled, is_terminal,
)
from scripts.trade_logger import open_journal
SYMBOL = "SBIN"
EXCHANGE = "NSE"
ACTION = "BUY"
QUANTITY = 10
PRODUCT = "MIS"
LIMIT_PRICE = 750.00
HOLD_SECONDS = 60
client = get_client()
strategy = f"{default_strategy_tag()}_time_cancel"
workdir = Path(f"openalgo_workspace/execution_algos/time_cancel_{SYMBOL.lower()}")
workdir.mkdir(parents=True, exist_ok=True)
journal = open_journal(workdir / "journal.csv")
mode = client.analyzerstatus().get("data", {}).get("mode", "unknown")
print(f"[{mode.upper()}] {ACTION} {QUANTITY} {SYMBOL} @ Rs {LIMIT_PRICE} LIMIT "
f"cancel after {HOLD_SECONDS}s")
# ---- Place -------------------------------------------------------------
resp = place_with_retry(
client,
strategy=strategy,
symbol=SYMBOL,
exchange=EXCHANGE,
action=ACTION,
price_type="LIMIT",
product=PRODUCT,
quantity=QUANTITY,
price=LIMIT_PRICE,
)
order_id = extract_orderid(resp)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="placed", order_id=order_id,
price=LIMIT_PRICE, quantity=QUANTITY)
print(f"placed {order_id}")
# ---- Wait + check ------------------------------------------------------
deadline = time.monotonic() + HOLD_SECONDS
final_status = None
while time.monotonic() < deadline:
time.sleep(2)
status = client.orderstatus(order_id=order_id, strategy=strategy)
if is_filled(status):
final_status = status
break
if is_terminal(status):
final_status = status
print(f"order terminal: {status['data']['order_status']}")
break
# ---- Outcome -----------------------------------------------------------
if final_status and is_filled(final_status):
px = avg_fill_price(final_status)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="filled", order_id=order_id,
average_price=px)
msg = f"[TIME-CANCEL FILLED] {ACTION} {QUANTITY} {SYMBOL} @ Rs {px} order {order_id}"
else:
cancel_with_retry(client, order_id=order_id, strategy=strategy)
journal.write(strategy=strategy, symbol=SYMBOL, exchange=EXCHANGE,
action=ACTION, event="cancelled_timeout", order_id=order_id)
msg = (f"[TIME-CANCEL EXPIRED] {ACTION} {QUANTITY} {SYMBOL} @ Rs {LIMIT_PRICE} "
f"not filled in {HOLD_SECONDS}s — cancelled ({order_id})")
print("\n" + msg)
notify(client, msg, via=("telegram",))
journal.close()