Real-Time Data with WebSockets
Stream live ticks over a WebSocket and manage a position in real time - computing stop-loss, target and trailing stop as each price arrives.
- ·Polling (REST) vs streaming (WebSocket)
- ·When to use the API vs the feed
- ·connect() & subscribe_ltp/quote/depth
- ·Compute a live stop-loss & target
- ·Trailing stop-loss on every tick
- ·A complete live trade manager
You've built strategies, backtested them, even wired up a complete bot. But notice something about every example so far: you asked for the data. You called quotes(), you called history() - you reached out and pulled a value each time you wanted one. That's perfect for analysis and for placing orders. It's the wrong tool the moment you have a live position and need to react the instant price moves.
This final chapter adds the missing piece: data that is pushed to you, tick by tick, the moment it changes - and the live trade-management logic that rides on top of it. By the end you'll compute a stop-loss, a target and a trailing stop straight from a real-time stream.
What exactly is a WebSocket?
A normal API call is a quick conversation that ends immediately. You knock on the door, ask "what's the price of Reliance?", get one answer, and the door shuts. Want a fresh price a second later? Knock again. This request-response pattern is called polling, and it's how every chapter up to now has worked.
A WebSocket is different. You knock once, the door opens, and it stays open - a single, persistent, two-way line between your script and OpenAlgo. You tell it which symbols you care about, and from then on the exchange streams prices to you the instant a trade happens. No more knocking. The data simply arrives.
Polling = you pull data, again and again. A WebSocket = the market pushes data to you over one always-on connection. For anything that must react in real time, push beats pull every single time.
That "push" model is why a WebSocket is fast and light. Polling fifty symbols once a second is fifty separate requests every second hammering your broker connection. A single WebSocket subscription delivers all fifty over one connection, only when something actually changes.
API or feed - which should you use?
Both have their place, and a good trading system uses each for what it's best at. The rule is simple: pull with the REST API for actions and history; stream over the feed for live monitoring.
| Use the REST API when... | Use the WebSocket feed when... |
|---|---|
| You need a one-off snapshot, or a value once a minute | You need every price change the instant it happens |
| Placing, modifying or cancelling orders | Watching an open position for its exit |
| Downloading historical candles for a backtest | Running a live dashboard or a scalping screen |
| Scanning a universe every few minutes | Trailing a stop or reacting to the order book |
A live bot almost always uses both at once: it places the entry order through the REST API (Chapter 25), then monitors that position over the WebSocket feed and fires the exit order - again through the REST API - when the stream tells it to. Orders go out by pull; price comes in by push.
Open your first stream
Three method calls run every stream: connect() opens the line, subscribe_ltp() registers the symbols you want plus a callback function, and disconnect() closes it cleanly at the end. The callback is the heart of it - OpenAlgo calls your function automatically every time a new tick arrives, handing it the latest data.
# Your first live stream: open one connection and let prices come to you.
import os
import time
from openalgo import api
client = api(
api_key=os.getenv("OPENALGO_API_KEY", "your_api_key_here"),
host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
ws_url=os.getenv("OPENALGO_WS_URL", "ws://127.0.0.1:8765"),
)
instruments = [
{"exchange": "NSE", "symbol": "RELIANCE"},
{"exchange": "NSE", "symbol": "INFY"},
]
# This callback fires every time the exchange pushes a fresh tick.
def on_ltp(message):
tick = message["data"]
print(f"{tick['symbol']:10s} LTP: {tick['ltp']}")
client.connect() # open the persistent connection once
client.subscribe_ltp(instruments, on_data_received=on_ltp)
time.sleep(10) # let ticks stream in for ten seconds
client.unsubscribe_ltp(instruments)
client.disconnect() # always close the line when you are doneEach message your callback receives is a small dictionary. The fields you'll use live under message["data"] - message["data"]["ltp"] is the last traded price. That nesting matters, so keep it in mind for every callback below.
The three modes: LTP, quote and depth
Just like the snapshot calls back in Chapter 4, the stream comes in three depths of detail. You pick one by calling the matching subscribe_ method:
- LTP (
subscribe_ltp) - just the last traded price and a timestamp. The lightest, fastest stream. - Quote (
subscribe_quote) - the full snapshot: LTP plus open, high, low, close and volume. - Depth (
subscribe_depth) - the live order book, the stacked bids and asks from Chapter 4, updating in real time.
Here's quote mode, giving you enough to show price, the day's change and its range as they move:
# Quote mode streams the full snapshot - price, volume and the day's range.
import os
import time
from openalgo import api
client = api(
api_key=os.getenv("OPENALGO_API_KEY", "your_api_key_here"),
host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
ws_url=os.getenv("OPENALGO_WS_URL", "ws://127.0.0.1:8765"),
)
instruments = [{"exchange": "NSE", "symbol": "SBIN"}]
def on_quote(message):
q = message["data"]
change = q["ltp"] - q["close"] # close here is yesterday's close
print(
f"{q['symbol']} LTP {q['ltp']:.2f} "
f"({change:+.2f}) vol {q['volume']} "
f"range {q['low']:.2f}-{q['high']:.2f}"
)
client.connect()
client.subscribe_quote(instruments, on_data_received=on_quote)
time.sleep(10)
client.unsubscribe_quote(instruments)
client.disconnect()Your callback runs on a background thread while your main script keeps going - that's how prices arrive without you waiting for them. So keep the callback quick: read the tick, update a couple of variables, decide whether to exit. Never run a slow download or a long calculation inside it, or you'll fall behind the stream.
Computing a stop-loss and a target
Now the part that actually protects your money. Two numbers turn a raw position into a managed trade:
- A stop-loss - the price at which you admit the trade is wrong and exit, capping the loss to an amount you decided before entering.
- A target - the price at which you're happy to take profit and walk away.
For a long (buy) position you set them around your entry price: the stop sits below, the target above. The gap between them is your risk-reward: risk 30 points to make 60 and you have a 1:2 trade. Then you let the live stream watch price for you - the moment LTP touches either line, you're out.
# Watch a live position and exit the moment price hits the stop-loss or target.
import os
import time
from openalgo import api
client = api(
api_key=os.getenv("OPENALGO_API_KEY", "your_api_key_here"),
host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
ws_url=os.getenv("OPENALGO_WS_URL", "ws://127.0.0.1:8765"),
)
SYMBOL, EXCHANGE = "CRUDEOIL20JUL26FUT", "MCX"
entry = 6000.0 # the price you bought at (from your fill - see Chapter 25)
stop_loss = entry - 30 # cap the loss 30 points below entry
target = entry + 60 # take profit 60 points above (a 1:2 risk-reward)
position_open = True
def on_ltp(message):
global position_open
if not position_open:
return
ltp = float(message["data"]["ltp"])
if ltp <= stop_loss:
print(f"\nSTOP-LOSS hit at {ltp:.2f} - exit for a small, planned loss")
position_open = False
elif ltp >= target:
print(f"\nTARGET hit at {ltp:.2f} - exit in profit")
position_open = False
else:
print(f"holding LTP {ltp:.2f} SL {stop_loss:.2f} TGT {target:.2f}", end="\r")
client.connect()
client.subscribe_ltp([{"exchange": EXCHANGE, "symbol": SYMBOL}], on_data_received=on_ltp)
while position_open: # the feed, not a polling loop, drives the exit
time.sleep(0.2)
client.unsubscribe_ltp([{"exchange": EXCHANGE, "symbol": SYMBOL}])
client.disconnect()Read the callback closely: on every tick it asks two questions - have we fallen to the stop? and have we risen to the target? - and flips position_open to False the instant either is true. The while position_open loop in the main script does nothing but wait; the feed drives the decision, not a timer.
In a real bot, the exit would place a market SELL order through the REST API, exactly as in the order chapter - here we just print, so you can study the logic safely in analyze mode. And entry would come from your actual fill price, not a hard-coded number.
Trailing the stop (TSL)
A fixed stop-loss is good; a trailing stop-loss is smarter when a trade runs in your favour. The idea: as price climbs, you drag the stop up behind it - never letting it slip back down. The trade is given room to breathe, but every new high locks in a little more profit. If price then reverses, you exit having kept most of the gain instead of riding it all the way back to your original stop.
The mechanics are just two lines of bookkeeping per tick: remember the highest price seen (high_water), and set the stop a fixed distance under it. The stop only ever moves up.
# A trailing stop follows price up to lock in profit - it never moves back down.
import os
import time
from openalgo import api
client = api(
api_key=os.getenv("OPENALGO_API_KEY", "your_api_key_here"),
host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
ws_url=os.getenv("OPENALGO_WS_URL", "ws://127.0.0.1:8765"),
)
SYMBOL, EXCHANGE = "CRUDEOIL20JUL26FUT", "MCX"
TRAIL = 25.0 # keep the stop 25 points under the best price seen
entry = 6000.0
high_water = entry # highest price reached since entry
stop_loss = entry - TRAIL
position_open = True
def on_ltp(message):
global high_water, stop_loss, position_open
if not position_open:
return
ltp = float(message["data"]["ltp"])
if ltp > high_water: # new high -> drag the stop up behind it
high_water = ltp
stop_loss = high_water - TRAIL
if ltp <= stop_loss: # price slipped back to the trailing stop
print(f"\nTRAILING STOP hit at {ltp:.2f} (stop had locked to {stop_loss:.2f})")
position_open = False
else:
print(f"LTP {ltp:.2f} peak {high_water:.2f} stop {stop_loss:.2f}", end="\r")
client.connect()
client.subscribe_ltp([{"exchange": EXCHANGE, "symbol": SYMBOL}], on_data_received=on_ltp)
while position_open:
time.sleep(0.2)
client.unsubscribe_ltp([{"exchange": EXCHANGE, "symbol": SYMBOL}])
client.disconnect()The whole secret of a trailing stop is the one-way ratchet: stop = max(stop, new_high - trail). Because of that max, the stop can rise but can never fall. Profit, once locked, stays locked.
A complete live trade manager
Let's put it together the way you actually would in production - a small class that holds the trade's state and exposes a single on_tick method as the callback. It carries a fixed target and a trailing stop at the same time, exiting on whichever comes first. This is the pattern your real bot would wrap around a freshly filled order.
# A complete live trade manager: a fixed target plus a trailing stop, in one class.
import os
import time
from openalgo import api
client = api(
api_key=os.getenv("OPENALGO_API_KEY", "your_api_key_here"),
host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
ws_url=os.getenv("OPENALGO_WS_URL", "ws://127.0.0.1:8765"),
)
class TradeManager:
def __init__(self, entry, trail, target):
self.entry = entry
self.trail = trail
self.target = entry + target
self.high_water = entry
self.stop = entry - trail
self.open = True
def on_tick(self, message):
if not self.open:
return
ltp = float(message["data"]["ltp"])
if ltp > self.high_water: # trail the stop upward only
self.high_water = ltp
self.stop = max(self.stop, ltp - self.trail)
if ltp >= self.target:
self.close(ltp, "TARGET")
elif ltp <= self.stop:
self.close(ltp, "TRAILING STOP")
else:
print(f"LTP {ltp:.2f} stop {self.stop:.2f} tgt {self.target:.2f}", end="\r")
def close(self, ltp, reason):
print(f"\n{reason} hit at {ltp:.2f} - closing the position")
self.open = False
mgr = TradeManager(entry=6000.0, trail=25.0, target=60.0)
inst = [{"exchange": "MCX", "symbol": "CRUDEOIL20JUL26FUT"}]
client.connect()
client.subscribe_ltp(inst, on_data_received=mgr.on_tick)
while mgr.open:
time.sleep(0.2)
client.unsubscribe_ltp(inst)
client.disconnect()Wrapping the logic in a class keeps every moving part - entry, high-water mark, stop, target - tidily together, and makes it trivial to run several managers at once, one per open position, all fed by the same connection.
Reconnects and heartbeats
A live connection has to survive a flaky network. OpenAlgo sends a ping roughly every 30 seconds; the client answers with a pong automatically to prove it's still alive. If the line does drop, the client reconnects and re-subscribes to your symbols for you. The one rule for your code: keep your state (entry, stop, target) in variables that outlive a single message - as we've done throughout - so a brief blip never loses track of your trade.
On a local setup the feed lives at ws://127.0.0.1:8765. Behind a domain in production it's served over TLS at wss://yourdomain.com/ws. Set OPENALGO_WS_URL in your .env and the same code works in both places.
Try it yourself
- Point the LTP stream at an MCX symbol like
CRUDEOIL20JUL26FUTand run it in the evening - the commodity session keeps ticking long after stocks close. - Change the trailing example's
TRAILfrom 25 to 10 points and watch how a tighter stop exits sooner but locks in less. - Add a time stop to the trade manager: store the start time and close the position if it hasn't hit target or stop within, say, 15 minutes.
- Swap
subscribe_ltpforsubscribe_depthand print the best bid and ask frommessage["data"]["depth"]as they move.
Recap
- A WebSocket is one persistent, two-way connection: the market pushes prices to you, instead of you pulling them with repeated API calls.
- Pull with the REST API for snapshots, history and placing orders; stream over the feed for live position monitoring. Real bots use both together.
- Every stream is
connect()->subscribe_ltp/quote/depth(instruments, on_data_received=...)->disconnect(), and your callback receives each tick with the price undermessage["data"]["ltp"]. - A stop-loss caps your loss and a target books profit; the live feed exits the instant price touches either.
- A trailing stop ratchets up behind a rising price -
stop = max(stop, high - trail)- locking in gains while never giving them back. - Keep callbacks fast and your trade state in long-lived variables, so reconnects and heartbeats never trip you up.
That's the loop a real trading desk runs on: pull to act, stream to watch, and let well-defined stops and targets do the deciding. You now have every piece - from your first quotes() call to a live, self-managing position. Go build something, and trade it in analyze mode first.