Skip to content

Symbol Info

CSymbolInfo

Source code in strategytester5\trade_classes\SymbolInfo.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
class CSymbolInfo:
    def __init__(self, symbol: str, terminal: Union[OverLoadedMetaTrader5API|MetaTrader5]) -> None:

        """
        A lightweight Python wrapper that resembles the MQL5 Standard Library class
        `CSymbolInfo` and provides convenient, read-only access to MetaTrader 5
        symbol properties.

        This class stores a symbol name, cached symbol information, and cached tick
        information. Symbol data can be refreshed via `refresh()`, while quote/tick
        data can be refreshed via `refresh_rates()`.

        [MQL5 Reference](https://www.mql5.com/en/docs/standardlibrary/tradeclasses/csymbolinfo)

        Parameters
        ----------
        terminal : Initialize native MetaTrader5 API or the simulated one from the StrategyTester instance

        Notes
        -----
        - `name(symbol_name)` assigns the symbol and refreshes its static info.
        - `refresh_rates()` updates the cached tick/quote values.
        - If tick data is not refreshed yet, tick-related properties return their default cached values.
        - Time values are returned as timezone-aware UTC datetimes where applicable.
        """

        self.terminal = terminal

        self._symbol: str = symbol
        self._info: Optional[Any] = None

        self._tick_info: dict[str, Any] = {
            "time": None,
            "ask": 0.0,
            "bid": 0.0,
            "last": 0.0,
            "volume": 0,
            "time_msc": 0,
            "volume_real": 0.0,
        }   

        self.check_market_watch()
        self.refresh()

    def refresh(self) -> bool:
        """
        Refreshes the symbol data by fetching symbol information from the terminal.

        Returns
        -------
        bool
            True if successful.

        Raises
        ------
        RuntimeError
            If symbol information could not be retrieved.
        """
        info = self.terminal.symbol_info(self._symbol)
        if not info:
            code, msg = self.terminal.last_error()
            raise RuntimeError(f"Failed to get symbol info for {self._symbol}. MetaTrader5 error = [{code}] {msg}")

        self._info = info
        return True

    def refresh_rates(self) -> bool:
        """
        Refreshes the symbol quotes/gets the latest tick information from the market using `symbol_info_tick(symbol)`.

        Returns
        -------
        bool
            True if successful, False otherwise.
        """
        try:
            new_ticks = self.terminal.symbol_info_tick(self._symbol)
            if new_ticks is None:
                return False

            self._tick_info["time"] = new_ticks.time
            self._tick_info["ask"] = new_ticks.ask
            self._tick_info["bid"] = new_ticks.bid
            self._tick_info["last"] = new_ticks.last
            self._tick_info["volume"] = new_ticks.volume
            self._tick_info["time_msc"] = new_ticks.time_msc
            self._tick_info["volume_real"] = new_ticks.volume_real

            return True

        except AttributeError:
            return False

    # ----------- Basic properties ---------------

    @property
    def symbol(self) -> str:
        """Gets the current symbol name."""
        return self._symbol

    def select(self, select: bool = True) -> bool:
        """
        Gets/sets the Market Watch symbol flag.

        Parameters
        ----------
        select : bool, optional
            True to select the symbol in Market Watch, False to deselect.

        Returns
        -------
        bool
            True if successful, otherwise False.
        """
        return self.terminal.symbol_select(self._symbol, select)

    def check_market_watch(self) -> bool:
        """Checks if the symbol is selected in Market Watch and adds it to the Market Watch if necessary."""

        if not self.select():
            raise ValueError(f"Failed to select a symbol {self._symbol}")

        if not self.select(True):
            raise ValueError(f"Failed to add a symbol {self._symbol} in Market Watch")

        return True

    @property
    def is_synchronized(self) -> bool:
        """Checks the symbol synchronization with server."""
        return bool(self._info.select) if self._info else False

    # ------------- Volumes ---------------

    @property
    def volume(self) -> int:
        """Gets the volume of the last deal."""
        return int(self._tick_info["volume"])

    @property
    def volume_real(self) -> float:
        """Gets the real volume of the last deal."""
        return float(self._tick_info["volume_real"])

    @property
    def volume_high(self) -> int:
        """Gets the maximal volume for a day."""
        return int(self._info.volumehigh) if self._info else 0

    @property
    def volume_low(self) -> int:
        """Gets the minimal volume for a day."""
        return int(self._info.volumelow) if self._info else 0

    # ------------ Miscellaneous ------------

    @property
    def time(self) -> Optional[datetime]:
        """Gets the time of the last quote as UTC datetime."""
        tick_time = self._tick_info["time"]
        if tick_time is None:
            return None
        return datetime.fromtimestamp(tick_time, tz=timezone.utc)

    @property
    def time_msc(self) -> int:
        """Gets the time of the last quote in milliseconds."""
        return int(self._tick_info["time_msc"])

    @property
    def spread(self) -> float:
        """Gets the amount of spread in points."""
        return float(self._info.spread) if self._info else 0.0

    @property
    def spread_float(self) -> bool:
        """Gets the floating spread flag."""
        return bool(self._info.spread_float) if self._info else False

    @property
    def ticks_book_depth(self) -> int:
        """Gets the depth of ticks saving."""
        return int(self._info.ticks_bookdepth) if self._info else 0

    # -------------- Trade levels ---------------

    @property
    def stops_level(self) -> int:
        """Gets the minimal indent for orders in points."""
        return int(self._info.trade_stops_level) if self._info else 0

    @property
    def freeze_level(self) -> int:
        """Gets the freezing distance for trade operations in points."""
        return int(self._info.trade_freeze_level) if self._info else 0

    # ------------- Bid prices -------------

    @property
    def bid(self) -> float:
        """Returns the current bid price."""
        return float(self._tick_info["bid"])

    @property
    def bid_high(self) -> float:
        """Gets the maximal Bid price for a day."""
        return float(self._info.bidhigh) if self._info else 0.0

    @property
    def bid_low(self) -> float:
        """Gets the minimal Bid price for a day."""
        return float(self._info.bidlow) if self._info else 0.0

    # ------------- Ask prices -------------

    @property
    def ask(self) -> float:
        """Returns the current ask price."""
        return float(self._tick_info["ask"])

    @property
    def ask_high(self) -> float:
        """Gets the maximal Ask price for a day."""
        return float(self._info.askhigh) if self._info else 0.0

    @property
    def ask_low(self) -> float:
        """Gets the minimal Ask price for a day."""
        return float(self._info.asklow) if self._info else 0.0

    # ------------- Last prices -------------

    @property
    def last(self) -> float:
        """Gets the current Last price."""
        return float(self._tick_info["last"])

    @property
    def last_high(self) -> float:
        """Gets the maximal Last price for a day."""
        return float(self._info.lasthigh) if self._info else 0.0

    @property
    def last_low(self) -> float:
        """Gets the minimal Last price for a day."""
        return float(self._info.lastlow) if self._info else 0.0

    # ------------ Trade modes -------------

    @property
    def trade_calc_mode(self) -> int:
        """Contract price calculation mode"""
        return int(self._info.trade_calc_mode) if self._info else -1

    @property
    def trade_calc_mode_description(self) -> str:
        """A description of contract price calculation modes."""

        calc_mode_map = {
            self.terminal.SYMBOL_CALC_MODE_FOREX: "Calculation of profit and margin for Forex",
            self.terminal.SYMBOL_CALC_MODE_FUTURES: "Calculation of margin and profit for futures",
            self.terminal.SYMBOL_CALC_MODE_CFD: "Calculation of margin and profit for CFD",
            self.terminal.SYMBOL_CALC_MODE_CFDINDEX: "Calculation of margin and profit for CFD by indexes",
            self.terminal.SYMBOL_CALC_MODE_CFDLEVERAGE: "Calculation of margin and profit for CFD at leverage trading",
            self.terminal.SYMBOL_CALC_MODE_FOREX_NO_LEVERAGE: "Calculation of profit and margin for Forex symbols without taking into account the leverage",
            self.terminal.SYMBOL_CALC_MODE_EXCH_STOCKS: "Calculation of margin and profit for trading securities on a stock exchange",
            self.terminal.SYMBOL_CALC_MODE_EXCH_FUTURES: "Calculation of margin and profit for trading futures contracts on a stock exchange",
            self.terminal.SYMBOL_CALC_MODE_EXCH_BONDS: "Calculation of margin and profit for trading bonds on a stock exchange",
            self.terminal.SYMBOL_CALC_MODE_EXCH_STOCKS_MOEX: "Calculation of margin and profit for trading securities on MOEX",
            self.terminal.SYMBOL_CALC_MODE_EXCH_BONDS_MOEX: "Calculation of margin and profit for trading bonds on MOEX",
            self.terminal.SYMBOL_CALC_MODE_SERV_COLLATERAL: "Collateral mode - a symbol is used as a non-tradable asset on a trading account"
        }
        return calc_mode_map.get(self.trade_calc_mode, "Unknown trade calculation mode")

    @property
    def trade_mode(self) -> int:
        """Symbol trading modes (restrictions)"""
        return int(self._info.trade_mode) if self._info else -1

    @property
    def trade_mode_description(self) -> str:
        """A description of symbol trading modes (restrictions)"""
        trade_mode_map = {
            self.terminal.SYMBOL_TRADE_MODE_DISABLED: "Trade is disabled for the symbol",
            self.terminal.SYMBOL_TRADE_MODE_LONGONLY: "Allowed only long positions",
            self.terminal.SYMBOL_TRADE_MODE_SHORTONLY: "Allowed only short positions",
            self.terminal.SYMBOL_TRADE_MODE_CLOSEONLY: "Allowed only position close operations",
            self.terminal.SYMBOL_TRADE_MODE_FULL: "No trade restrictions"
        }
        return trade_mode_map.get(self.trade_mode, "Unknown trade mode")

    @property
    def trade_execution(self) -> int:
        """Possible deal execution modes for a given symbol."""
        return int(self._info.trade_exemode) if self._info else -1

    @property
    def trade_execution_description(self) -> str:
        """A description of possible deal execution modes for a given symbol."""

        exec_mode_map = {
            self.terminal.SYMBOL_TRADE_EXECUTION_REQUEST: "Execution by request",
            self.terminal.SYMBOL_TRADE_EXECUTION_INSTANT: "Instant execution",
            self.terminal.SYMBOL_TRADE_EXECUTION_MARKET: "Market execution",
            self.terminal.SYMBOL_TRADE_EXECUTION_EXCHANGE: "Exchange execution"
        }
        return exec_mode_map.get(self.trade_execution, "Unkown trade execution mode")

    @property
    def order_mode(self) -> int:
        """Flags of allowed order types for the symbol (bit field)."""
        return int(self._info.order_mode) if self._info else 0

    # ----------- Swaps -------------

    @property
    def swap_mode(self) -> int:
        """A method of swap calculation at position transfer."""
        return int(self._info.swap_mode) if self._info else -1

    @property
    def swap_mode_description(self) -> str:
        """A description of methods of swap calculation at position transfer."""

        swap_mode_map = {
            self.terminal.SYMBOL_SWAP_MODE_DISABLED: "No swaps",
            self.terminal.SYMBOL_SWAP_MODE_POINTS: "Swaps are calculated in points",
            self.terminal.SYMBOL_SWAP_MODE_CURRENCY_SYMBOL: "Swaps are calculated in base currency",
            self.terminal.SYMBOL_SWAP_MODE_CURRENCY_MARGIN: "Swaps are calculated in margin currency",
            self.terminal.SYMBOL_SWAP_MODE_CURRENCY_DEPOSIT: "Swaps are calculated in deposit currency",
            self.terminal.SYMBOL_SWAP_MODE_INTEREST_CURRENT: "Swaps are calculated as annual interest using the current price",
            self.terminal.SYMBOL_SWAP_MODE_INTEREST_OPEN: "Swaps are calculated as annual interest using the open price",
            self.terminal.SYMBOL_SWAP_MODE_REOPEN_CURRENT: "Swaps are charged by reopening positions at the close price",
            self.terminal.SYMBOL_SWAP_MODE_REOPEN_BID: "Swaps are charged by reopening positions at the Bid price"
        }
        return swap_mode_map.get(self.swap_mode, "Unkown swap mode")

    @property
    def swap_rollover_3days(self) -> int:
        """The day of week to charge 3-day swap rollover"""
        return int(self._info.swap_rollover3days) if self._info else -1

    @property
    def swap_rollover_3days_description(self) -> str:
        """A description (string) of the day of week to charge 3-day swap rollover"""

        swap_rollover_map = {
            0: "Sunday",
            1: "Monday",
            2: "Tuesday",
            3: "Wednesday",
            4: "Thursday",
            5: "Friday",
            6: "Saturday",
        }
        return swap_rollover_map.get(self.swap_rollover_3days, "Unkown swap rollover 3 days")

    @property
    def filling_mode(self) -> int:
        """Flags of allowed order filling modes for the symbol (bit field)."""
        return int(self._info.filling_mode) if self._info else 0

    # ------------- Dates for futures ----------------

    @property
    def expiration_time(self) -> Optional[datetime]:
        """Date of the symbol trade end (usually used for futures)"""
        if not self._info or int(self._info.expiration_time) == 0:
            return None
        return datetime.fromtimestamp(self._info.expiration_time, tz=timezone.utc)

    @property
    def start_time(self) -> Optional[datetime]:
        """Date of the symbol trade beginning (usually used for futures)"""
        if not self._info or int(self._info.start_time) == 0:
            return None
        return datetime.fromtimestamp(self._info.start_time, tz=timezone.utc)

    # -----------  Margin parameters -------------

    @property
    def margin_initial(self) -> float:
        """        
            Initial margin means the amount in the margin currency required for opening a position with the volume of one lot. It is used for checking a client's assets when he or she enters the market.

            The SymbolInfoMarginRate() function provides data on the amount of charged margin depending on the order type and direction.
        """
        return float(self._info.margin_initial) if self._info else 0.0

    @property
    def margin_maintenance(self) -> float:
        """
            The maintenance margin. If it is set, it sets the margin amount in the margin currency of the symbol, charged from one lot. It is used for checking a client's assets when his/her account state changes. If the maintenance margin is equal to 0, the initial margin is used.

            The SymbolInfoMarginRate() function provides data on the amount of charged margin depending on the order type and direction.
        """
        return float(self._info.margin_maintenance) if self._info else 0.0

    @property
    def margin_hedged(self) -> float:

        """
            Contract size or margin value per one lot of hedged positions (oppositely directed positions of one symbol). Two margin calculation methods are possible for hedged positions. The calculation method is defined by the broker.         

            Basic calculation:
            -----------------

            - If the initial margin (margin_initial) is specified for a symbol, the hedged margin is specified as an absolute value (in monetary terms).
            - If the initial margin is not specified (equal to 0), SYMBOL_MARGIN_HEDGED is equal to the size of the contract, that will be used to calculate the margin by the appropriate formula in accordance with the type of the financial instrument (trade_calc_mode).


            Calculation for the largest position:
            -------------------------

            - The SYMBOL_MARGIN_HEDGED value is not taken into account.
            - The volume of all short and all long positions of a symbol is calculated.
            - For each direction, a weighted average open price and a weighted average rate of conversion to the deposit currency is calculated.
            - Next, using the appropriate formula chosen in accordance with the symbol type (trade_calc_mode) the margin is calculated for the short and the long part.
            - The largest one of the values is used as the margin.
        """

        return float(self._info.margin_hedged) if self._info else 0.0

    @property
    def margin_hedged_use_leg(self) -> bool:
        """
            Boolean flag to enable (true) or disable (false) the hedged margin calculation mode for the largest of the overlapped positions (buy and sell
            positions of the same symbol). 

            If the flag is enabled, the margin for the largest position is calculated using the SYMBOL_MARGIN_HEDGED value. If the flag is disabled, the margin for the largest position is calculated using the SYMBOL_MARGIN_INITIAL value.
        """

        return bool(self._info.margin_hedged_use_leg) if self._info else False

    # -------------------------
    # Tick parameters / quantization
    # -------------------------

    @property
    def digits(self) -> int:
        """Digits after a decimal point for the symbol prices."""
        return int(self._info.digits) if self._info else 0

    @property
    def point(self) -> float:
        """Symbol point value/The smallest price change for the symbol."""
        return float(self._info.point) if self._info else 0.0

    @property
    def tick_value(self) -> float:
        """The value of a single tick for the symbol."""
        return float(self._info.trade_tick_value) if self._info else 0.0

    @property
    def tick_value_profit(self) -> float:
        """Calculated tick price for a profitable position"""
        return float(self._info.trade_tick_value_profit) if self._info else 0.0

    @property
    def tick_value_loss(self) -> float:
        """Calculated tick price for a losing position"""
        return float(self._info.trade_tick_value_loss) if self._info else 0.0

    @property
    def tick_size(self) -> float:
        """Minimal price change / The size of a single tick for the symbol."""
        return float(self._info.trade_tick_size) if self._info else 0.0

    @property
    def swap_long(self) -> float:
        """Gets the swap for long positions."""
        return float(self._info.swap_long) if self._info else 0.0

    @property
    def swap_short(self) -> float:
        """Gets the swap for short positions."""
        return float(self._info.swap_short) if self._info else 0.0

    # ------------ Contract / lots parameters ------------

    @property
    def contract_size(self) -> float:
        """Trade contract size"""
        return float(self._info.trade_contract_size) if self._info else 0.0

    @property
    def lots_min(self) -> float:
        """Minimum allowed volume of a position in lots."""
        return float(self._info.volume_min) if self._info else 0.0

    @property
    def lots_max(self) -> float:
        """Maximum allowed volume of a position in lots."""
        return float(self._info.volume_max) if self._info else 0.0

    @property
    def lots_step(self) -> float:
        """Minimal volume change step for deal executions."""
        return float(self._info.volume_step) if self._info else 0.0

    @property
    def lots_limit(self) -> float:
        """
            Maximum allowed aggregate volume of an open position and pending orders in one direction (buy or sell) for the symbol. For example, with the limitation of 5 lots, you can have an open buy position with the volume of 5 lots and place a pending order Sell Limit with the volume of 5 lots. But in this case you cannot place a Buy Limit pending order (since the total volume in one direction will exceed the limitation) or place Sell Limit with the volume more than 5 lots.
        """

        return float(self._info.volume_limit) if self._info else 0.0

    # -------------- Currency / text properties ------------

    @property
    def currency_base(self) -> str:
        """Base currency of the symbol."""
        return str(self._info.currency_base) if self._info else ""

    @property
    def currency_profit(self) -> str:
        """Profit currency for the symbol"""
        return str(self._info.currency_profit) if self._info else ""

    @property
    def currency_margin(self) -> str:
        """Margin currency for the symbol"""
        return str(self._info.currency_margin) if self._info else ""

    @property
    def bank(self) -> str:
        """Feeder of the current quote"""
        return str(self._info.bank) if self._info else ""

    @property
    def description(self) -> str:
        """Symbol description"""
        return str(self._info.description) if self._info else ""

    @property
    def path(self) -> str:
        """Path to the symbol tree. For example, "Forex\\EURUSD" or "CFD\\Apple Inc."""
        return str(self._info.path) if self._info else ""

    @property
    def page(self) -> str:
        """
        The address of the web page containing symbol information.

        This address will be displayed as a link when viewing symbol properties in the terminal
        """

        return str(self._info.page) if self._info else ""

    # -------------------------
    # Session properties
    # -------------------------

    @property
    def session_deals(self) -> int:
        """Number of deals in the current session."""
        return int(self._info.session_deals) if self._info else 0

    @property
    def session_buy_orders(self) -> int:
        """Number of buy orders at the moment (in the current session)."""
        return int(self._info.session_buy_orders) if self._info else 0

    @property
    def session_sell_orders(self) -> int:
        """Number of sell orders at the moment (in the current session)."""
        return int(self._info.session_sell_orders) if self._info else 0

    @property
    def session_turnover(self) -> float:
        """Summary turnover of the current session"""
        return float(self._info.session_turnover) if self._info else 0.0

    @property
    def session_interest(self) -> float:
        """Summary open interest"""
        return float(self._info.session_interest) if self._info else 0.0

    @property
    def session_buy_orders_volume(self) -> float:
        """Current volume of Buy orders"""
        return float(self._info.session_buy_orders_volume) if self._info else 0.0

    @property
    def session_sell_orders_volume(self) -> float:
        """Current volume of Sell orders"""
        return float(self._info.session_sell_orders_volume) if self._info else 0.0

    @property
    def session_open(self) -> float:
        """Opening price of the current session"""
        return float(self._info.session_open) if self._info else 0.0

    @property
    def session_close(self) -> float:
        """Closing price of the current session"""
        return float(self._info.session_close) if self._info else 0.0

    @property
    def session_aw(self) -> float:
        """Average weighted price of the current session"""
        return float(self._info.session_aw) if self._info else 0.0

    @property
    def session_price_settlement(self) -> float:
        """Settlement price of the current session"""
        return float(self._info.session_price_settlement) if self._info else 0.0

    @property
    def session_price_limit_min(self) -> float:
        """Minimal price for the current session"""
        return float(self._info.session_price_limit_min) if self._info else 0.0

    @property
    def session_price_limit_max(self) -> float:
        """Maximal price for the current session"""
        return float(self._info.session_price_limit_max) if self._info else 0.0

    # -------------- Generic Info* methods ------------

    def info_integer(self, prop_name: str) -> Optional[int]:
        """
        Gets the value of a specified integer type property.

        Parameters
        ----------
        prop_name : str
            Name of the symbol info attribute.

        Returns
        -------
        Optional[int]
            Integer value if present, otherwise None.
        """
        if self._info is None or not hasattr(self._info, prop_name):
            return None
        value = getattr(self._info, prop_name)
        return None if value is None else int(value)

    def info_double(self, prop_name: str) -> Optional[float]:
        """
        Gets the value of a specified double type property.

        Parameters
        ----------
        prop_name : str
            Name of the symbol info attribute.

        Returns
        -------
        Optional[float]
            Float value if present, otherwise None.
        """
        if self._info is None or not hasattr(self._info, prop_name):
            return None
        value = getattr(self._info, prop_name)

        return None if value is None else float(value)

    def info_string(self, prop_name: str) -> Optional[str]:
        """
        Gets the value of a specified string type property.

        Parameters
        ----------
        prop_name : str
            Name of the symbol info attribute.

        Returns
        -------
        Optional[str]
            String value if present, otherwise None.
        """
        if self._info is None or not hasattr(self._info, prop_name):
            return None
        value = getattr(self._info, prop_name)
        return None if value is None else str(value)

    # --------------Service functions --------------

    def normalize_price(self, price: float) -> float:
        """
        Returns the value of price normalized using the symbol properties.

        Parameters
        ----------
        price : float
            Price to normalize.

        Returns
        -------
        float
            Normalized price based on symbol digits.
        """
        return round(float(price), self.digits)

    # ----------- Debug / utility helpers ---------------

    def to_dict(self) -> dict[str, Any]:
        """Return all @property values as a dictionary."""
        data: dict[str, Any] = {}
        seen: set[str] = set()

        for cls in type(self).mro():
            for name, attr in vars(cls).items():
                if name in seen:
                    continue
                if isinstance(attr, property):
                    seen.add(name)
                    try:
                        data[name] = getattr(self, name)
                    except Exception as e:
                        data[name] = f"<error: {e}>"

        return data

    def print_all(self) -> None:
        """Print all @property values of the class."""
        for name, value in self.to_dict().items():
            print(f"{name:28} : {value}")

    def __repr__(self) -> str:
        props = self.to_dict()
        props_str = ", ".join(f"{k}={v!r}" for k, v in props.items())
        return f"{self.__class__.__name__}({props_str})"

ask property

Returns the current ask price.

ask_high property

Gets the maximal Ask price for a day.

ask_low property

Gets the minimal Ask price for a day.

bank property

Feeder of the current quote

bid property

Returns the current bid price.

bid_high property

Gets the maximal Bid price for a day.

bid_low property

Gets the minimal Bid price for a day.

contract_size property

Trade contract size

currency_base property

Base currency of the symbol.

currency_margin property

Margin currency for the symbol

currency_profit property

Profit currency for the symbol

description property

Symbol description

digits property

Digits after a decimal point for the symbol prices.

expiration_time property

Date of the symbol trade end (usually used for futures)

filling_mode property

Flags of allowed order filling modes for the symbol (bit field).

freeze_level property

Gets the freezing distance for trade operations in points.

is_synchronized property

Checks the symbol synchronization with server.

last property

Gets the current Last price.

last_high property

Gets the maximal Last price for a day.

last_low property

Gets the minimal Last price for a day.

lots_limit property

Maximum allowed aggregate volume of an open position and pending orders in one direction (buy or sell) for the symbol. For example, with the limitation of 5 lots, you can have an open buy position with the volume of 5 lots and place a pending order Sell Limit with the volume of 5 lots. But in this case you cannot place a Buy Limit pending order (since the total volume in one direction will exceed the limitation) or place Sell Limit with the volume more than 5 lots.

lots_max property

Maximum allowed volume of a position in lots.

lots_min property

Minimum allowed volume of a position in lots.

lots_step property

Minimal volume change step for deal executions.

margin_hedged property

Contract size or margin value per one lot of hedged positions (oppositely directed positions of one symbol). Two margin calculation methods are possible for hedged positions. The calculation method is defined by the broker.

Basic calculation:
  • If the initial margin (margin_initial) is specified for a symbol, the hedged margin is specified as an absolute value (in monetary terms).
  • If the initial margin is not specified (equal to 0), SYMBOL_MARGIN_HEDGED is equal to the size of the contract, that will be used to calculate the margin by the appropriate formula in accordance with the type of the financial instrument (trade_calc_mode).
Calculation for the largest position:
  • The SYMBOL_MARGIN_HEDGED value is not taken into account.
  • The volume of all short and all long positions of a symbol is calculated.
  • For each direction, a weighted average open price and a weighted average rate of conversion to the deposit currency is calculated.
  • Next, using the appropriate formula chosen in accordance with the symbol type (trade_calc_mode) the margin is calculated for the short and the long part.
  • The largest one of the values is used as the margin.

margin_hedged_use_leg property

Boolean flag to enable (true) or disable (false) the hedged margin calculation mode for the largest of the overlapped positions (buy and sell positions of the same symbol).

If the flag is enabled, the margin for the largest position is calculated using the SYMBOL_MARGIN_HEDGED value. If the flag is disabled, the margin for the largest position is calculated using the SYMBOL_MARGIN_INITIAL value.

margin_initial property

Initial margin means the amount in the margin currency required for opening a position with the volume of one lot. It is used for checking a client's assets when he or she enters the market.

The SymbolInfoMarginRate() function provides data on the amount of charged margin depending on the order type and direction.

margin_maintenance property

The maintenance margin. If it is set, it sets the margin amount in the margin currency of the symbol, charged from one lot. It is used for checking a client's assets when his/her account state changes. If the maintenance margin is equal to 0, the initial margin is used.

The SymbolInfoMarginRate() function provides data on the amount of charged margin depending on the order type and direction.

order_mode property

Flags of allowed order types for the symbol (bit field).

page property

The address of the web page containing symbol information.

This address will be displayed as a link when viewing symbol properties in the terminal

path property

Path to the symbol tree. For example, "Forex\EURUSD" or "CFD\Apple Inc.

point property

Symbol point value/The smallest price change for the symbol.

session_aw property

Average weighted price of the current session

session_buy_orders property

Number of buy orders at the moment (in the current session).

session_buy_orders_volume property

Current volume of Buy orders

session_close property

Closing price of the current session

session_deals property

Number of deals in the current session.

session_interest property

Summary open interest

session_open property

Opening price of the current session

session_price_limit_max property

Maximal price for the current session

session_price_limit_min property

Minimal price for the current session

session_price_settlement property

Settlement price of the current session

session_sell_orders property

Number of sell orders at the moment (in the current session).

session_sell_orders_volume property

Current volume of Sell orders

session_turnover property

Summary turnover of the current session

spread property

Gets the amount of spread in points.

spread_float property

Gets the floating spread flag.

start_time property

Date of the symbol trade beginning (usually used for futures)

stops_level property

Gets the minimal indent for orders in points.

swap_long property

Gets the swap for long positions.

swap_mode property

A method of swap calculation at position transfer.

swap_mode_description property

A description of methods of swap calculation at position transfer.

swap_rollover_3days property

The day of week to charge 3-day swap rollover

swap_rollover_3days_description property

A description (string) of the day of week to charge 3-day swap rollover

swap_short property

Gets the swap for short positions.

symbol property

Gets the current symbol name.

tick_size property

Minimal price change / The size of a single tick for the symbol.

tick_value property

The value of a single tick for the symbol.

tick_value_loss property

Calculated tick price for a losing position

tick_value_profit property

Calculated tick price for a profitable position

ticks_book_depth property

Gets the depth of ticks saving.

time property

Gets the time of the last quote as UTC datetime.

time_msc property

Gets the time of the last quote in milliseconds.

trade_calc_mode property

Contract price calculation mode

trade_calc_mode_description property

A description of contract price calculation modes.

trade_execution property

Possible deal execution modes for a given symbol.

trade_execution_description property

A description of possible deal execution modes for a given symbol.

trade_mode property

Symbol trading modes (restrictions)

trade_mode_description property

A description of symbol trading modes (restrictions)

volume property

Gets the volume of the last deal.

volume_high property

Gets the maximal volume for a day.

volume_low property

Gets the minimal volume for a day.

volume_real property

Gets the real volume of the last deal.

__init__(symbol, terminal)

A lightweight Python wrapper that resembles the MQL5 Standard Library class CSymbolInfo and provides convenient, read-only access to MetaTrader 5 symbol properties.

This class stores a symbol name, cached symbol information, and cached tick information. Symbol data can be refreshed via refresh(), while quote/tick data can be refreshed via refresh_rates().

MQL5 Reference

Parameters

terminal : Initialize native MetaTrader5 API or the simulated one from the StrategyTester instance

Notes
  • name(symbol_name) assigns the symbol and refreshes its static info.
  • refresh_rates() updates the cached tick/quote values.
  • If tick data is not refreshed yet, tick-related properties return their default cached values.
  • Time values are returned as timezone-aware UTC datetimes where applicable.
Source code in strategytester5\trade_classes\SymbolInfo.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(self, symbol: str, terminal: Union[OverLoadedMetaTrader5API|MetaTrader5]) -> None:

    """
    A lightweight Python wrapper that resembles the MQL5 Standard Library class
    `CSymbolInfo` and provides convenient, read-only access to MetaTrader 5
    symbol properties.

    This class stores a symbol name, cached symbol information, and cached tick
    information. Symbol data can be refreshed via `refresh()`, while quote/tick
    data can be refreshed via `refresh_rates()`.

    [MQL5 Reference](https://www.mql5.com/en/docs/standardlibrary/tradeclasses/csymbolinfo)

    Parameters
    ----------
    terminal : Initialize native MetaTrader5 API or the simulated one from the StrategyTester instance

    Notes
    -----
    - `name(symbol_name)` assigns the symbol and refreshes its static info.
    - `refresh_rates()` updates the cached tick/quote values.
    - If tick data is not refreshed yet, tick-related properties return their default cached values.
    - Time values are returned as timezone-aware UTC datetimes where applicable.
    """

    self.terminal = terminal

    self._symbol: str = symbol
    self._info: Optional[Any] = None

    self._tick_info: dict[str, Any] = {
        "time": None,
        "ask": 0.0,
        "bid": 0.0,
        "last": 0.0,
        "volume": 0,
        "time_msc": 0,
        "volume_real": 0.0,
    }   

    self.check_market_watch()
    self.refresh()

check_market_watch()

Checks if the symbol is selected in Market Watch and adds it to the Market Watch if necessary.

Source code in strategytester5\trade_classes\SymbolInfo.py
126
127
128
129
130
131
132
133
134
135
def check_market_watch(self) -> bool:
    """Checks if the symbol is selected in Market Watch and adds it to the Market Watch if necessary."""

    if not self.select():
        raise ValueError(f"Failed to select a symbol {self._symbol}")

    if not self.select(True):
        raise ValueError(f"Failed to add a symbol {self._symbol} in Market Watch")

    return True

info_double(prop_name)

Gets the value of a specified double type property.

Parameters

prop_name : str Name of the symbol info attribute.

Returns

Optional[float] Float value if present, otherwise None.

Source code in strategytester5\trade_classes\SymbolInfo.py
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
def info_double(self, prop_name: str) -> Optional[float]:
    """
    Gets the value of a specified double type property.

    Parameters
    ----------
    prop_name : str
        Name of the symbol info attribute.

    Returns
    -------
    Optional[float]
        Float value if present, otherwise None.
    """
    if self._info is None or not hasattr(self._info, prop_name):
        return None
    value = getattr(self._info, prop_name)

    return None if value is None else float(value)

info_integer(prop_name)

Gets the value of a specified integer type property.

Parameters

prop_name : str Name of the symbol info attribute.

Returns

Optional[int] Integer value if present, otherwise None.

Source code in strategytester5\trade_classes\SymbolInfo.py
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
def info_integer(self, prop_name: str) -> Optional[int]:
    """
    Gets the value of a specified integer type property.

    Parameters
    ----------
    prop_name : str
        Name of the symbol info attribute.

    Returns
    -------
    Optional[int]
        Integer value if present, otherwise None.
    """
    if self._info is None or not hasattr(self._info, prop_name):
        return None
    value = getattr(self._info, prop_name)
    return None if value is None else int(value)

info_string(prop_name)

Gets the value of a specified string type property.

Parameters

prop_name : str Name of the symbol info attribute.

Returns

Optional[str] String value if present, otherwise None.

Source code in strategytester5\trade_classes\SymbolInfo.py
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
def info_string(self, prop_name: str) -> Optional[str]:
    """
    Gets the value of a specified string type property.

    Parameters
    ----------
    prop_name : str
        Name of the symbol info attribute.

    Returns
    -------
    Optional[str]
        String value if present, otherwise None.
    """
    if self._info is None or not hasattr(self._info, prop_name):
        return None
    value = getattr(self._info, prop_name)
    return None if value is None else str(value)

normalize_price(price)

Returns the value of price normalized using the symbol properties.

Parameters

price : float Price to normalize.

Returns

float Normalized price based on symbol digits.

Source code in strategytester5\trade_classes\SymbolInfo.py
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
def normalize_price(self, price: float) -> float:
    """
    Returns the value of price normalized using the symbol properties.

    Parameters
    ----------
    price : float
        Price to normalize.

    Returns
    -------
    float
        Normalized price based on symbol digits.
    """
    return round(float(price), self.digits)

print_all()

Print all @property values of the class.

Source code in strategytester5\trade_classes\SymbolInfo.py
727
728
729
730
def print_all(self) -> None:
    """Print all @property values of the class."""
    for name, value in self.to_dict().items():
        print(f"{name:28} : {value}")

refresh()

Refreshes the symbol data by fetching symbol information from the terminal.

Returns

bool True if successful.

Raises

RuntimeError If symbol information could not be retrieved.

Source code in strategytester5\trade_classes\SymbolInfo.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def refresh(self) -> bool:
    """
    Refreshes the symbol data by fetching symbol information from the terminal.

    Returns
    -------
    bool
        True if successful.

    Raises
    ------
    RuntimeError
        If symbol information could not be retrieved.
    """
    info = self.terminal.symbol_info(self._symbol)
    if not info:
        code, msg = self.terminal.last_error()
        raise RuntimeError(f"Failed to get symbol info for {self._symbol}. MetaTrader5 error = [{code}] {msg}")

    self._info = info
    return True

refresh_rates()

Refreshes the symbol quotes/gets the latest tick information from the market using symbol_info_tick(symbol).

Returns

bool True if successful, False otherwise.

Source code in strategytester5\trade_classes\SymbolInfo.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def refresh_rates(self) -> bool:
    """
    Refreshes the symbol quotes/gets the latest tick information from the market using `symbol_info_tick(symbol)`.

    Returns
    -------
    bool
        True if successful, False otherwise.
    """
    try:
        new_ticks = self.terminal.symbol_info_tick(self._symbol)
        if new_ticks is None:
            return False

        self._tick_info["time"] = new_ticks.time
        self._tick_info["ask"] = new_ticks.ask
        self._tick_info["bid"] = new_ticks.bid
        self._tick_info["last"] = new_ticks.last
        self._tick_info["volume"] = new_ticks.volume
        self._tick_info["time_msc"] = new_ticks.time_msc
        self._tick_info["volume_real"] = new_ticks.volume_real

        return True

    except AttributeError:
        return False

select(select=True)

Gets/sets the Market Watch symbol flag.

Parameters

select : bool, optional True to select the symbol in Market Watch, False to deselect.

Returns

bool True if successful, otherwise False.

Source code in strategytester5\trade_classes\SymbolInfo.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def select(self, select: bool = True) -> bool:
    """
    Gets/sets the Market Watch symbol flag.

    Parameters
    ----------
    select : bool, optional
        True to select the symbol in Market Watch, False to deselect.

    Returns
    -------
    bool
        True if successful, otherwise False.
    """
    return self.terminal.symbol_select(self._symbol, select)

to_dict()

Return all @property values as a dictionary.

Source code in strategytester5\trade_classes\SymbolInfo.py
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
def to_dict(self) -> dict[str, Any]:
    """Return all @property values as a dictionary."""
    data: dict[str, Any] = {}
    seen: set[str] = set()

    for cls in type(self).mro():
        for name, attr in vars(cls).items():
            if name in seen:
                continue
            if isinstance(attr, property):
                seen.add(name)
                try:
                    data[name] = getattr(self, name)
                except Exception as e:
                    data[name] = f"<error: {e}>"

    return data