Skip to content

noren

Ticks from brokers on the Noren platform: Flattrade and Shoonya.

Noren names an instrument as EXCHANGE|TOKEN in its own exchange vocabulary - NFO for NSE derivatives, CDS for NSE currency, MCX - and stores the bare token in unified.broker_mappings, where it collides across segments, so the exchange is what narrows the search.

What Noren's values mean, as measured against the stored ticks of 2026-09-12 and 2026-09-13:

  • c is the previous session's close while a session runs and the session's own close after it. In Saturday's mock session Flattrade's RELIANCE read c 1257.5, Friday's close, against a mock last price of 1270.1; after the exchanges reset on Sunday, Shoonya's read 1257.5 for both. So, as for Dhan, it is used only before the session's close.
  • On MCX, open interest is in lots - CRUDEOIL SEP 17552 and GOLD OCT 9618 at Shoonya, both the same as Zerodha - and the other MCX quantities are taken to be lots too, which a live session checks.
  • ft is a true instant: Flattrade's 21:02:12 for RELIANCE's last mock trade is the second Wisdom Capital's corrected last trade time shows. The last trade time is parsed in the market feed.

Classes:

Name Description
NorenTickNormalizer

Normalizes ticks from a Noren platform broker. A subclass sets BROKER_NAME.

NorenTickNormalizer

NorenTickNormalizer()

Bases: TickNormalizer

Normalizes ticks from a Noren platform broker. A subclass sets BROKER_NAME.

Build the lookup from each Noren exchange name to the canonical segments it can hold.

The table is worked out once here from EXCHANGES, so feed_key only has to look a tick's exchange name up rather than recompute its segments on every call.

Example programs

examples/stock_brokers/instruments/ticks/noren/NorenTickNormalizer/example_1_every_noren_exchange.py
"""Places a token from every exchange the Noren platform names on the canonical segments it can be in.

Flattrade and Shoonya both run on the Noren platform, and `NorenTickNormalizer` holds everything they share: a token is `EXCHANGE|TOKEN`, and the exchange name decides where the instrument is searched for. NSE and BSE hold shares and indices together; NFO, CDS and NCO are NSE's equity, currency and commodity derivatives; BFO and BCD are BSE's; MCX is MCX; and NCDEX is spelt either NCX or NCDEX. The exchange is read without regard to case and surrounding spaces are ignored.

The shared class sets no broker name, because each broker's subclass does; it is used here directly because placing a token does not depend on which Noren broker sent it. The normalizer only works on the text of each token, so no data store, network or stand-in is needed. Each line shows the token, the broker token the mappings are searched with, and how many canonical segments are searched, with the first of them. A token the normalizer cannot place prints `None`.

Run it from the project root:

    python examples/stock_brokers/instruments/ticks/noren/NorenTickNormalizer/example_1_every_noren_exchange.py
"""

from stock_brokers.instruments.ticks.noren import (
    NorenTickNormalizer,
)


class EveryNorenExchangeExample:
    """Places one token from each Noren exchange.

    Attributes:
        normalizer (NorenTickNormalizer): The normalizer being shown.
        tokens (list): The tokens to place, spelled as a Noren feed spells them.
    """

    def __init__(self):
        """Builds the normalizer and the tokens to place.

        Returns:
            None: This method returns nothing.
        """
        self.normalizer = NorenTickNormalizer()
        self.tokens = [
            'NSE|2885',
            'BSE|500325',
            'NFO|35003',
            'CDS|1573',
            'NCO|2003',
            'BFO|1135101',
            'BCD|8001',
            'MCX|440001',
            'NCX|12345',
            'NCDEX|12345',
            ' mcx | 440001 ',
            'LME|1',
            'NSE|',
        ]

    def describe(self, feed_key):
        """Describes a feed key in one line.

        Args:
            feed_key (FeedKey | None): The key the normalizer returned.

        Returns:
            str: The description.
        """
        if feed_key is None:
            return 'None'
        segment_count = len(feed_key.segments)
        first_segment = feed_key.segments[0]
        return f'broker_token={feed_key.broker_token!r} segments={segment_count} starting with {first_segment}'

    def run(self):
        """Prints where each token belongs.

        Returns:
            None: This method returns nothing.
        """
        print(f'Broker name on the shared class: {NorenTickNormalizer.BROKER_NAME}')
        for token in self.tokens:
            feed_key = self.normalizer.feed_key(token)
            print(f'{token!r} -> {self.describe(feed_key)}')


if __name__ == '__main__':
    EveryNorenExchangeExample().run()

It prints:

Broker name on the shared class: None
'NSE|2885' -> broker_token='2885' segments=10 starting with nse_equities
'BSE|500325' -> broker_token='500325' segments=10 starting with bse_equities
'NFO|35003' -> broker_token='35003' segments=16 starting with nse_fixed_income_futures
'CDS|1573' -> broker_token='1573' segments=16 starting with nse_fixed_income_futures
'NCO|2003' -> broker_token='2003' segments=16 starting with nse_fixed_income_futures
'BFO|1135101' -> broker_token='1135101' segments=16 starting with bse_fixed_income_futures
'BCD|8001' -> broker_token='8001' segments=16 starting with bse_fixed_income_futures
'MCX|440001' -> broker_token='440001' segments=16 starting with mcx_fixed_income_futures
'NCX|12345' -> broker_token='12345' segments=16 starting with ncdex_fixed_income_futures
'NCDEX|12345' -> broker_token='12345' segments=16 starting with ncdex_fixed_income_futures
' mcx | 440001 ' -> broker_token='440001' segments=16 starting with mcx_fixed_income_futures
'LME|1' -> None
'NSE|' -> None
examples/stock_brokers/instruments/ticks/noren/NorenTickNormalizer/example_2_a_new_noren_broker.py
"""Adds a normalizer for a new Noren broker in one line, and normalizes an NCDEX tick with it.

A broker that runs on the Noren platform needs nothing of its own but a name: `FlattradeTickNormalizer` and `ShoonyaTickNormalizer` are both a subclass of `NorenTickNormalizer` that sets `BROKER_NAME`. This program does the same for an imaginary Noren broker, then uses what it inherits: `feed_key` places an NCDEX token, `quantity_basis` says NCDEX quantities arrive in lots, and `close_policy` says `close` is the previous close only before the session ends.

The plan that `normalize` applies is normally compiled by `TickResolver` from the mapping cache; here it is built by hand with the same rules, for an NCDEX guar seed future with a lot of 5 units, so no database is needed. The tick is received at 20:00 India time, inside NCDEX's evening session, and is normalized twice, as received before the session's close and after it, which only changes whether `reported_close` is kept.

Run it from the project root:

    python examples/stock_brokers/instruments/ticks/noren/NorenTickNormalizer/example_2_a_new_noren_broker.py
"""

import datetime
import zoneinfo

from stock_brokers.instruments.ticks import base
from stock_brokers.instruments.ticks.base import (
    InstrumentPlan,
)
from stock_brokers.instruments.ticks.noren import (
    NorenTickNormalizer,
)
from stock_brokers.instruments.ticks.utilities import sessions

INDIA = zoneinfo.ZoneInfo('Asia/Kolkata')


class ImaginaryNorenTickNormalizer(NorenTickNormalizer):
    """Normalizes ticks from an imaginary broker on the Noren platform."""

    BROKER_NAME = 'imaginary_noren'


class NewNorenBrokerExample:
    """Normalizes one NCDEX tick with the new broker's normalizer.

    Attributes:
        normalizer (ImaginaryNorenTickNormalizer): The normalizer being shown.
        received_at (float): When the tick was received, in epoch seconds.
        tick (dict): The tick as the market feed published it.
    """

    def __init__(self):
        """Builds the normalizer and the tick.

        Returns:
            None: This method returns nothing.
        """
        self.normalizer = ImaginaryNorenTickNormalizer()
        received = datetime.datetime(2026, 9, 15, 20, 0, tzinfo=INDIA)
        self.received_at = received.timestamp()
        self.tick = {
            'instrument_token': 'NCX|12345',
            'last_price': 5412.0,
            'last_quantity': 2,
            'volume': 3120,
            'ohlc': {
                'open': 5390.0,
                'high': 5430.0,
                'low': 5381.0,
                'close': 5377.0,
            },
            'oi': 14880,
            'last_trade_time': self.received_at - 3.0,
            'exchange_timestamp': self.received_at - 1.0,
            'received_at': self.received_at,
        }

    def build_plan(self, broker_token):
        """Compiles the plan for the guar seed future the way the resolver would.

        Args:
            broker_token (str): The token the mappings were searched with.

        Returns:
            InstrumentPlan: The plan.
        """
        exchange = 'ncdex'
        segment = 'ncdex_commodity_futures'
        lot_size = 5
        multipliers = {}
        for field in base.QUANTITY_FIELDS:
            if self.normalizer.quantity_basis(exchange, field) == base.BASIS_LOTS:
                multipliers[field] = lot_size
            else:
                multipliers[field] = 1
        identity = {
            'exchange': exchange,
            'segment': segment,
            'shape': 'future',
            'symbol': 'GUARSEED10OCT2026',
        }
        instrument_id = '11111111-1111-5111-8111-000000000110'
        return InstrumentPlan(
            instrument_id=instrument_id,
            broker=ImaginaryNorenTickNormalizer.BROKER_NAME,
            broker_token=broker_token,
            exchange=exchange,
            segment=segment,
            shape='future',
            session=sessions.session_for(segment),
            lot_size=lot_size,
            multipliers=multipliers,
            price_decimals=2,
            negative_prices=True,
            close_policy=self.normalizer.close_policy(exchange),
            has_open_interest=True,
            trusts_last_trade_time=True,
            trusts_exchange_time=True,
            identity_json=base.identity_json(
                instrument_id,
                ImaginaryNorenTickNormalizer.BROKER_NAME,
                broker_token,
                identity,
                lot_size,
            ),
        )

    def run(self):
        """Prints the feed key, the rules and the normalized values.

        Returns:
            None: This method returns nothing.
        """
        feed_key = self.normalizer.feed_key(self.tick['instrument_token'])
        print(f'Broker: {ImaginaryNorenTickNormalizer.BROKER_NAME}')
        print(f'Token {self.tick["instrument_token"]!r}: broker_token {feed_key.broker_token!r}, {len(feed_key.segments)} segments starting with {feed_key.segments[0]}')
        print(f"NCDEX volume is reported in {self.normalizer.quantity_basis('ncdex', 'volume')}")
        print(f"NCDEX close policy: {self.normalizer.close_policy('ncdex')}")
        plan = self.build_plan(feed_key.broker_token)
        before_close = self.normalizer.normalize(self.tick, plan, True)
        after_close = self.normalizer.normalize(self.tick, plan, False)
        print(f"last_price {before_close['last_price']}, last_quantity {before_close['last_quantity']}, volume {before_close['volume']}, oi {before_close['oi']}")
        print(f"reported_close before the close {before_close['reported_close']}, after it {after_close['reported_close']}")
        gate = sessions.SessionGate(sessions.TradingCalendar.empty())
        print(f'Received inside the NCDEX window: {gate.in_window("ncdex", plan.session, self.received_at)}')


if __name__ == '__main__':
    NewNorenBrokerExample().run()

It prints:

Broker: imaginary_noren
Token 'NCX|12345': broker_token '12345', 16 segments starting with ncdex_fixed_income_futures
NCDEX volume is reported in lots
NCDEX close policy: before
last_price 5412.0, last_quantity 10, volume 15600, oi 74400
reported_close before the close 5377.0, after it None
Received inside the NCDEX window: True

Methods:

Name Description
feed_key

The broker token and segments for a Noren EXCHANGE|TOKEN.

tick_spelling

A subscription-set token spelled the way the feed spells it on ticks.

quantity_basis

Whether the broker reports a quantity field in lots or units on an exchange.

close_policy

When the broker's close is the previous session's close on an exchange.

normalize

Apply a plan to one tick.

Source code in stock_brokers/instruments/ticks/noren.py
def __init__(self):
    """
    Build the lookup from each Noren exchange name to the canonical segments it can hold.

    The table is worked out once here from `EXCHANGES`, so `feed_key` only has to look a tick's exchange name up rather than recompute its segments on every call.
    """
    self._segments = {name: family_segments(exchanges, family)
                      for name, (exchanges, family) in self.EXCHANGES.items()}

feed_key

feed_key(instrument_token)

The broker token and segments for a Noren EXCHANGE|TOKEN.

Parameters:

Name Type Description Default
instrument_token str

The tick's instrument_token, such as "MCX|565899".

required

Returns:

Type Description
FeedKey | None

The key, or None for a malformed token or an unknown exchange.

Example usage

From example_1_every_noren_exchange.py
feed_key = self.normalizer.feed_key(token)
From example_2_a_new_noren_broker.py
feed_key = self.normalizer.feed_key(self.tick['instrument_token'])
Source code in stock_brokers/instruments/ticks/noren.py
def feed_key(self, instrument_token):
    """
    The broker token and segments for a Noren `EXCHANGE|TOKEN`.

    Args:
        instrument_token (str): The tick's `instrument_token`, such as "MCX|565899".

    Returns:
        FeedKey | None: The key, or None for a malformed token or an unknown exchange.
    """
    exchange, _, token = str(instrument_token).partition("|")
    segments = self._segments.get(exchange.strip().upper())
    if segments is None or not token.strip():
        return None
    return FeedKey(token.strip(), segments)

tick_spelling

tick_spelling(subscription_token)

A subscription-set token spelled the way the feed spells it on ticks.

Plans are filed under the tick's spelling, because that is what the per-tick path looks up. A subscription set holds strings in the spelling the broker's quote feed subscribes with, which is not always the tick's: Kite ticks carry an integer, Dhan ticks a numeric segment code.

Parameters:

Name Type Description Default
subscription_token str

A member of subscriptions_<broker>.

required

Returns:

Type Description
int | str | None

The tick's spelling, or None for a token this normalizer cannot place.

Example usage

From example_1_a_new_broker_subclass.py
print(f"Tick spelling of 'NSE-2885': {self.normalizer.tick_spelling('NSE-2885')!r}")
Source code in stock_brokers/instruments/ticks/base.py
def tick_spelling(self, subscription_token):
    """
    A subscription-set token spelled the way the feed spells it on ticks.

    Plans are filed under the tick's spelling, because that is what the per-tick path looks up.
    A subscription set holds strings in the spelling the broker's quote feed subscribes with, which is
    not always the tick's: Kite ticks carry an integer, Dhan ticks a numeric segment code.

    Args:
        subscription_token (str): A member of `subscriptions_<broker>`.

    Returns:
        int | str | None: The tick's spelling, or None for a token this normalizer cannot place.
    """
    return subscription_token

quantity_basis

quantity_basis(exchange, field)

Whether the broker reports a quantity field in lots or units on an exchange.

Parameters:

Name Type Description Default
exchange str

Canonical exchange.

required
field str

One of QUANTITY_FIELDS.

required

Returns:

Type Description
str

BASIS_UNITS, BASIS_LOTS or BASIS_BROKER_LOTS.

Example usage

From example_1_a_new_broker_subclass.py
basis = self.normalizer.quantity_basis(exchange, field)
From example_2_what_normalize_refuses.py
self.normalizer.quantity_basis(exchange, field)
Source code in stock_brokers/instruments/ticks/base.py
def quantity_basis(self, exchange, field):
    """
    Whether the broker reports a quantity field in lots or units on an exchange.

    Args:
        exchange (str): Canonical exchange.
        field (str): One of QUANTITY_FIELDS.

    Returns:
        str: BASIS_UNITS, BASIS_LOTS or BASIS_BROKER_LOTS.
    """
    if field in self.BROKER_LOT_FIELDS.get(exchange, ()):
        return BASIS_BROKER_LOTS
    if field in self.LOT_FIELDS.get(exchange, ()):
        return BASIS_LOTS
    return BASIS_UNITS

close_policy

close_policy(exchange)

When the broker's close is the previous session's close on an exchange.

Parameters:

Name Type Description Default
exchange str

Canonical exchange.

required

Returns:

Type Description
str

One of the CLOSE_* policies.

Example usage

From example_1_a_new_broker_subclass.py
print(f'{exchange} close policy: {self.normalizer.close_policy(exchange)}')
From example_2_what_normalize_refuses.py
return InstrumentPlan(
    instrument_id='11111111-1111-5111-8111-000000000102',
    broker='imaginary',
    broker_token='2885',
    exchange=exchange,
    segment=segment,
    shape=shape,
    session=sessions.session_for(segment),
    lot_size=lot_size,
    multipliers=multipliers,
    price_decimals=2,
    negative_prices=shape != 'security' and exchange == 'mcx',
    close_policy=self.normalizer.close_policy(exchange),
    has_open_interest=shape != 'security',
    trusts_last_trade_time=True,
    trusts_exchange_time=True,
    identity_json='"instrument_id":"11111111-1111-5111-8111-000000000102"',
)
Source code in stock_brokers/instruments/ticks/base.py
def close_policy(self, exchange):
    """
    When the broker's `close` is the previous session's close on an exchange.

    Args:
        exchange (str): Canonical exchange.

    Returns:
        str: One of the CLOSE_* policies.
    """
    return self.CLOSE_POLICY.get(exchange, CLOSE_NEVER)

normalize

normalize(tick, plan, before_trading_close)

Apply a plan to one tick.

Parameters:

Name Type Description Default
tick dict

A tick as the broker's market feed published it.

required
plan InstrumentPlan

The compiled plan for the tick's token.

required
before_trading_close bool

Whether the tick was received before its session's close, for the close policy.

required

Returns:

Type Description
dict | None

The normalized values, or None when the tick carries no usable last price. Keys are last_price, last_quantity, average_price, volume, buy_quantity, sell_quantity, open, high, low, reported_close, oi, oi_day_high, oi_day_low, last_trade_time, exchange_time, bids and asks, where bids and asks are lists of (price, quantity, orders) best first.

Example usage

From example_2_what_normalize_refuses.py
values = self.normalizer.normalize(tick, share, True)
From example_2_what_normalize_refuses.py
values = self.normalizer.normalize(
    {
        'last_price': -12.5,
        'volume': 400,
        'oi': 900,
        'received_at': self.received_at,
    },
    future,
    True,
)
From example_2_what_normalize_refuses.py
outcome = self.normalizer.normalize(
    {
        'last_price': last_price,
        'received_at': self.received_at,
    },
    share,
    True,
)
Source code in stock_brokers/instruments/ticks/base.py
def normalize(self, tick, plan, before_trading_close):
    """
    Apply a plan to one tick.

    Args:
        tick (dict): A tick as the broker's market feed published it.
        plan (InstrumentPlan): The compiled plan for the tick's token.
        before_trading_close (bool): Whether the tick was received before its session's close, for the close policy.

    Returns:
        dict | None: The normalized values, or None when the tick carries no usable last price. Keys are last_price, last_quantity, average_price, volume, buy_quantity, sell_quantity, open, high, low, reported_close, oi, oi_day_high, oi_day_low, last_trade_time, exchange_time, bids and asks, where bids and asks are lists of (price, quantity, orders) best first.
    """
    decimals = plan.price_decimals
    negative = plan.negative_prices

    last_price = tick.get("last_price")
    if last_price is None or last_price == 0 or (last_price < 0 and not negative):
        return None
    last_price = round(last_price, decimals)

    multipliers = plan.multipliers
    received_at = tick.get("received_at") or 0.0
    ohlc = tick.get("ohlc") or {}

    close = None
    policy = plan.close_policy
    if policy == CLOSE_ALWAYS or (policy == CLOSE_BEFORE_SESSION_END and before_trading_close):
        close = _price(ohlc.get("close"), decimals, negative)

    if plan.has_open_interest:
        oi = _quantity(tick.get("oi"), multipliers["oi"])
        oi_day_high = _quantity(tick.get("oi_day_high") or None, multipliers["oi_day_high"])
        oi_day_low = _quantity(tick.get("oi_day_low") or None, multipliers["oi_day_low"])
    else:
        oi = oi_day_high = oi_day_low = None

    depth = tick.get("depth") or {}
    depth_multiplier = multipliers["depth_quantity"]

    return {
        "last_price": last_price,
        "last_quantity": _quantity(tick.get("last_quantity"), multipliers["last_quantity"]),
        "average_price": _price(tick.get("average_price"), decimals, negative),
        "volume": _quantity(tick.get("volume"), multipliers["volume"]),
        "buy_quantity": _quantity(tick.get("buy_quantity"), multipliers["buy_quantity"]),
        "sell_quantity": _quantity(tick.get("sell_quantity"), multipliers["sell_quantity"]),
        "open": _price(ohlc.get("open"), decimals, negative),
        "high": _price(ohlc.get("high"), decimals, negative),
        "low": _price(ohlc.get("low"), decimals, negative),
        "reported_close": close,
        "oi": oi,
        "oi_day_high": oi_day_high,
        "oi_day_low": oi_day_low,
        "last_trade_time": self._instant(tick.get("last_trade_time"), received_at) if plan.trusts_last_trade_time else None,
        "exchange_time": self._instant(tick.get("exchange_timestamp"), received_at) if plan.trusts_exchange_time else None,
        "bids": _levels(depth.get("buy"), decimals, negative, depth_multiplier),
        "asks": _levels(depth.get("sell"), decimals, negative, depth_multiplier),
    }

_instant

_instant(value, received_at)

A broker timestamp as a true epoch, or None when it is missing or implausible.

Brokers that stamp times on a shifted clock have that corrected in their market feed; what is left to catch here is a clock nobody has corrected yet, which shows as an instant days away from when the tick arrived.

Parameters:

Name Type Description Default
value float | int | None

Epoch seconds as the feed published them.

required
received_at float

When the feed decoded the tick.

required

Returns:

Type Description
float | None

The instant, or None.

Source code in stock_brokers/instruments/ticks/base.py
def _instant(self, value, received_at):
    """
    A broker timestamp as a true epoch, or None when it is missing or implausible.

    Brokers that stamp times on a shifted clock have that corrected in their market feed; what is
    left to catch here is a clock nobody has corrected yet, which shows as an instant days away
    from when the tick arrived.

    Args:
        value (float | int | None): Epoch seconds as the feed published them.
        received_at (float): When the feed decoded the tick.

    Returns:
        float | None: The instant, or None.
    """
    if not value:
        return None
    try:
        instant = float(value)
    except (TypeError, ValueError):
        return None
    if received_at and not (received_at - self.MAXIMUM_TIMESTAMP_AGE_SECONDS
                            <= instant <= received_at + self.MAXIMUM_TIMESTAMP_LEAD_SECONDS):
        return None
    return instant