Skip to content

fyers

Fyers ticks.

A Fyers tick names its instrument by symbol - NSE:SBIN-EQ, MCX:CRUDEOIL26SEPFUT - rather than by token. unified.broker_mappings stores that symbol as Fyers's order symbol beside its numeric token, so the resolver translates one to the other before searching. The symbol's prefix gives the exchange and its shape the kind: -INDEX is an index, a symbol ending in FUT, CE or PE a derivative on NSE and BSE, anything else there cash; everything on MCX is a derivative.

Nothing Fyers streams is stored yet - its raw tick table is empty - so every fact below is from its protocol rather than from a measurement, and the broker stays unverified until a live session:

  • prev_close_price is named for what it is, and is taken as the previous close at every hour.
  • MCX quantities are taken to be lots, as at every broker measured.
  • The last traded and exchange feed times are epochs.

Currency derivatives are left out. Fyers scales prices by a precision and multiplier per instrument that arrive with the subscription, and the market feed does not read them yet, so it divides every price by 100 - right for two-decimal instruments, a hundred times wrong for four-decimal currency pairs. Refusing those instruments writes nothing rather than something wrong; they return once the feed reads its scale.

Classes:

Name Description
FyersTickNormalizer

Normalizes Fyers ticks.

FyersTickNormalizer

FyersTickNormalizer()

Bases: TickNormalizer

Normalizes Fyers ticks.

Build the lookup from each Fyers exchange prefix and instrument family to the canonical segments it can hold.

Every prefix in EXCHANGES gets an entry for the cash, index and derivative families. Currency derivative segments are left out of the derivative entry, because the market feed does not yet scale Fyers currency prices correctly, so those instruments are refused rather than resolved.

Example programs

examples/stock_brokers/instruments/ticks/fyers/FyersTickNormalizer/example_1_placing_feed_tokens.py
"""Places Fyers feed tokens, which are ticker symbols, on the canonical segments they can be in.

A Fyers tick names its instrument by the Fyers symbol rather than by a number, so the key carries no broker token: it carries the symbol as `order_symbol`, which the resolver turns into a token through the broker mappings. The normalizer decides the family from the symbol itself: MCX and names ending in FUT, CE or PE are derivatives, names ending in -INDEX are indices, and everything else is cash. An exchange Fyers does not serve, or a prefix with no name after it, prints `None`.

The normalizer only works on the text of each token, so the program needs no Redis, database, network or stand-in of any kind. Each output line shows a token as the feed spells it, then the broker token that `unified.broker_mappings` is searched with, the order symbol when the broker names instruments by symbol, and how many canonical segments the search is limited to, 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/fyers/FyersTickNormalizer/example_1_placing_feed_tokens.py
"""

from stock_brokers.instruments.ticks.fyers import (
    FyersTickNormalizer,
)


class PlacingFeedTokensExample:
    """Asks the Fyers normalizer where each of a few feed tokens belongs.

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

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

        Returns:
            None: This method returns nothing.
        """
        self.normalizer = FyersTickNormalizer()
        self.tokens = [
            'NSE:SBIN-EQ',
            'NSE:NIFTY50-INDEX',
            'NSE:NIFTY26SEPFUT',
            'MCX:CRUDEOIL26OCTFUT',
            'LSE:VOD',
            '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} order_symbol={feed_key.order_symbol!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: {FyersTickNormalizer.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__':
    PlacingFeedTokensExample().run()

It prints:

Broker: fyers
'NSE:SBIN-EQ' -> broker_token=None order_symbol='NSE:SBIN-EQ' segments=6 starting with nse_equities
'NSE:NIFTY50-INDEX' -> broker_token=None order_symbol='NSE:NIFTY50-INDEX' segments=4 starting with nse_equity_indices
'NSE:NIFTY26SEPFUT' -> broker_token=None order_symbol='NSE:NIFTY26SEPFUT' segments=12 starting with nse_fixed_income_futures
'MCX:CRUDEOIL26OCTFUT' -> broker_token=None order_symbol='MCX:CRUDEOIL26OCTFUT' segments=12 starting with mcx_fixed_income_futures
'LSE:VOD' -> None
'NSE:' -> None
examples/stock_brokers/instruments/ticks/fyers/FyersTickNormalizer/example_2_a_share_named_by_symbol.py
"""Normalizes one Fyers tick for SBIN on NSE, whose `close` is trusted at every hour.

Fyers names the instrument by symbol, so the program prints the order symbol the feed key carries. The plan's broker token, 3045, is what the resolver would have found for that symbol in the broker mappings. Fyers' `close` is always the previous session's close, so `reported_close` is kept both before and after the session ends.

The plan that `normalize` applies is normally compiled by `TickResolver` from the mapping cache. This program builds it by hand with the same rules, asking the normalizer how it reports each quantity field and when its close may be trusted, so it needs no database. The tick is shaped like the ones the broker's market feed publishes, with fixed times, so the output is the same on every run. The program normalizes the tick twice, once as received before the session's close and once as received after it, which only changes whether `reported_close` is kept. Order book levels are printed as (price, quantity, orders).

Run it from the project root:

    python examples/stock_brokers/instruments/ticks/fyers/FyersTickNormalizer/example_2_a_share_named_by_symbol.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.fyers import (
    FyersTickNormalizer,
)
from stock_brokers.instruments.ticks.utilities import sessions

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


class StateBankTickExample:
    """Normalizes one Fyers tick through a hand-built plan and prints the result.

    Attributes:
        normalizer (FyersTickNormalizer): 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.
        plan (InstrumentPlan): The plan for the tick's instrument.
    """

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

        Returns:
            None: This method returns nothing.
        """
        self.normalizer = FyersTickNormalizer()
        received = datetime.datetime(2026, 9, 15, 11, 5, tzinfo=INDIA)
        self.received_at = received.timestamp()
        self.tick = {
            'instrument_token': 'NSE:SBIN-EQ',
            'last_price': 812.35,
            'last_quantity': 40,
            'average_price': 810.92,
            'volume': 6320415,
            'buy_quantity': 812640,
            'sell_quantity': 790115,
            'ohlc': {
                'open': 806.0,
                'high': 813.9,
                'low': 805.2,
                'close': 804.55,
            },
            'oi': None,
            'oi_day_high': None,
            'oi_day_low': None,
            'last_trade_time': self.received_at - 2.0,
            'exchange_timestamp': self.received_at - 1.0,
            'received_at': self.received_at,
            'depth': {
                'buy': [
                    {
                        'price': 812.3,
                        'quantity': 1500,
                        'orders': 12,
                    },
                    {
                        'price': 812.25,
                        'quantity': 820,
                        'orders': 7,
                    },
                    {
                        'price': 812.2,
                        'quantity': 2300,
                        'orders': 15,
                    },
                    {
                        'price': 812.15,
                        'quantity': 640,
                        'orders': 5,
                    },
                    {
                        'price': 812.1,
                        'quantity': 1910,
                        'orders': 11,
                    },
                ],
                'sell': [
                    {
                        'price': 812.35,
                        'quantity': 700,
                        'orders': 6,
                    },
                    {
                        'price': 812.4,
                        'quantity': 1340,
                        'orders': 10,
                    },
                    {
                        'price': 812.45,
                        'quantity': 980,
                        'orders': 8,
                    },
                    {
                        'price': 812.5,
                        'quantity': 4100,
                        'orders': 21,
                    },
                    {
                        'price': 812.55,
                        'quantity': 560,
                        'orders': 4,
                    },
                ],
            },
        }
        self.plan = self.build_plan()

    def build_plan(self):
        """Compiles the plan for the tick's instrument the way the resolver would.

        Returns:
            InstrumentPlan: The plan.
        """
        exchange = 'nse'
        segment = 'nse_equities'
        lot_size = 1
        broker_lot_size = 1
        multipliers = {}
        for field in base.QUANTITY_FIELDS:
            basis = self.normalizer.quantity_basis(exchange, field)
            if basis == base.BASIS_LOTS:
                multipliers[field] = lot_size
            elif basis == base.BASIS_BROKER_LOTS:
                multipliers[field] = lot_size // broker_lot_size
            else:
                multipliers[field] = 1
        identity = {
            'exchange': 'nse',
            'segment': 'nse_equities',
            'shape': 'security',
            'symbol': 'SBIN',
            'underlying_symbol': None,
            'expiry_date': None,
            'strike_price': None,
            'option_type': None,
        }
        instrument_id = '11111111-1111-5111-8111-000000000104'
        broker_token = '3045'
        return InstrumentPlan(
            instrument_id=instrument_id,
            broker=FyersTickNormalizer.BROKER_NAME,
            broker_token=broker_token,
            exchange=exchange,
            segment=segment,
            shape=identity['shape'],
            session=sessions.session_for(segment),
            lot_size=lot_size,
            multipliers=multipliers,
            price_decimals=2,
            negative_prices=False,
            close_policy=self.normalizer.close_policy(exchange),
            has_open_interest=identity['shape'] != 'security',
            trusts_last_trade_time=FyersTickNormalizer.TRUSTS_LAST_TRADE_TIME,
            trusts_exchange_time=FyersTickNormalizer.TRUSTS_EXCHANGE_TIME,
            identity_json=base.identity_json(
                instrument_id,
                FyersTickNormalizer.BROKER_NAME,
                broker_token,
                identity,
                lot_size,
            ),
        )

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

        Returns:
            None: This method returns nothing.
        """
        token = self.tick['instrument_token']
        feed_key = self.normalizer.feed_key(token)
        print(f'Token {token!r}: broker_token={feed_key.broker_token!r} order_symbol={feed_key.order_symbol!r}')
        volume_basis = self.normalizer.quantity_basis(self.plan.exchange, 'volume')
        print(f'Volume on {self.plan.exchange} is reported in {volume_basis}, multiplier {self.plan.multipliers["volume"]}')
        print(f'Close policy on {self.plan.exchange}: {self.normalizer.close_policy(self.plan.exchange)}')
        before_close = self.normalizer.normalize(self.tick, self.plan, True)
        print('Normalized, received before the close:')
        for name, value in before_close.items():
            print(f'  {name}: {value}')
        after_close = self.normalizer.normalize(self.tick, self.plan, False)
        print(f'reported_close when received after the close: {after_close["reported_close"]}')


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

It prints:

Token 'NSE:SBIN-EQ': broker_token=None order_symbol='NSE:SBIN-EQ'
Volume on nse is reported in units, multiplier 1
Close policy on nse: always
Normalized, received before the close:
  last_price: 812.35
  last_quantity: 40
  average_price: 810.92
  volume: 6320415
  buy_quantity: 812640
  sell_quantity: 790115
  open: 806.0
  high: 813.9
  low: 805.2
  reported_close: 804.55
  oi: None
  oi_day_high: None
  oi_day_low: None
  last_trade_time: 1789450498.0
  exchange_time: 1789450499.0
  bids: [(812.3, 1500, 12), (812.25, 820, 7), (812.2, 2300, 15), (812.15, 640, 5), (812.1, 1910, 11)]
  asks: [(812.35, 700, 6), (812.4, 1340, 10), (812.45, 980, 8), (812.5, 4100, 21), (812.55, 560, 4)]
reported_close when received after the close: 804.55

Methods:

Name Description
feed_key

The order symbol and segments for a Fyers symbol.

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/fyers.py
def __init__(self):
    """
    Build the lookup from each Fyers exchange prefix and instrument family to the canonical segments it can hold.

    Every prefix in `EXCHANGES` gets an entry for the cash, index and derivative families. Currency derivative segments are left out of the derivative entry, because the market feed does not yet scale Fyers currency prices correctly, so those instruments are refused rather than resolved.
    """
    self._segments = {}
    for prefix, exchange in self.EXCHANGES.items():
        for family in ("cash", "index", "derivative"):
            segments = family_segments([exchange], family)
            if family == "derivative":
                segments = tuple(segment for segment in segments if "_currenc" not in segment)
            self._segments[(prefix, family)] = segments

feed_key

feed_key(instrument_token)

The order symbol and segments for a Fyers symbol.

Parameters:

Name Type Description Default
instrument_token str

The tick's instrument_token, a Fyers symbol such as "NSE:SBIN-EQ".

required

Returns:

Type Description
FeedKey | None

The key, carrying the symbol as the order symbol, or None for an unknown exchange.

Example usage

From example_1_placing_feed_tokens.py
feed_key = self.normalizer.feed_key(token)
From example_2_a_share_named_by_symbol.py
feed_key = self.normalizer.feed_key(token)
Source code in stock_brokers/instruments/ticks/fyers.py
def feed_key(self, instrument_token):
    """
    The order symbol and segments for a Fyers symbol.

    Args:
        instrument_token (str): The tick's `instrument_token`, a Fyers symbol such as "NSE:SBIN-EQ".

    Returns:
        FeedKey | None: The key, carrying the symbol as the order symbol, or None for an unknown exchange.
    """
    symbol = str(instrument_token).strip()
    prefix, _, name = symbol.partition(":")
    prefix = prefix.upper()
    if prefix not in self.EXCHANGES or not name:
        return None
    if prefix == "MCX" or name.upper().endswith(self.DERIVATIVE_SUFFIXES):
        family = "derivative"
    elif name.upper().endswith("-INDEX"):
        family = "index"
    else:
        family = "cash"
    return FeedKey(None, self._segments[(prefix, family)], symbol)

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