| @app.command()
-def account_info(ctx: typer.Context) -> None:
- """Export account information."""
- _execute_export(ctx, _sdk_client(ctx).account_info)
+ | @app.command()
+def account_info(ctx: typer.Context) -> None:
+ """Export account information."""
+ _execute_export(ctx, _sdk_client(ctx).account_info)
|
@@ -360,55 +360,7 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- 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
+ | @app.command()
-def collect_history(
- ctx: typer.Context,
- symbol: Annotated[
- list[str],
- typer.Option(
- "--symbol",
- "-s",
- help="Symbol to collect (repeat for multiple symbols).",
- ),
- ],
- date_from: Annotated[
- datetime,
- typer.Option(click_type=DATETIME_TYPE, help="Start date."),
- ],
- date_to: Annotated[
- datetime,
- typer.Option(click_type=DATETIME_TYPE, help="End date."),
- ],
- dataset: Annotated[
- list[Dataset] | None,
- typer.Option(
- "--dataset",
- help=(
- "Dataset to include (repeat for multiple)."
- " Defaults to all: rates, ticks, history-orders, history-deals."
- ),
- ),
- ] = None,
- timeframe: Annotated[
- int,
- typer.Option(
- click_type=TIMEFRAME_TYPE,
- help="Rates timeframe (e.g., M1, H1, D1).",
- ),
- ] = 1,
- flags: Annotated[
- int,
- typer.Option(
- click_type=TICK_FLAGS_TYPE,
- help="Tick copy flags (ALL, INFO, TRADE, or integer).",
- ),
- ] = 1,
- if_exists: Annotated[
- IfExists,
- typer.Option(
- "--if-exists",
- help="Behavior when a target table already exists.",
- ),
- ] = IfExists.FAIL,
- with_views: Annotated[
- bool,
- typer.Option(
- "--with-views",
- help=(
- "Add cash_events and positions_reconstructed SQLite views"
- " derived from history_deals."
- ),
- ),
- ] = False,
-) -> None:
- """Collect historical datasets into a single SQLite database.
-
- Tables written depend on ``--dataset``: ``rates``, ``ticks``,
- ``history_orders``, ``history_deals``. History datasets are fetched per
- symbol and concatenated. Rates rows carry the requested ``timeframe`` so
- appended runs at different timeframes remain distinguishable.
-
- With ``--with-views`` (requires the ``history-deals`` dataset), optional
- views ``cash_events`` and ``positions_reconstructed`` are derived from
- ``history_deals`` when the required columns are present.
-
- Raises:
- typer.BadParameter: If the output format is not SQLite3.
- """
- export_ctx = _get_export_context(ctx)
- if export_ctx.output_format != "sqlite3":
- msg = (
- "collect-history requires SQLite3 output."
- " Use a .db/.sqlite/.sqlite3 extension or --format sqlite3."
- )
- raise typer.BadParameter(msg)
- datasets = set(dataset) if dataset else set(Dataset)
- sdk.collect_history(
- output=export_ctx.output,
- symbols=symbol,
- date_from=date_from,
- date_to=date_to,
- datasets=datasets,
- timeframe=timeframe,
- flags=flags,
- if_exists=if_exists,
- with_views=with_views,
- config=export_ctx.config,
- )
+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
| @app.command()
+def collect_history(
+ ctx: typer.Context,
+ symbol: Annotated[
+ list[str],
+ typer.Option(
+ "--symbol",
+ "-s",
+ help="Symbol to collect (repeat for multiple symbols).",
+ ),
+ ],
+ date_from: Annotated[
+ datetime,
+ typer.Option(click_type=DATETIME_TYPE, help="Start date."),
+ ],
+ date_to: Annotated[
+ datetime,
+ typer.Option(click_type=DATETIME_TYPE, help="End date."),
+ ],
+ dataset: Annotated[
+ list[Dataset] | None,
+ typer.Option(
+ "--dataset",
+ help=(
+ "Dataset to include (repeat for multiple)."
+ " Defaults to all: rates, ticks, history-orders, history-deals."
+ ),
+ ),
+ ] = None,
+ timeframe: Annotated[
+ int,
+ typer.Option(
+ click_type=TIMEFRAME_TYPE,
+ help="Rates timeframe (e.g., M1, H1, D1).",
+ ),
+ ] = 1,
+ flags: Annotated[
+ int,
+ typer.Option(
+ click_type=TICK_FLAGS_TYPE,
+ help="Tick copy flags (ALL, INFO, TRADE, or integer).",
+ ),
+ ] = 1,
+ if_exists: Annotated[
+ IfExists,
+ typer.Option(
+ "--if-exists",
+ help="Behavior when a target table already exists.",
+ ),
+ ] = IfExists.FAIL,
+ with_views: Annotated[
+ bool,
+ typer.Option(
+ "--with-views",
+ help=(
+ "Add cash_events and positions_reconstructed SQLite views"
+ " derived from history_deals."
+ ),
+ ),
+ ] = False,
+) -> None:
+ """Collect historical datasets into a single SQLite database.
+
+ Tables written depend on ``--dataset``: ``rates``, ``ticks``,
+ ``history_orders``, ``history_deals``. History datasets are fetched per
+ symbol and concatenated. Rates rows carry the requested ``timeframe`` so
+ appended runs at different timeframes remain distinguishable.
+
+ With ``--with-views`` (requires the ``history-deals`` dataset), optional
+ views ``cash_events`` and ``positions_reconstructed`` are derived from
+ ``history_deals`` when the required columns are present.
+
+ Raises:
+ typer.BadParameter: If the output format is not SQLite3.
+ """
+ export_ctx = _get_export_context(ctx)
+ if export_ctx.output_format != "sqlite3":
+ msg = (
+ "collect-history requires SQLite3 output."
+ " Use a .db/.sqlite/.sqlite3 extension or --format sqlite3."
+ )
+ raise typer.BadParameter(msg)
+ datasets = set(dataset) if dataset else set(Dataset)
+ sdk.collect_history(
+ output=export_ctx.output,
+ symbols=symbol,
+ date_from=date_from,
+ date_to=date_to,
+ datasets=datasets,
+ timeframe=timeframe,
+ flags=flags,
+ if_exists=if_exists,
+ with_views=with_views,
+ config=export_ctx.config,
+ )
|
@@ -607,63 +607,63 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def history_deals(
- ctx: typer.Context,
- date_from: Annotated[
- datetime | None,
- typer.Option(click_type=DATETIME_TYPE, help="Start date."),
- ] = None,
- date_to: Annotated[
- datetime | None,
- typer.Option(click_type=DATETIME_TYPE, help="End date."),
- ] = None,
- group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
- symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
- ticket: Annotated[int | None, typer.Option(help="Order ticket.")] = None,
- position: Annotated[int | None, typer.Option(help="Position ticket.")] = None,
-) -> None:
- """Export historical deals."""
- client = _sdk_client(ctx)
- _execute_export(
- ctx,
- lambda: client.history_deals(
- date_from=date_from,
- date_to=date_to,
- group=group,
- symbol=symbol,
- ticket=ticket,
- position=position,
- ),
- )
+ | @app.command()
+def history_deals(
+ ctx: typer.Context,
+ date_from: Annotated[
+ datetime | None,
+ typer.Option(click_type=DATETIME_TYPE, help="Start date."),
+ ] = None,
+ date_to: Annotated[
+ datetime | None,
+ typer.Option(click_type=DATETIME_TYPE, help="End date."),
+ ] = None,
+ group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
+ symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
+ ticket: Annotated[int | None, typer.Option(help="Order ticket.")] = None,
+ position: Annotated[int | None, typer.Option(help="Position ticket.")] = None,
+) -> None:
+ """Export historical deals."""
+ client = _sdk_client(ctx)
+ _execute_export(
+ ctx,
+ lambda: client.history_deals(
+ date_from=date_from,
+ date_to=date_to,
+ group=group,
+ symbol=symbol,
+ ticket=ticket,
+ position=position,
+ ),
+ )
|
@@ -722,63 +722,63 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def history_orders(
- ctx: typer.Context,
- date_from: Annotated[
- datetime | None,
- typer.Option(click_type=DATETIME_TYPE, help="Start date."),
- ] = None,
- date_to: Annotated[
- datetime | None,
- typer.Option(click_type=DATETIME_TYPE, help="End date."),
- ] = None,
- group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
- symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
- ticket: Annotated[int | None, typer.Option(help="Order ticket.")] = None,
- position: Annotated[int | None, typer.Option(help="Position ticket.")] = None,
-) -> None:
- """Export historical orders."""
- client = _sdk_client(ctx)
- _execute_export(
- ctx,
- lambda: client.history_orders(
- date_from=date_from,
- date_to=date_to,
- group=group,
- symbol=symbol,
- ticket=ticket,
- position=position,
- ),
- )
+ | @app.command()
+def history_orders(
+ ctx: typer.Context,
+ date_from: Annotated[
+ datetime | None,
+ typer.Option(click_type=DATETIME_TYPE, help="Start date."),
+ ] = None,
+ date_to: Annotated[
+ datetime | None,
+ typer.Option(click_type=DATETIME_TYPE, help="End date."),
+ ] = None,
+ group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
+ symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
+ ticket: Annotated[int | None, typer.Option(help="Order ticket.")] = None,
+ position: Annotated[int | None, typer.Option(help="Position ticket.")] = None,
+) -> None:
+ """Export historical orders."""
+ client = _sdk_client(ctx)
+ _execute_export(
+ ctx,
+ lambda: client.history_orders(
+ date_from=date_from,
+ date_to=date_to,
+ group=group,
+ symbol=symbol,
+ ticket=ticket,
+ position=position,
+ ),
+ )
|
@@ -803,13 +803,13 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def last_error(ctx: typer.Context) -> None:
- """Export the last error information."""
- _execute_export(ctx, _sdk_client(ctx).last_error)
+ | @app.command()
+def last_error(ctx: typer.Context) -> None:
+ """Export the last error information."""
+ _execute_export(ctx, _sdk_client(ctx).last_error)
|
@@ -834,11 +834,11 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | def main() -> None:
- """Run the mt5cli CLI."""
- app()
+ | def main() -> None:
+ """Run the mt5cli CLI."""
+ app()
|
@@ -866,21 +866,63 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def market_book(
- ctx: typer.Context,
- symbol: Annotated[str, typer.Option(help="Symbol name.")],
-) -> None:
- """Export market depth (order book) for a symbol."""
- client = _sdk_client(ctx)
- _execute_export(ctx, lambda: client.market_book(symbol))
+ | @app.command()
+def market_book(
+ ctx: typer.Context,
+ symbol: Annotated[str, typer.Option(help="Symbol name.")],
+) -> None:
+ """Export market depth (order book) for a symbol."""
+ client = _sdk_client(ctx)
+ _execute_export(ctx, lambda: client.market_book(symbol))
+
|
+
+
+
+
+
+
+
+
+
+ minimum_margins
+
+
+
+ minimum_margins(
+ ctx: Context,
+ symbol: Annotated[str, Option(help="Symbol name.")],
+) -> None
+
+
+
+
+ Export minimum-volume buy and sell margin requirements.
+
+
+
+ Source code in mt5cli/cli.py
+ | @app.command()
+def minimum_margins(
+ ctx: typer.Context,
+ symbol: Annotated[str, typer.Option(help="Symbol name.")],
+) -> None:
+ """Export minimum-volume buy and sell margin requirements."""
+ client = _sdk_client(ctx)
+ _execute_export(ctx, lambda: client.minimum_margins(symbol))
|
@@ -919,41 +961,41 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def order_check(
- ctx: typer.Context,
- request: Annotated[
- dict[str, Any],
- typer.Option(click_type=REQUEST_TYPE, help=_REQUEST_OPTION_HELP),
- ],
-) -> None:
- """Check funds sufficiency for a trading operation."""
- export_ctx = _get_export_context(ctx)
-
- def _fetch() -> pd.DataFrame:
- return sdk._run_with_client( # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
- export_ctx.config,
- lambda c: c.order_check_as_df(request=request),
- )
-
- _execute_export(ctx, _fetch)
+ | @app.command()
+def order_check(
+ ctx: typer.Context,
+ request: Annotated[
+ dict[str, Any],
+ typer.Option(click_type=REQUEST_TYPE, help=_REQUEST_OPTION_HELP),
+ ],
+) -> None:
+ """Check funds sufficiency for a trading operation."""
+ export_ctx = _get_export_context(ctx)
+
+ def _fetch() -> pd.DataFrame:
+ return sdk._run_with_client( # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
+ export_ctx.config,
+ lambda c: c.order_check_as_df(request=request),
+ )
+
+ _execute_export(ctx, _fetch)
|
@@ -1021,63 +1063,63 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def order_send(
- ctx: typer.Context,
- request: Annotated[
- dict[str, Any],
- typer.Option(click_type=REQUEST_TYPE, help=_REQUEST_OPTION_HELP),
- ],
- yes: Annotated[
- bool,
- typer.Option("--yes", help="Confirm the live trade request."),
- ] = False,
-) -> None:
- """Send a trading operation request to the trade server.
-
- Raises:
- typer.BadParameter: If --yes is not provided.
- """
- if not yes:
- msg = "Pass --yes to send a live trade request."
- raise typer.BadParameter(msg, param_hint="--yes")
- export_ctx = _get_export_context(ctx)
-
- def _fetch() -> pd.DataFrame:
- return sdk._run_with_client( # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
- export_ctx.config,
- lambda c: c.order_send_as_df(request=request),
- )
-
- _execute_export(ctx, _fetch)
+ | @app.command()
+def order_send(
+ ctx: typer.Context,
+ request: Annotated[
+ dict[str, Any],
+ typer.Option(click_type=REQUEST_TYPE, help=_REQUEST_OPTION_HELP),
+ ],
+ yes: Annotated[
+ bool,
+ typer.Option("--yes", help="Confirm the live trade request."),
+ ] = False,
+) -> None:
+ """Send a trading operation request to the trade server.
+
+ Raises:
+ typer.BadParameter: If --yes is not provided.
+ """
+ if not yes:
+ msg = "Pass --yes to send a live trade request."
+ raise typer.BadParameter(msg, param_hint="--yes")
+ export_ctx = _get_export_context(ctx)
+
+ def _fetch() -> pd.DataFrame:
+ return sdk._run_with_client( # noqa: SLF001 # pyright: ignore[reportPrivateUsage]
+ export_ctx.config,
+ lambda c: c.order_send_as_df(request=request),
+ )
+
+ _execute_export(ctx, _fetch)
|
@@ -1113,31 +1155,31 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def orders(
- ctx: typer.Context,
- symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
- group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
- ticket: Annotated[int | None, typer.Option(help="Ticket filter.")] = None,
-) -> None:
- """Export active orders."""
- client = _sdk_client(ctx)
- _execute_export(
- ctx,
- lambda: client.orders(symbol=symbol, group=group, ticket=ticket),
- )
+ | @app.command()
+def orders(
+ ctx: typer.Context,
+ symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
+ group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
+ ticket: Annotated[int | None, typer.Option(help="Ticket filter.")] = None,
+) -> None:
+ """Export active orders."""
+ client = _sdk_client(ctx)
+ _execute_export(
+ ctx,
+ lambda: client.orders(symbol=symbol, group=group, ticket=ticket),
+ )
|
@@ -1173,31 +1215,31 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def positions(
- ctx: typer.Context,
- symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
- group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
- ticket: Annotated[int | None, typer.Option(help="Ticket filter.")] = None,
-) -> None:
- """Export open positions."""
- client = _sdk_client(ctx)
- _execute_export(
- ctx,
- lambda: client.positions(symbol=symbol, group=group, ticket=ticket),
- )
+ | @app.command()
+def positions(
+ ctx: typer.Context,
+ symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
+ group: Annotated[str | None, typer.Option(help="Group filter.")] = None,
+ ticket: Annotated[int | None, typer.Option(help="Ticket filter.")] = None,
+) -> None:
+ """Export open positions."""
+ client = _sdk_client(ctx)
+ _execute_export(
+ ctx,
+ lambda: client.positions(symbol=symbol, group=group, ticket=ticket),
+ )
|
@@ -1523,21 +1565,21 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def symbol_info(
- ctx: typer.Context,
- symbol: Annotated[str, typer.Option(help="Symbol name.")],
-) -> None:
- """Export symbol details."""
- client = _sdk_client(ctx)
- _execute_export(ctx, lambda: client.symbol_info(symbol))
+ | @app.command()
+def symbol_info(
+ ctx: typer.Context,
+ symbol: Annotated[str, typer.Option(help="Symbol name.")],
+) -> None:
+ """Export symbol details."""
+ client = _sdk_client(ctx)
+ _execute_export(ctx, lambda: client.symbol_info(symbol))
|
@@ -1565,21 +1607,21 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def symbol_info_tick(
- ctx: typer.Context,
- symbol: Annotated[str, typer.Option(help="Symbol name.")],
-) -> None:
- """Export the last tick for a symbol."""
- client = _sdk_client(ctx)
- _execute_export(ctx, lambda: client.symbol_info_tick(symbol))
+ | @app.command()
+def symbol_info_tick(
+ ctx: typer.Context,
+ symbol: Annotated[str, typer.Option(help="Symbol name.")],
+) -> None:
+ """Export the last tick for a symbol."""
+ client = _sdk_client(ctx)
+ _execute_export(ctx, lambda: client.symbol_info_tick(symbol))
|
@@ -1610,27 +1652,27 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def symbols(
- ctx: typer.Context,
- group: Annotated[
- str | None,
- typer.Option(help="Symbol group filter (e.g., *USD*)."),
- ] = None,
-) -> None:
- """Export symbol list."""
- client = _sdk_client(ctx)
- _execute_export(ctx, lambda: client.symbols(group=group))
+ | @app.command()
+def symbols(
+ ctx: typer.Context,
+ group: Annotated[
+ str | None,
+ typer.Option(help="Symbol group filter (e.g., *USD*)."),
+ ] = None,
+) -> None:
+ """Export symbol list."""
+ client = _sdk_client(ctx)
+ _execute_export(ctx, lambda: client.symbols(group=group))
|
@@ -1655,13 +1697,13 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def terminal_info(ctx: typer.Context) -> None:
- """Export terminal information."""
- _execute_export(ctx, _sdk_client(ctx).terminal_info)
+ | @app.command()
+def terminal_info(ctx: typer.Context) -> None:
+ """Export terminal information."""
+ _execute_export(ctx, _sdk_client(ctx).terminal_info)
|
@@ -1870,6 +1912,135 @@ views cash_events and positions_reconstructed are deri
+
+ ticks_recent
+
+
+
+ ticks_recent(
+ ctx: Context,
+ symbol: Annotated[str, Option(help="Symbol name.")],
+ seconds: Annotated[
+ float, Option(help="Lookback window in seconds.")
+ ],
+ date_to: Annotated[
+ datetime | None,
+ Option(
+ click_type=DATETIME_TYPE,
+ help="Window end date.",
+ ),
+ ] = None,
+ count: Annotated[
+ int,
+ Option(help="Maximum number of ticks to return."),
+ ] = 10000,
+ flags: Annotated[
+ int,
+ Option(
+ click_type=TICK_FLAGS_TYPE,
+ help="Tick flags (ALL, INFO, TRADE, or integer).",
+ ),
+ ] = 1,
+) -> None
+
+
+
+
+ Export ticks from a recent time window.
+
+
+
+ Source code in mt5cli/cli.py
+ | @app.command()
+def ticks_recent(
+ ctx: typer.Context,
+ symbol: Annotated[str, typer.Option(help="Symbol name.")],
+ seconds: Annotated[
+ float,
+ typer.Option(help="Lookback window in seconds."),
+ ],
+ date_to: Annotated[
+ datetime | None,
+ typer.Option(click_type=DATETIME_TYPE, help="Window end date."),
+ ] = None,
+ count: Annotated[
+ int,
+ typer.Option(help="Maximum number of ticks to return."),
+ ] = 10000,
+ flags: Annotated[
+ int,
+ typer.Option(
+ click_type=TICK_FLAGS_TYPE,
+ help="Tick flags (ALL, INFO, TRADE, or integer).",
+ ),
+ ] = 1,
+) -> None:
+ """Export ticks from a recent time window."""
+ client = _sdk_client(ctx)
+ _execute_export(
+ ctx,
+ lambda: client.recent_ticks(
+ symbol,
+ seconds,
+ date_to=date_to,
+ count=count,
+ flags=flags,
+ ),
+ )
+
|
+
+
+
+
+
+
+
+
version
@@ -1885,13 +2056,13 @@ views cash_events and positions_reconstructed are deri
Source code in mt5cli/cli.py
- | @app.command()
-def version(ctx: typer.Context) -> None:
- """Export MetaTrader5 version information."""
- _execute_export(ctx, _sdk_client(ctx).version)
+ | @app.command()
+def version(ctx: typer.Context) -> None:
+ """Export MetaTrader5 version information."""
+ _execute_export(ctx, _sdk_client(ctx).version)
|
diff --git a/api/history/index.html b/api/history/index.html
index ca7455d..4e09317 100644
--- a/api/history/index.html
+++ b/api/history/index.html
@@ -209,6 +209,28 @@
+
+ SqliteConnOrPath
+
+
+
+ module-attribute
+
+
+
+SqliteConnOrPath = Connection | Path | str
+
+
+
+
+
+
+
+
+
+
+
+
logger
@@ -276,49 +298,49 @@
Source code in mt5cli/history.py
- | def append_dataframe(
- conn: sqlite3.Connection,
- frame: pd.DataFrame,
- table_name: str,
- if_exists: IfExists,
-) -> bool:
- """Append a DataFrame to SQLite when it has a schema.
-
- Returns:
- True if a table was written, False if the frame had no columns.
- """
- if len(frame.columns) == 0:
- logger.warning("Skipping %s: dataset returned no columns", table_name)
- return False
- frame.to_sql( # type: ignore[reportUnknownMemberType]
- table_name,
- conn,
- if_exists=if_exists.value,
- index=False,
- chunksize=50_000,
- )
- return True
+ | def append_dataframe(
+ conn: sqlite3.Connection,
+ frame: pd.DataFrame,
+ table_name: str,
+ if_exists: IfExists,
+) -> bool:
+ """Append a DataFrame to SQLite when it has a schema.
+
+ Returns:
+ True if a table was written, False if the frame had no columns.
+ """
+ if len(frame.columns) == 0:
+ logger.warning("Skipping %s: dataset returned no columns", table_name)
+ return False
+ frame.to_sql( # type: ignore[reportUnknownMemberType]
+ table_name,
+ conn,
+ if_exists=if_exists.value,
+ index=False,
+ chunksize=50_000,
+ )
+ return True
|
@@ -347,33 +369,33 @@
Source code in mt5cli/history.py
- | def augment_written_columns_from_sqlite(
- conn: sqlite3.Connection,
- datasets: set[Dataset],
- written_columns: dict[Dataset, set[str]],
-) -> None:
- """Add existing table columns to the written column map."""
- for dataset in datasets:
- columns = get_table_columns(conn, dataset.table_name)
- if not columns:
- continue
- if dataset in written_columns:
- written_columns[dataset].update(columns)
- else:
- written_columns[dataset] = columns
+ | def augment_written_columns_from_sqlite(
+ conn: sqlite3.Connection,
+ datasets: set[Dataset],
+ written_columns: dict[Dataset, set[str]],
+) -> None:
+ """Add existing table columns to the written column map."""
+ for dataset in datasets:
+ columns = get_table_columns(conn, dataset.table_name)
+ if not columns:
+ continue
+ if dataset in written_columns:
+ written_columns[dataset].update(columns)
+ else:
+ written_columns[dataset] = columns
|
@@ -407,8 +429,7 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- 107
-108
+ | def build_rate_view_name(
- *,
- symbol: str,
- granularity: str,
- granularity_count: int,
- timeframe: int,
-) -> str:
- """Return a collision-free offline optimize view name.
-
- View names always include the timeframe integer after a ``__`` separator so
- a symbol such as ``EURUSD_M1`` cannot collide with ``EURUSD`` at timeframe
- ``M1``.
- """
- if granularity_count == 1:
- return f"rate_{symbol}__{timeframe}"
- return f"rate_{symbol}__{granularity}_{timeframe}"
+122
+123
| def build_rate_view_name(
+ *,
+ symbol: str,
+ granularity: str,
+ granularity_count: int,
+ timeframe: int,
+) -> str:
+ """Return a collision-free offline optimize view name.
+
+ View names always include the timeframe integer after a ``__`` separator so
+ a symbol such as ``EURUSD_M1`` cannot collide with ``EURUSD`` at timeframe
+ ``M1``.
+ """
+ if granularity_count == 1:
+ return f"rate_{symbol}__{timeframe}"
+ return f"rate_{symbol}__{granularity}_{timeframe}"
|
@@ -487,41 +509,41 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def create_cash_events_view(
- conn: sqlite3.Connection,
- deals_columns: set[str],
-) -> bool:
- """Create the cash_events SQLite view derived from history_deals.
-
- Returns:
- True if the view was created, False if required columns are missing.
- """
- if "type" not in deals_columns:
- logger.warning("Skipping cash_events view: history_deals.type is missing")
- return False
- conn.execute("DROP VIEW IF EXISTS cash_events")
- conn.execute(
- "CREATE VIEW cash_events AS" # noqa: S608
- f" SELECT * FROM history_deals WHERE type NOT IN {_TRADE_DEAL_TYPES_SQL}",
- )
- return True
+ | def create_cash_events_view(
+ conn: sqlite3.Connection,
+ deals_columns: set[str],
+) -> bool:
+ """Create the cash_events SQLite view derived from history_deals.
+
+ Returns:
+ True if the view was created, False if required columns are missing.
+ """
+ if "type" not in deals_columns:
+ logger.warning("Skipping cash_events view: history_deals.type is missing")
+ return False
+ conn.execute("DROP VIEW IF EXISTS cash_events")
+ conn.execute(
+ "CREATE VIEW cash_events AS" # noqa: S608
+ f" SELECT * FROM history_deals WHERE type NOT IN {_TRADE_DEAL_TYPES_SQL}",
+ )
+ return True
|
@@ -549,51 +571,51 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def create_history_indexes(
- conn: sqlite3.Connection,
- written_columns: dict[Dataset, set[str]],
-) -> None:
- """Create useful indexes for collected history tables when present."""
- if {"symbol", "timeframe", "time"}.issubset(
- written_columns.get(Dataset.rates, set()),
- ):
- conn.execute(
- "CREATE INDEX IF NOT EXISTS idx_rates_symbol_timeframe_time"
- " ON rates(symbol, timeframe, time)",
- )
- if {"symbol", "time"}.issubset(written_columns.get(Dataset.ticks, set())):
- conn.execute(
- "CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time ON ticks(symbol, time)",
- )
- if {"position_id", "symbol"}.issubset(
- written_columns.get(Dataset.history_deals, set()),
- ):
- conn.execute(
- "CREATE INDEX IF NOT EXISTS idx_history_deals_position_symbol"
- " ON history_deals(position_id, symbol)",
- )
+ | def create_history_indexes(
+ conn: sqlite3.Connection,
+ written_columns: dict[Dataset, set[str]],
+) -> None:
+ """Create useful indexes for collected history tables when present."""
+ if {"symbol", "timeframe", "time"}.issubset(
+ written_columns.get(Dataset.rates, set()),
+ ):
+ conn.execute(
+ "CREATE INDEX IF NOT EXISTS idx_rates_symbol_timeframe_time"
+ " ON rates(symbol, timeframe, time)",
+ )
+ if {"symbol", "time"}.issubset(written_columns.get(Dataset.ticks, set())):
+ conn.execute(
+ "CREATE INDEX IF NOT EXISTS idx_ticks_symbol_time ON ticks(symbol, time)",
+ )
+ if {"position_id", "symbol"}.issubset(
+ written_columns.get(Dataset.history_deals, set()),
+ ):
+ conn.execute(
+ "CREATE INDEX IF NOT EXISTS idx_history_deals_position_symbol"
+ " ON history_deals(position_id, symbol)",
+ )
|
@@ -643,99 +665,99 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def create_positions_reconstructed_view(
- conn: sqlite3.Connection,
- deals_columns: set[str],
-) -> bool:
- """Create the positions_reconstructed SQLite view derived from history_deals.
-
- Returns:
- True if the view was created, False if required columns are missing.
- """
- if not _POSITIONS_VIEW_REQUIRED_COLUMNS.issubset(deals_columns):
- missing = ", ".join(sorted(_POSITIONS_VIEW_REQUIRED_COLUMNS - deals_columns))
- logger.warning(
- "Skipping positions_reconstructed view: history_deals missing columns: %s",
- missing,
- )
- return False
- conn.execute("DROP VIEW IF EXISTS positions_reconstructed")
- conn.execute(
- "CREATE VIEW positions_reconstructed AS" # noqa: S608
- " SELECT"
- " position_id,"
- " symbol,"
- " MIN(CASE WHEN entry = 0 THEN time END) AS open_time,"
- " MAX(CASE WHEN entry IN (1, 2, 3) THEN time END) AS close_time,"
- " MIN(CASE WHEN entry = 0 THEN type END) AS direction,"
- " SUM(CASE WHEN entry = 0 THEN volume ELSE 0 END) AS volume_open,"
- " SUM(CASE WHEN entry IN (1, 2, 3) THEN volume ELSE 0 END) AS volume_close,"
- " SUM(CASE WHEN entry = 2 THEN volume ELSE 0 END) AS volume_reversal,"
- " CASE"
- " WHEN SUM(CASE WHEN entry = 0 THEN volume ELSE 0 END) > 0"
- " THEN SUM(CASE WHEN entry = 0 THEN price * volume ELSE 0 END)"
- " / SUM(CASE WHEN entry = 0 THEN volume ELSE 0 END)"
- " END AS open_price,"
- " CASE"
- " WHEN SUM(CASE WHEN entry IN (1, 2, 3) THEN volume ELSE 0 END) > 0"
- " THEN SUM(CASE WHEN entry IN (1, 2, 3) THEN price * volume ELSE 0 END)"
- " / SUM(CASE WHEN entry IN (1, 2, 3) THEN volume ELSE 0 END)"
- " END AS close_price,"
- " SUM(profit) AS total_profit,"
- " SUM(CASE WHEN entry = 2 THEN 1 ELSE 0 END) AS reversal_count,"
- " COUNT(*) AS deals_count"
- " FROM history_deals"
- f" WHERE type IN {_TRADE_DEAL_TYPES_SQL} AND position_id != 0"
- " GROUP BY position_id, symbol"
- " HAVING SUM(CASE WHEN entry IN (1, 2, 3) THEN 1 ELSE 0 END) > 0",
- )
- return True
+ | def create_positions_reconstructed_view(
+ conn: sqlite3.Connection,
+ deals_columns: set[str],
+) -> bool:
+ """Create the positions_reconstructed SQLite view derived from history_deals.
+
+ Returns:
+ True if the view was created, False if required columns are missing.
+ """
+ if not _POSITIONS_VIEW_REQUIRED_COLUMNS.issubset(deals_columns):
+ missing = ", ".join(sorted(_POSITIONS_VIEW_REQUIRED_COLUMNS - deals_columns))
+ logger.warning(
+ "Skipping positions_reconstructed view: history_deals missing columns: %s",
+ missing,
+ )
+ return False
+ conn.execute("DROP VIEW IF EXISTS positions_reconstructed")
+ conn.execute(
+ "CREATE VIEW positions_reconstructed AS" # noqa: S608
+ " SELECT"
+ " position_id,"
+ " symbol,"
+ " MIN(CASE WHEN entry = 0 THEN time END) AS open_time,"
+ " MAX(CASE WHEN entry IN (1, 2, 3) THEN time END) AS close_time,"
+ " MIN(CASE WHEN entry = 0 THEN type END) AS direction,"
+ " SUM(CASE WHEN entry = 0 THEN volume ELSE 0 END) AS volume_open,"
+ " SUM(CASE WHEN entry IN (1, 2, 3) THEN volume ELSE 0 END) AS volume_close,"
+ " SUM(CASE WHEN entry = 2 THEN volume ELSE 0 END) AS volume_reversal,"
+ " CASE"
+ " WHEN SUM(CASE WHEN entry = 0 THEN volume ELSE 0 END) > 0"
+ " THEN SUM(CASE WHEN entry = 0 THEN price * volume ELSE 0 END)"
+ " / SUM(CASE WHEN entry = 0 THEN volume ELSE 0 END)"
+ " END AS open_price,"
+ " CASE"
+ " WHEN SUM(CASE WHEN entry IN (1, 2, 3) THEN volume ELSE 0 END) > 0"
+ " THEN SUM(CASE WHEN entry IN (1, 2, 3) THEN price * volume ELSE 0 END)"
+ " / SUM(CASE WHEN entry IN (1, 2, 3) THEN volume ELSE 0 END)"
+ " END AS close_price,"
+ " SUM(profit) AS total_profit,"
+ " SUM(CASE WHEN entry = 2 THEN 1 ELSE 0 END) AS reversal_count,"
+ " COUNT(*) AS deals_count"
+ " FROM history_deals"
+ f" WHERE type IN {_TRADE_DEAL_TYPES_SQL} AND position_id != 0"
+ " GROUP BY position_id, symbol"
+ " HAVING SUM(CASE WHEN entry IN (1, 2, 3) THEN 1 ELSE 0 END) > 0",
+ )
+ return True
|
@@ -760,67 +782,67 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def create_rate_compatibility_views(conn: sqlite3.Connection) -> None:
- """Create rate compatibility views from the normalized rates table."""
- columns = get_table_columns(conn, Dataset.rates.table_name)
- if not {"symbol", "timeframe", "time"}.issubset(columns):
- return
- drop_rate_compatibility_views(conn)
- select_columns = sorted(columns - {"symbol", "timeframe"})
- quoted_columns = ", ".join(f'"{column}"' for column in select_columns)
- rows = conn.execute(
- "SELECT DISTINCT symbol, timeframe FROM rates ORDER BY symbol, timeframe",
- ).fetchall()
- timeframes_by_symbol: dict[str, list[int]] = {}
- for symbol, timeframe in rows:
- timeframes_by_symbol.setdefault(str(symbol), []).append(int(timeframe))
- for symbol, timeframes in timeframes_by_symbol.items():
- for timeframe in timeframes:
- granularity = resolve_granularity_name(timeframe)
- view_name = build_rate_view_name(
- symbol=symbol,
- granularity=granularity,
- granularity_count=len(timeframes),
- timeframe=timeframe,
- )
- quoted_view_name = quote_sqlite_identifier(view_name)
- escaped_symbol = symbol.replace("'", "''")
- conn.execute(
- f"CREATE VIEW {quoted_view_name} AS" # noqa: S608
- f" SELECT {quoted_columns} FROM rates"
- f" WHERE symbol = '{escaped_symbol}'"
- f" AND timeframe = {timeframe}",
- )
+ | def create_rate_compatibility_views(conn: sqlite3.Connection) -> None:
+ """Create rate compatibility views from the normalized rates table."""
+ columns = get_table_columns(conn, Dataset.rates.table_name)
+ if not {"symbol", "timeframe", "time"}.issubset(columns):
+ return
+ drop_rate_compatibility_views(conn)
+ select_columns = sorted(columns - {"symbol", "timeframe"})
+ quoted_columns = ", ".join(f'"{column}"' for column in select_columns)
+ rows = conn.execute(
+ "SELECT DISTINCT symbol, timeframe FROM rates ORDER BY symbol, timeframe",
+ ).fetchall()
+ timeframes_by_symbol: dict[str, list[int]] = {}
+ for symbol, timeframe in rows:
+ timeframes_by_symbol.setdefault(str(symbol), []).append(int(timeframe))
+ for symbol, timeframes in timeframes_by_symbol.items():
+ for timeframe in timeframes:
+ granularity = resolve_granularity_name(timeframe)
+ view_name = build_rate_view_name(
+ symbol=symbol,
+ granularity=granularity,
+ granularity_count=len(timeframes),
+ timeframe=timeframe,
+ )
+ quoted_view_name = quote_sqlite_identifier(view_name)
+ escaped_symbol = symbol.replace("'", "''")
+ conn.execute(
+ f"CREATE VIEW {quoted_view_name} AS" # noqa: S608
+ f" SELECT {quoted_columns} FROM rates"
+ f" WHERE symbol = '{escaped_symbol}'"
+ f" AND timeframe = {timeframe}",
+ )
|
@@ -856,81 +878,81 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def deduplicate_history_tables(
- conn: sqlite3.Connection,
- written_columns: dict[Dataset, set[str]],
- written_tables: set[Dataset],
- dedup_scopes: dict[Dataset, list[DedupScope]] | None = None,
-) -> None:
- """Deduplicate appended history tables by stable identifiers."""
- cursor = conn.cursor()
- for dataset in written_tables:
- columns = written_columns.get(dataset, set())
- table = dataset.table_name
- keys = next(
- (
- candidate
- for candidate in _HISTORY_DEDUP_KEYS[dataset]
- if set(candidate).issubset(columns)
- ),
- None,
- )
- if keys is None:
- logger.warning(
- "Skipping %s deduplication: no supported key columns",
- table,
- )
- continue
- scopes = dedup_scopes.get(dataset, []) if dedup_scopes else []
- if scopes:
- for scope_where, scope_params in scopes:
- drop_duplicates_in_table(
- cursor,
- table,
- list(keys),
- keep="last",
- scope_where=scope_where,
- scope_params=scope_params,
- )
- continue
- drop_duplicates_in_table(cursor, table, list(keys), keep="last")
+ | def deduplicate_history_tables(
+ conn: sqlite3.Connection,
+ written_columns: dict[Dataset, set[str]],
+ written_tables: set[Dataset],
+ dedup_scopes: dict[Dataset, list[DedupScope]] | None = None,
+) -> None:
+ """Deduplicate appended history tables by stable identifiers."""
+ cursor = conn.cursor()
+ for dataset in written_tables:
+ columns = written_columns.get(dataset, set())
+ table = dataset.table_name
+ keys = next(
+ (
+ candidate
+ for candidate in _HISTORY_DEDUP_KEYS[dataset]
+ if set(candidate).issubset(columns)
+ ),
+ None,
+ )
+ if keys is None:
+ logger.warning(
+ "Skipping %s deduplication: no supported key columns",
+ table,
+ )
+ continue
+ scopes = dedup_scopes.get(dataset, []) if dedup_scopes else []
+ if scopes:
+ for scope_where, scope_params in scopes:
+ drop_duplicates_in_table(
+ cursor,
+ table,
+ list(keys),
+ keep="last",
+ scope_where=scope_where,
+ scope_params=scope_params,
+ )
+ continue
+ drop_duplicates_in_table(cursor, table, list(keys), keep="last")
|
@@ -986,73 +1008,73 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def drop_duplicates_in_table(
- cursor: sqlite3.Cursor,
- table: str,
- ids: list[str],
- *,
- keep: Literal["first", "last"] = "last",
- scope_where: str | None = None,
- scope_params: tuple[object, ...] = (),
-) -> None:
- """Remove duplicate rows, keeping the first or last ROWID per key group.
-
- Raises:
- ValueError: If the table or column names are invalid.
- """
- if not table.isidentifier():
- msg = f"Invalid table name: {table}"
- raise ValueError(msg)
- if invalid := {column for column in ids if not column.isidentifier()}:
- msg = f"Invalid column names: {', '.join(sorted(invalid))}"
- raise ValueError(msg)
- ids_csv = ", ".join(f'"{column}"' for column in ids)
- rowid_selector = "MIN" if keep == "first" else "MAX"
- if scope_where:
- delete_sql = (
- f"DELETE FROM {table} WHERE {scope_where} AND ROWID NOT IN" # noqa: S608
- f" (SELECT {rowid_selector}(ROWID) FROM {table} WHERE {scope_where}"
- f" GROUP BY {ids_csv})"
- )
- cursor.execute(delete_sql, scope_params + scope_params)
- return
- cursor.execute(
- f"DELETE FROM {table} WHERE ROWID NOT IN" # noqa: S608
- f" (SELECT {rowid_selector}(ROWID) FROM {table} GROUP BY {ids_csv})",
- )
+ | def drop_duplicates_in_table(
+ cursor: sqlite3.Cursor,
+ table: str,
+ ids: list[str],
+ *,
+ keep: Literal["first", "last"] = "last",
+ scope_where: str | None = None,
+ scope_params: tuple[object, ...] = (),
+) -> None:
+ """Remove duplicate rows, keeping the first or last ROWID per key group.
+
+ Raises:
+ ValueError: If the table or column names are invalid.
+ """
+ if not table.isidentifier():
+ msg = f"Invalid table name: {table}"
+ raise ValueError(msg)
+ if invalid := {column for column in ids if not column.isidentifier()}:
+ msg = f"Invalid column names: {', '.join(sorted(invalid))}"
+ raise ValueError(msg)
+ ids_csv = ", ".join(f'"{column}"' for column in ids)
+ rowid_selector = "MIN" if keep == "first" else "MAX"
+ if scope_where:
+ delete_sql = (
+ f"DELETE FROM {table} WHERE {scope_where} AND ROWID NOT IN" # noqa: S608
+ f" (SELECT {rowid_selector}(ROWID) FROM {table} WHERE {scope_where}"
+ f" GROUP BY {ids_csv})"
+ )
+ cursor.execute(delete_sql, scope_params + scope_params)
+ return
+ cursor.execute(
+ f"DELETE FROM {table} WHERE ROWID NOT IN" # noqa: S608
+ f" (SELECT {rowid_selector}(ROWID) FROM {table} GROUP BY {ids_csv})",
+ )
|
@@ -1077,21 +1099,21 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def drop_rate_compatibility_views(conn: sqlite3.Connection) -> None:
- """Drop all mt5cli-managed ``rate_*`` compatibility views."""
- rows = conn.execute(
- "SELECT name FROM sqlite_master WHERE type = 'view' AND name GLOB 'rate_*'",
- ).fetchall()
- for (view_name,) in rows:
- quoted_view_name = quote_sqlite_identifier(str(view_name))
- conn.execute(f"DROP VIEW IF EXISTS {quoted_view_name}")
+ | def drop_rate_compatibility_views(conn: sqlite3.Connection) -> None:
+ """Drop all mt5cli-managed ``rate_*`` compatibility views."""
+ rows = conn.execute(
+ "SELECT name FROM sqlite_master WHERE type = 'view' AND name GLOB 'rate_*'",
+ ).fetchall()
+ for (view_name,) in rows:
+ quoted_view_name = quote_sqlite_identifier(str(view_name))
+ conn.execute(f"DROP VIEW IF EXISTS {quoted_view_name}")
|
@@ -1154,61 +1176,61 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def filter_incremental_history_deals_frame(
- frame: pd.DataFrame,
- symbols: Sequence[str],
- start_by_symbol: dict[str, datetime],
- account_event_start: datetime,
-) -> pd.DataFrame:
- """Filter incrementally fetched history_deals by symbol and event start times.
-
- Returns:
- Rows for selected symbols at or after each symbol start, plus account
- events at or after ``account_event_start``.
- """
- if frame.empty:
- return frame.copy()
- parsed_times = _frame_parsed_times(frame)
- time_valid = parsed_times.notna()
- account_event_mask = _history_deals_account_event_mask(frame)
- account_keep = account_event_mask & (parsed_times >= account_event_start)
- trade_keep = pd.Series(data=False, index=frame.index)
- if "symbol" in frame.columns:
- for symbol in symbols:
- trade_keep |= (
- (frame["symbol"] == symbol)
- & (parsed_times >= start_by_symbol[symbol])
- & ~account_event_mask
- )
- keep = (account_keep | trade_keep) & time_valid
- return frame.loc[keep].copy()
+ | def filter_incremental_history_deals_frame(
+ frame: pd.DataFrame,
+ symbols: Sequence[str],
+ start_by_symbol: dict[str, datetime],
+ account_event_start: datetime,
+) -> pd.DataFrame:
+ """Filter incrementally fetched history_deals by symbol and event start times.
+
+ Returns:
+ Rows for selected symbols at or after each symbol start, plus account
+ events at or after ``account_event_start``.
+ """
+ if frame.empty:
+ return frame.copy()
+ parsed_times = _frame_parsed_times(frame)
+ time_valid = parsed_times.notna()
+ account_event_mask = _history_deals_account_event_mask(frame)
+ account_keep = account_event_mask & (parsed_times >= account_event_start)
+ trade_keep = pd.Series(data=False, index=frame.index)
+ if "symbol" in frame.columns:
+ for symbol in symbols:
+ trade_keep |= (
+ (frame["symbol"] == symbol)
+ & (parsed_times >= start_by_symbol[symbol])
+ & ~account_event_mask
+ )
+ keep = (account_keep | trade_keep) & time_valid
+ return frame.loc[keep].copy()
|
@@ -1261,41 +1283,41 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def filter_trade_history_frame(
- frame: pd.DataFrame,
- symbols: Sequence[str],
- *,
- include_account_events: bool,
-) -> pd.DataFrame:
- """Filter trade history rows to selected symbols and account events.
-
- Returns:
- Filtered history rows.
- """
- if "symbol" not in frame.columns:
- return frame
- symbol_mask = frame["symbol"].isin(symbols)
- if not include_account_events:
- return frame.loc[symbol_mask].copy()
- account_event_mask = _history_deals_account_event_mask(frame)
- return frame.loc[symbol_mask | account_event_mask].copy()
+ | def filter_trade_history_frame(
+ frame: pd.DataFrame,
+ symbols: Sequence[str],
+ *,
+ include_account_events: bool,
+) -> pd.DataFrame:
+ """Filter trade history rows to selected symbols and account events.
+
+ Returns:
+ Filtered history rows.
+ """
+ if "symbol" not in frame.columns:
+ return frame
+ symbol_mask = frame["symbol"].isin(symbols)
+ if not include_account_events:
+ return frame.loc[symbol_mask].copy()
+ account_event_mask = _history_deals_account_event_mask(frame)
+ return frame.loc[symbol_mask | account_event_mask].copy()
|
@@ -1322,47 +1344,47 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def get_history_deals_account_event_start_datetime(
- conn: sqlite3.Connection,
- *,
- fallback_start: datetime,
-) -> datetime:
- """Return the next update start for account-level history_deals rows."""
- table = Dataset.history_deals.table_name
- columns = get_table_columns(conn, table)
- if "time" not in columns:
- return fallback_start
- if "type" in columns:
- where_clause = f"type NOT IN {_TRADE_DEAL_TYPES_SQL}"
- elif "symbol" in columns:
- where_clause = "symbol IS NULL OR symbol = ''"
- else:
- return fallback_start
- row = conn.execute(
- f"SELECT MAX(time) FROM {table} WHERE {where_clause}", # noqa: S608
- ).fetchone()
- parsed = parse_sqlite_timestamp(row[0] if row else None)
- return parsed if parsed is not None else fallback_start
+ | def get_history_deals_account_event_start_datetime(
+ conn: sqlite3.Connection,
+ *,
+ fallback_start: datetime,
+) -> datetime:
+ """Return the next update start for account-level history_deals rows."""
+ table = Dataset.history_deals.table_name
+ columns = get_table_columns(conn, table)
+ if "time" not in columns:
+ return fallback_start
+ if "type" in columns:
+ where_clause = f"type NOT IN {_TRADE_DEAL_TYPES_SQL}"
+ elif "symbol" in columns:
+ where_clause = "symbol IS NULL OR symbol = ''"
+ else:
+ return fallback_start
+ row = conn.execute(
+ f"SELECT MAX(time) FROM {table} WHERE {where_clause}", # noqa: S608
+ ).fetchone()
+ parsed = parse_sqlite_timestamp(row[0] if row else None)
+ return parsed if parsed is not None else fallback_start
|
@@ -1394,41 +1416,41 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def get_incremental_start_datetime(
- conn: sqlite3.Connection,
- dataset: Dataset,
- *,
- symbol: str,
- timeframe: int | None,
- fallback_start: datetime,
-) -> datetime:
- """Return the next update start datetime from existing MAX(time)."""
- timeframes = [timeframe] if timeframe is not None else None
- starts = load_incremental_start_datetimes(
- conn,
- dataset,
- symbols=[symbol],
- timeframes=timeframes,
- fallback_start=fallback_start,
- )
- return starts[symbol, timeframe]
+ | def get_incremental_start_datetime(
+ conn: sqlite3.Connection,
+ dataset: Dataset,
+ *,
+ symbol: str,
+ timeframe: int | None,
+ fallback_start: datetime,
+) -> datetime:
+ """Return the next update start datetime from existing MAX(time)."""
+ timeframes = [timeframe] if timeframe is not None else None
+ starts = load_incremental_start_datetimes(
+ conn,
+ dataset,
+ symbols=[symbol],
+ timeframes=timeframes,
+ fallback_start=fallback_start,
+ )
+ return starts[symbol, timeframe]
|
@@ -1453,13 +1475,13 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def get_table_columns(conn: sqlite3.Connection, table: str) -> set[str]:
- """Return existing SQLite columns for a table."""
- rows = conn.execute(f"PRAGMA table_info({table})").fetchall()
- return {str(row[1]) for row in rows}
+ | def get_table_columns(conn: sqlite3.Connection, table: str) -> set[str]:
+ """Return existing SQLite columns for a table."""
+ rows = conn.execute(f"PRAGMA table_info({table})").fetchall()
+ return {str(row[1]) for row in rows}
|
@@ -1491,153 +1513,153 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def load_incremental_start_datetimes(
- conn: sqlite3.Connection,
- dataset: Dataset,
- *,
- symbols: Sequence[str],
- timeframes: Sequence[int] | None = None,
- fallback_start: datetime,
-) -> dict[tuple[str, int | None], datetime]:
- """Return next update start datetimes keyed by symbol and optional timeframe."""
- table = dataset.table_name
- columns = get_table_columns(conn, table)
- if dataset is Dataset.rates and columns:
- _validate_rates_schema(columns)
-
- if "time" not in columns:
- if dataset is Dataset.rates and timeframes is not None:
- return {
- (symbol, timeframe): fallback_start
- for symbol in symbols
- for timeframe in timeframes
- }
- return {(symbol, None): fallback_start for symbol in symbols}
-
- parsed_by_key: dict[tuple[str, int | None], datetime] = {}
- if (
- dataset is Dataset.rates
- and timeframes is not None
- and {"symbol", "timeframe"}.issubset(columns)
- ):
- symbol_placeholders = ", ".join("?" for _ in symbols)
- timeframe_placeholders = ", ".join("?" for _ in timeframes)
- grouped_rates_query = (
- "SELECT symbol, timeframe, MAX(time) FROM " # noqa: S608
- f"{table} WHERE symbol IN ({symbol_placeholders})"
- f" AND timeframe IN ({timeframe_placeholders})"
- " GROUP BY symbol, timeframe"
- )
- rows = conn.execute(
- grouped_rates_query,
- [*symbols, *timeframes],
- ).fetchall()
- for row_symbol, row_timeframe, max_time in rows:
- parsed = parse_sqlite_timestamp(max_time)
- if parsed is not None:
- parsed_by_key[str(row_symbol), int(row_timeframe)] = parsed
- return {
- (symbol, timeframe): parsed_by_key.get(
- (symbol, timeframe),
- fallback_start,
- )
- for symbol in symbols
- for timeframe in timeframes
- }
-
- if "symbol" in columns:
- symbol_placeholders = ", ".join("?" for _ in symbols)
- rows = conn.execute(
- f"SELECT symbol, MAX(time) FROM {table}" # noqa: S608
- f" WHERE symbol IN ({symbol_placeholders}) GROUP BY symbol",
- list(symbols),
- ).fetchall()
- for row_symbol, max_time in rows:
- parsed = parse_sqlite_timestamp(max_time)
- if parsed is not None:
- parsed_by_key[str(row_symbol), None] = parsed
- return {
- (symbol, None): parsed_by_key.get((symbol, None), fallback_start)
- for symbol in symbols
- }
-
- row = conn.execute(f"SELECT MAX(time) FROM {table}").fetchone() # noqa: S608
- parsed = parse_sqlite_timestamp(row[0] if row else None)
- shared_start = parsed if parsed is not None else fallback_start
- return {(symbol, None): shared_start for symbol in symbols}
+ | def load_incremental_start_datetimes(
+ conn: sqlite3.Connection,
+ dataset: Dataset,
+ *,
+ symbols: Sequence[str],
+ timeframes: Sequence[int] | None = None,
+ fallback_start: datetime,
+) -> dict[tuple[str, int | None], datetime]:
+ """Return next update start datetimes keyed by symbol and optional timeframe."""
+ table = dataset.table_name
+ columns = get_table_columns(conn, table)
+ if dataset is Dataset.rates and columns:
+ _validate_rates_schema(columns)
+
+ if "time" not in columns:
+ if dataset is Dataset.rates and timeframes is not None:
+ return {
+ (symbol, timeframe): fallback_start
+ for symbol in symbols
+ for timeframe in timeframes
+ }
+ return {(symbol, None): fallback_start for symbol in symbols}
+
+ parsed_by_key: dict[tuple[str, int | None], datetime] = {}
+ if (
+ dataset is Dataset.rates
+ and timeframes is not None
+ and {"symbol", "timeframe"}.issubset(columns)
+ ):
+ symbol_placeholders = ", ".join("?" for _ in symbols)
+ timeframe_placeholders = ", ".join("?" for _ in timeframes)
+ grouped_rates_query = (
+ "SELECT symbol, timeframe, MAX(time) FROM " # noqa: S608
+ f"{table} WHERE symbol IN ({symbol_placeholders})"
+ f" AND timeframe IN ({timeframe_placeholders})"
+ " GROUP BY symbol, timeframe"
+ )
+ rows = conn.execute(
+ grouped_rates_query,
+ [*symbols, *timeframes],
+ ).fetchall()
+ for row_symbol, row_timeframe, max_time in rows:
+ parsed = parse_sqlite_timestamp(max_time)
+ if parsed is not None:
+ parsed_by_key[str(row_symbol), int(row_timeframe)] = parsed
+ return {
+ (symbol, timeframe): parsed_by_key.get(
+ (symbol, timeframe),
+ fallback_start,
+ )
+ for symbol in symbols
+ for timeframe in timeframes
+ }
+
+ if "symbol" in columns:
+ symbol_placeholders = ", ".join("?" for _ in symbols)
+ rows = conn.execute(
+ f"SELECT symbol, MAX(time) FROM {table}" # noqa: S608
+ f" WHERE symbol IN ({symbol_placeholders}) GROUP BY symbol",
+ list(symbols),
+ ).fetchall()
+ for row_symbol, max_time in rows:
+ parsed = parse_sqlite_timestamp(max_time)
+ if parsed is not None:
+ parsed_by_key[str(row_symbol), None] = parsed
+ return {
+ (symbol, None): parsed_by_key.get((symbol, None), fallback_start)
+ for symbol in symbols
+ }
+
+ row = conn.execute(f"SELECT MAX(time) FROM {table}").fetchone() # noqa: S608
+ parsed = parse_sqlite_timestamp(row[0] if row else None)
+ shared_start = parsed if parsed is not None else fallback_start
+ return {(symbol, None): shared_start for symbol in symbols}
|
@@ -1685,37 +1707,37 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def parse_sqlite_timestamp(value: object) -> datetime | None:
- """Parse a SQLite history timestamp value.
-
- Returns:
- Parsed timezone-aware datetime, or None when parsing fails.
- """
- if value is None:
- return None
- if isinstance(value, datetime):
- return value if value.tzinfo is not None else value.replace(tzinfo=UTC)
- if isinstance(value, int | float):
- return datetime.fromtimestamp(float(value), tz=UTC)
- if isinstance(value, str):
- return _parse_string_sqlite_timestamp(value)
- logger.warning("Ignoring unsupported history timestamp type: %s", type(value))
- return None
+ | def parse_sqlite_timestamp(value: object) -> datetime | None:
+ """Parse a SQLite history timestamp value.
+
+ Returns:
+ Parsed timezone-aware datetime, or None when parsing fails.
+ """
+ if value is None:
+ return None
+ if isinstance(value, datetime):
+ return value if value.tzinfo is not None else value.replace(tzinfo=UTC)
+ if isinstance(value, int | float):
+ return datetime.fromtimestamp(float(value), tz=UTC)
+ if isinstance(value, str):
+ return _parse_string_sqlite_timestamp(value)
+ logger.warning("Ignoring unsupported history timestamp type: %s", type(value))
+ return None
|
@@ -1740,11 +1762,11 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def quote_sqlite_identifier(identifier: str) -> str:
- """Return a safely quoted SQLite identifier using double quotes."""
- return '"' + identifier.replace('"', '""') + '"'
+ | def quote_sqlite_identifier(identifier: str) -> str:
+ """Return a safely quoted SQLite identifier using double quotes."""
+ return '"' + identifier.replace('"', '""') + '"'
|
@@ -1773,27 +1795,27 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def record_written_columns(
- written_columns: dict[Dataset, set[str]],
- dataset: Dataset,
- frame: pd.DataFrame,
-) -> None:
- """Remember columns for datasets written during collection."""
- columns = set(frame.columns)
- if dataset in written_columns:
- written_columns[dataset].update(columns)
- else:
- written_columns[dataset] = columns
+ | def record_written_columns(
+ written_columns: dict[Dataset, set[str]],
+ dataset: Dataset,
+ frame: pd.DataFrame,
+) -> None:
+ """Remember columns for datasets written during collection."""
+ columns = set(frame.columns)
+ if dataset in written_columns:
+ written_columns[dataset].update(columns)
+ else:
+ written_columns[dataset] = columns
|
@@ -1818,17 +1840,17 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- 99
-100
+ | def resolve_granularity_name(timeframe: int) -> str:
- """Return a granularity name for a timeframe integer when known."""
- for name, value in TIMEFRAME_MAP.items():
- if value == timeframe:
- return name
- return str(timeframe)
+104
+105
| def resolve_granularity_name(timeframe: int) -> str:
+ """Return a granularity name for a timeframe integer when known."""
+ for name, value in TIMEFRAME_MAP.items():
+ if value == timeframe:
+ return name
+ return str(timeframe)
|
@@ -1888,8 +1910,7 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- 57
-58
+ 58
59
60
61
@@ -1897,16 +1918,17 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
63
64
65
-66 | def resolve_history_datasets(datasets: set[Dataset] | None) -> set[Dataset]:
- """Resolve configured history datasets.
-
- Returns:
- All supported datasets when ``datasets`` is None, otherwise the
- configured selection (which may be empty).
- """
- if datasets is None:
- return set(Dataset)
- return set(datasets)
+66
+67
| def resolve_history_datasets(datasets: set[Dataset] | None) -> set[Dataset]:
+ """Resolve configured history datasets.
+
+ Returns:
+ All supported datasets when ``datasets`` is None, otherwise the
+ configured selection (which may be empty).
+ """
+ if datasets is None:
+ return set(Dataset)
+ return set(datasets)
|
@@ -1954,23 +1976,23 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- 88
-89
+ | def resolve_history_tick_flags(flags: int | str) -> int:
- """Resolve tick copy flags from an integer or name.
-
- Returns:
- Integer tick flag value.
- """
- if isinstance(flags, int):
- return flags
- return parse_tick_flags(flags)
+96
+97
| def resolve_history_tick_flags(flags: int | str) -> int:
+ """Resolve tick copy flags from an integer or name.
+
+ Returns:
+ Integer tick flag value.
+ """
+ if isinstance(flags, int):
+ return flags
+ return parse_tick_flags(flags)
|
@@ -2020,8 +2042,7 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- 69
-70
+ 70
71
72
73
@@ -2036,23 +2057,545 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
82
83
84
-85 | def resolve_history_timeframes(
- timeframes: Sequence[int | str] | None,
-) -> list[int]:
- """Resolve rate timeframes, deduplicating aliases for the same integer.
-
- Returns:
- Ordered list of unique timeframe integers.
- """
- raw = timeframes if timeframes is not None else DEFAULT_HISTORY_TIMEFRAMES
- seen: set[int] = set()
- resolved: list[int] = []
- for value in raw:
- tf = value if isinstance(value, int) else parse_timeframe(str(value))
- if tf not in seen:
- seen.add(tf)
- resolved.append(tf)
- return resolved
+85
+86
| def resolve_history_timeframes(
+ timeframes: Sequence[int | str] | None,
+) -> list[int]:
+ """Resolve rate timeframes, deduplicating aliases for the same integer.
+
+ Returns:
+ Ordered list of unique timeframe integers.
+ """
+ raw = timeframes if timeframes is not None else DEFAULT_HISTORY_TIMEFRAMES
+ seen: set[int] = set()
+ resolved: list[int] = []
+ for value in raw:
+ tf = value if isinstance(value, int) else parse_timeframe(str(value))
+ if tf not in seen:
+ seen.add(tf)
+ resolved.append(tf)
+ return resolved
+
|
+
+
+
+
+
+
+
+
+
+ resolve_rate_view_name
+
+
+
+ resolve_rate_view_name(
+ conn_or_path: SqliteConnOrPath,
+ symbol: str,
+ granularity: str,
+ *,
+ require_existing: bool = False,
+) -> str
+
+
+
+
+ Resolve the mt5cli-managed rate compatibility view name.
+
+
+ Parameters:
+
+
+
+ | Name |
+ Type |
+ Description |
+ Default |
+
+
+
+
+
+ conn_or_path
+ |
+
+ SqliteConnOrPath
+ |
+
+
+ SQLite database path or open connection.
+
+ |
+
+ required
+ |
+
+
+
+ symbol
+ |
+
+ str
+ |
+
+
+ Symbol stored in the normalized rates table.
+
+ |
+
+ required
+ |
+
+
+
+ granularity
+ |
+
+ str
+ |
+
+
+ Timeframe name (for example M1) or integer string.
+
+ |
+
+ required
+ |
+
+
+
+ require_existing
+ |
+
+ bool
+ |
+
+
+ When True, require the database and a managed view to exist.
+
+ |
+
+ False
+ |
+
+
+
+
+
+ Returns:
+
+
+
+ | Type |
+ Description |
+
+
+
+
+
+ str
+ |
+
+
+ View name such as rate_EURUSD__1 or rate_EURUSD__M1_1.
+
+ |
+
+
+
+
+
+ Raises:
+
+
+
+ | Type |
+ Description |
+
+
+
+
+
+ ValueError
+ |
+
+
+ If require_existing is True and the database or view is missing.
+
+ |
+
+
+
+
+
+
+ Source code in mt5cli/history.py
+ | def resolve_rate_view_name(
+ conn_or_path: SqliteConnOrPath,
+ symbol: str,
+ granularity: str,
+ *,
+ require_existing: bool = False,
+) -> str:
+ """Resolve the mt5cli-managed rate compatibility view name.
+
+ Args:
+ conn_or_path: SQLite database path or open connection.
+ symbol: Symbol stored in the normalized ``rates`` table.
+ granularity: Timeframe name (for example ``M1``) or integer string.
+ require_existing: When True, require the database and a managed view to exist.
+
+ Returns:
+ View name such as ``rate_EURUSD__1`` or ``rate_EURUSD__M1_1``.
+
+ Raises:
+ ValueError: If ``require_existing`` is True and the database or view is missing.
+ """
+ timeframe = parse_timeframe(granularity)
+ granularity_name = resolve_granularity_name(timeframe)
+ conn, should_close = _open_history_connection(conn_or_path)
+ try:
+ if conn is None:
+ if require_existing:
+ path = (
+ conn_or_path
+ if isinstance(conn_or_path, (Path, str))
+ else "database"
+ )
+ msg = f"SQLite database not found: {path}"
+ raise ValueError(msg)
+ return build_rate_view_name(
+ symbol=symbol,
+ granularity=granularity_name,
+ granularity_count=1,
+ timeframe=timeframe,
+ )
+ return _resolve_rate_view_name_from_context(
+ symbol=symbol,
+ timeframe=timeframe,
+ granularity_name=granularity_name,
+ timeframe_counts=_load_rates_timeframe_counts(conn),
+ existing_views=_load_existing_rate_views(conn),
+ require_existing=require_existing,
+ )
+ finally:
+ if should_close and conn is not None:
+ conn.close()
+
|
+
+
+
+
+
+
+
+
+
+ resolve_rate_view_names
+
+
+
+ resolve_rate_view_names(
+ conn_or_path: SqliteConnOrPath,
+ symbols: Sequence[str],
+ granularities: Sequence[str],
+ *,
+ require_existing: bool = False,
+) -> list[str]
+
+
+
+
+ Resolve rate compatibility view names for symbol and granularity pairs.
+
+
+ Parameters:
+
+
+
+ | Name |
+ Type |
+ Description |
+ Default |
+
+
+
+
+
+ conn_or_path
+ |
+
+ SqliteConnOrPath
+ |
+
+
+ SQLite database path or open connection.
+
+ |
+
+ required
+ |
+
+
+
+ symbols
+ |
+
+ Sequence[str]
+ |
+
+
+ Symbols stored in the normalized rates table.
+
+ |
+
+ required
+ |
+
+
+
+ granularities
+ |
+
+ Sequence[str]
+ |
+
+
+ Timeframe names (for example M1) or integer strings.
+
+ |
+
+ required
+ |
+
+
+
+ require_existing
+ |
+
+ bool
+ |
+
+
+ When True, require the database and managed views to exist.
+
+ |
+
+ False
+ |
+
+
+
+
+
+ Returns:
+
+
+
+ | Type |
+ Description |
+
+
+
+
+
+ list[str]
+ |
+
+
+ View names in row-major order: every granularity for the first
+
+ |
+
+
+
+ list[str]
+ |
+
+
+ symbol, then every granularity for the next symbol, and so on.
+
+ |
+
+
+
+
+
+
+ Source code in mt5cli/history.py
+ | def resolve_rate_view_names(
+ conn_or_path: SqliteConnOrPath,
+ symbols: Sequence[str],
+ granularities: Sequence[str],
+ *,
+ require_existing: bool = False,
+) -> list[str]:
+ """Resolve rate compatibility view names for symbol and granularity pairs.
+
+ Args:
+ conn_or_path: SQLite database path or open connection.
+ symbols: Symbols stored in the normalized ``rates`` table.
+ granularities: Timeframe names (for example ``M1``) or integer strings.
+ require_existing: When True, require the database and managed views to exist.
+
+ Returns:
+ View names in row-major order: every ``granularity`` for the first
+ symbol, then every granularity for the next symbol, and so on.
+ """
+ conn, should_close = _open_history_connection(conn_or_path)
+ try:
+ if conn is None:
+ return [
+ resolve_rate_view_name(
+ conn_or_path,
+ symbol,
+ granularity,
+ require_existing=require_existing,
+ )
+ for symbol in symbols
+ for granularity in granularities
+ ]
+ timeframe_counts = _load_rates_timeframe_counts(conn)
+ existing_views = _load_existing_rate_views(conn)
+ resolved: list[str] = []
+ for symbol in symbols:
+ for granularity in granularities:
+ timeframe = parse_timeframe(granularity)
+ resolved.append(
+ _resolve_rate_view_name_from_context(
+ symbol=symbol,
+ timeframe=timeframe,
+ granularity_name=resolve_granularity_name(timeframe),
+ timeframe_counts=timeframe_counts,
+ existing_views=existing_views,
+ require_existing=require_existing,
+ ),
+ )
+ return resolved
+ finally:
+ if should_close and conn is not None:
+ conn.close()
|
@@ -2110,135 +2653,135 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def write_collected_datasets(
- conn: sqlite3.Connection,
- client: Mt5DataClient,
- symbols: Sequence[str],
- datasets: set[Dataset],
- timeframe: int,
- flags: int,
- date_from: datetime,
- date_to: datetime,
- if_exists: IfExists,
-) -> tuple[set[Dataset], dict[Dataset, set[str]]]:
- """Collect selected datasets and stream each symbol frame into SQLite.
-
- Returns:
- Written datasets and their columns.
- """
- written_columns: dict[Dataset, set[str]] = {}
- written_tables: set[Dataset] = set()
- if Dataset.rates in datasets and write_rates_dataset(
- conn,
- client,
- symbols,
- timeframe,
- date_from,
- date_to,
- if_exists,
- written_columns,
- ):
- written_tables.add(Dataset.rates)
- if Dataset.ticks in datasets and write_ticks_dataset(
- conn,
- client,
- symbols,
- flags,
- date_from,
- date_to,
- if_exists,
- written_columns,
- ):
- written_tables.add(Dataset.ticks)
- if Dataset.history_orders in datasets and write_history_dataset(
- conn,
- client.history_orders_get_as_df,
- Dataset.history_orders,
- symbols,
- date_from,
- date_to,
- if_exists,
- written_columns,
- include_account_events=False,
- ):
- written_tables.add(Dataset.history_orders)
- if Dataset.history_deals in datasets and write_history_dataset(
- conn,
- client.history_deals_get_as_df,
- Dataset.history_deals,
- symbols,
- date_from,
- date_to,
- if_exists,
- written_columns,
- include_account_events=False,
- ):
- written_tables.add(Dataset.history_deals)
- return written_tables, written_columns
+ | def write_collected_datasets(
+ conn: sqlite3.Connection,
+ client: Mt5DataClient,
+ symbols: Sequence[str],
+ datasets: set[Dataset],
+ timeframe: int,
+ flags: int,
+ date_from: datetime,
+ date_to: datetime,
+ if_exists: IfExists,
+) -> tuple[set[Dataset], dict[Dataset, set[str]]]:
+ """Collect selected datasets and stream each symbol frame into SQLite.
+
+ Returns:
+ Written datasets and their columns.
+ """
+ written_columns: dict[Dataset, set[str]] = {}
+ written_tables: set[Dataset] = set()
+ if Dataset.rates in datasets and write_rates_dataset(
+ conn,
+ client,
+ symbols,
+ timeframe,
+ date_from,
+ date_to,
+ if_exists,
+ written_columns,
+ ):
+ written_tables.add(Dataset.rates)
+ if Dataset.ticks in datasets and write_ticks_dataset(
+ conn,
+ client,
+ symbols,
+ flags,
+ date_from,
+ date_to,
+ if_exists,
+ written_columns,
+ ):
+ written_tables.add(Dataset.ticks)
+ if Dataset.history_orders in datasets and write_history_dataset(
+ conn,
+ client.history_orders_get_as_df,
+ Dataset.history_orders,
+ symbols,
+ date_from,
+ date_to,
+ if_exists,
+ written_columns,
+ include_account_events=False,
+ ):
+ written_tables.add(Dataset.history_orders)
+ if Dataset.history_deals in datasets and write_history_dataset(
+ conn,
+ client.history_deals_get_as_df,
+ Dataset.history_deals,
+ symbols,
+ date_from,
+ date_to,
+ if_exists,
+ written_columns,
+ include_account_events=False,
+ ):
+ written_tables.add(Dataset.history_deals)
+ return written_tables, written_columns
|
@@ -2297,101 +2840,101 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def write_history_dataset(
- conn: sqlite3.Connection,
- fetch: Callable[..., pd.DataFrame],
- dataset: Dataset,
- symbols: Sequence[str],
- date_from: datetime,
- date_to: datetime,
- if_exists: IfExists,
- written_columns: dict[Dataset, set[str]],
- *,
- include_account_events: bool = False,
-) -> bool:
- """Stream a history dataset into SQLite.
-
- Returns:
- True if the target table was written.
- """
- table_exists = False
- if include_account_events:
- frame = filter_trade_history_frame(
- fetch(date_from=date_from, date_to=date_to),
- symbols,
- include_account_events=True,
- )
- return write_streamed_frame(
- conn,
- frame,
- dataset,
- table_exists,
- if_exists,
- written_columns,
- )
- for sym in symbols:
- frame = fetch(date_from=date_from, date_to=date_to, symbol=sym)
- frame = filter_trade_history_frame(
- frame,
- [sym],
- include_account_events=False,
- )
- table_exists = write_streamed_frame(
- conn,
- frame,
- dataset,
- table_exists,
- if_exists,
- written_columns,
- )
- return table_exists
+ | def write_history_dataset(
+ conn: sqlite3.Connection,
+ fetch: Callable[..., pd.DataFrame],
+ dataset: Dataset,
+ symbols: Sequence[str],
+ date_from: datetime,
+ date_to: datetime,
+ if_exists: IfExists,
+ written_columns: dict[Dataset, set[str]],
+ *,
+ include_account_events: bool = False,
+) -> bool:
+ """Stream a history dataset into SQLite.
+
+ Returns:
+ True if the target table was written.
+ """
+ table_exists = False
+ if include_account_events:
+ frame = filter_trade_history_frame(
+ fetch(date_from=date_from, date_to=date_to),
+ symbols,
+ include_account_events=True,
+ )
+ return write_streamed_frame(
+ conn,
+ frame,
+ dataset,
+ table_exists,
+ if_exists,
+ written_columns,
+ )
+ for sym in symbols:
+ frame = fetch(date_from=date_from, date_to=date_to, symbol=sym)
+ frame = filter_trade_history_frame(
+ frame,
+ [sym],
+ include_account_events=False,
+ )
+ table_exists = write_streamed_frame(
+ conn,
+ frame,
+ dataset,
+ table_exists,
+ if_exists,
+ written_columns,
+ )
+ return table_exists
|
@@ -2453,167 +2996,167 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def write_incremental_datasets( # noqa: PLR0913
- conn: sqlite3.Connection,
- client: Mt5DataClient,
- symbols: Sequence[str],
- selected_datasets: set[Dataset],
- resolved_timeframes: list[int],
- resolved_tick_flags: int,
- fallback_start: datetime,
- end_date: datetime,
- *,
- deduplicate: bool,
- create_rate_views: bool,
- with_views: bool,
- include_account_events: bool,
-) -> tuple[set[Dataset], dict[Dataset, set[str]]]:
- """Append selected datasets incrementally and refresh indexes and views.
-
- Returns:
- Written datasets and their columns.
- """
- written_columns: dict[Dataset, set[str]] = {}
- written_tables: set[Dataset] = set()
- dedup_scopes: dict[Dataset, list[DedupScope]] = {}
- if Dataset.rates in selected_datasets:
- _write_incremental_rates(
- conn,
- client,
- symbols,
- resolved_timeframes,
- fallback_start,
- end_date,
- written_columns,
- written_tables,
- dedup_scopes,
- )
- if Dataset.ticks in selected_datasets:
- _write_incremental_ticks(
- conn,
- client,
- symbols,
- resolved_tick_flags,
- fallback_start,
- end_date,
- written_columns,
- written_tables,
- dedup_scopes,
- )
- if Dataset.history_orders in selected_datasets:
- _write_incremental_history_orders(
- conn,
- client,
- symbols,
- fallback_start,
- end_date,
- written_columns,
- written_tables,
- dedup_scopes,
- )
- if Dataset.history_deals in selected_datasets:
- _write_incremental_history_deals(
- conn,
- client,
- symbols,
- fallback_start,
- end_date,
- written_columns,
- written_tables,
- dedup_scopes,
- include_account_events=include_account_events,
- )
- _finalize_incremental_writes(
- conn,
- selected_datasets,
- written_columns,
- written_tables,
- dedup_scopes,
- deduplicate=deduplicate,
- create_rate_views=create_rate_views,
- with_views=with_views,
- )
- return written_tables, written_columns
+ | def write_incremental_datasets( # noqa: PLR0913
+ conn: sqlite3.Connection,
+ client: Mt5DataClient,
+ symbols: Sequence[str],
+ selected_datasets: set[Dataset],
+ resolved_timeframes: list[int],
+ resolved_tick_flags: int,
+ fallback_start: datetime,
+ end_date: datetime,
+ *,
+ deduplicate: bool,
+ create_rate_views: bool,
+ with_views: bool,
+ include_account_events: bool,
+) -> tuple[set[Dataset], dict[Dataset, set[str]]]:
+ """Append selected datasets incrementally and refresh indexes and views.
+
+ Returns:
+ Written datasets and their columns.
+ """
+ written_columns: dict[Dataset, set[str]] = {}
+ written_tables: set[Dataset] = set()
+ dedup_scopes: dict[Dataset, list[DedupScope]] = {}
+ if Dataset.rates in selected_datasets:
+ _write_incremental_rates(
+ conn,
+ client,
+ symbols,
+ resolved_timeframes,
+ fallback_start,
+ end_date,
+ written_columns,
+ written_tables,
+ dedup_scopes,
+ )
+ if Dataset.ticks in selected_datasets:
+ _write_incremental_ticks(
+ conn,
+ client,
+ symbols,
+ resolved_tick_flags,
+ fallback_start,
+ end_date,
+ written_columns,
+ written_tables,
+ dedup_scopes,
+ )
+ if Dataset.history_orders in selected_datasets:
+ _write_incremental_history_orders(
+ conn,
+ client,
+ symbols,
+ fallback_start,
+ end_date,
+ written_columns,
+ written_tables,
+ dedup_scopes,
+ )
+ if Dataset.history_deals in selected_datasets:
+ _write_incremental_history_deals(
+ conn,
+ client,
+ symbols,
+ fallback_start,
+ end_date,
+ written_columns,
+ written_tables,
+ dedup_scopes,
+ include_account_events=include_account_events,
+ )
+ _finalize_incremental_writes(
+ conn,
+ selected_datasets,
+ written_columns,
+ written_tables,
+ dedup_scopes,
+ deduplicate=deduplicate,
+ create_rate_views=create_rate_views,
+ with_views=with_views,
+ )
+ return written_tables, written_columns
|
@@ -2670,75 +3213,75 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def write_rates_dataset(
- conn: sqlite3.Connection,
- client: Mt5DataClient,
- symbols: Sequence[str],
- timeframe: int,
- date_from: datetime,
- date_to: datetime,
- if_exists: IfExists,
- written_columns: dict[Dataset, set[str]],
-) -> bool:
- """Stream rates frames into SQLite.
-
- Returns:
- True if the rates table was written.
- """
- table_exists = False
- for sym in symbols:
- frame = client.copy_rates_range_as_df(
- symbol=sym,
- timeframe=timeframe,
- date_from=date_from,
- date_to=date_to,
- ).drop(columns=["symbol", "timeframe"], errors="ignore")
- if len(frame.columns) != 0:
- frame.insert(0, "symbol", sym)
- frame.insert(1, "timeframe", timeframe)
- table_exists = write_streamed_frame(
- conn,
- frame,
- Dataset.rates,
- table_exists,
- if_exists,
- written_columns,
- )
- return table_exists
+ | def write_rates_dataset(
+ conn: sqlite3.Connection,
+ client: Mt5DataClient,
+ symbols: Sequence[str],
+ timeframe: int,
+ date_from: datetime,
+ date_to: datetime,
+ if_exists: IfExists,
+ written_columns: dict[Dataset, set[str]],
+) -> bool:
+ """Stream rates frames into SQLite.
+
+ Returns:
+ True if the rates table was written.
+ """
+ table_exists = False
+ for sym in symbols:
+ frame = client.copy_rates_range_as_df(
+ symbol=sym,
+ timeframe=timeframe,
+ date_from=date_from,
+ date_to=date_to,
+ ).drop(columns=["symbol", "timeframe"], errors="ignore")
+ if len(frame.columns) != 0:
+ frame.insert(0, "symbol", sym)
+ frame.insert(1, "timeframe", timeframe)
+ table_exists = write_streamed_frame(
+ conn,
+ frame,
+ Dataset.rates,
+ table_exists,
+ if_exists,
+ written_columns,
+ )
+ return table_exists
|
@@ -2793,41 +3336,41 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def write_streamed_frame(
- conn: sqlite3.Connection,
- frame: pd.DataFrame,
- dataset: Dataset,
- table_exists: bool,
- if_exists: IfExists,
- written_columns: dict[Dataset, set[str]],
-) -> bool:
- """Write one streamed dataset frame and track table state.
-
- Returns:
- True if the dataset table exists after this write attempt.
- """
- write_mode = IfExists.APPEND if table_exists else if_exists
- if append_dataframe(conn, frame, dataset.table_name, write_mode):
- record_written_columns(written_columns, dataset, frame)
- return True
- return table_exists
+ | def write_streamed_frame(
+ conn: sqlite3.Connection,
+ frame: pd.DataFrame,
+ dataset: Dataset,
+ table_exists: bool,
+ if_exists: IfExists,
+ written_columns: dict[Dataset, set[str]],
+) -> bool:
+ """Write one streamed dataset frame and track table state.
+
+ Returns:
+ True if the dataset table exists after this write attempt.
+ """
+ write_mode = IfExists.APPEND if table_exists else if_exists
+ if append_dataframe(conn, frame, dataset.table_name, write_mode):
+ record_written_columns(written_columns, dataset, frame)
+ return True
+ return table_exists
|
@@ -2884,73 +3427,73 @@ a symbol such as EURUSD_M1 cannot collide with EURUSD
Source code in mt5cli/history.py
- | def write_ticks_dataset(
- conn: sqlite3.Connection,
- client: Mt5DataClient,
- symbols: Sequence[str],
- flags: int,
- date_from: datetime,
- date_to: datetime,
- if_exists: IfExists,
- written_columns: dict[Dataset, set[str]],
-) -> bool:
- """Stream ticks frames into SQLite.
-
- Returns:
- True if the ticks table was written.
- """
- table_exists = False
- for sym in symbols:
- frame = client.copy_ticks_range_as_df(
- symbol=sym,
- date_from=date_from,
- date_to=date_to,
- flags=flags,
- ).drop(columns=["symbol"], errors="ignore")
- if len(frame.columns) != 0:
- frame.insert(0, "symbol", sym)
- table_exists = write_streamed_frame(
- conn,
- frame,
- Dataset.ticks,
- table_exists,
- if_exists,
- written_columns,
- )
- return table_exists
+ | def write_ticks_dataset(
+ conn: sqlite3.Connection,
+ client: Mt5DataClient,
+ symbols: Sequence[str],
+ flags: int,
+ date_from: datetime,
+ date_to: datetime,
+ if_exists: IfExists,
+ written_columns: dict[Dataset, set[str]],
+) -> bool:
+ """Stream ticks frames into SQLite.
+
+ Returns:
+ True if the ticks table was written.
+ """
+ table_exists = False
+ for sym in symbols:
+ frame = client.copy_ticks_range_as_df(
+ symbol=sym,
+ date_from=date_from,
+ date_to=date_to,
+ flags=flags,
+ ).drop(columns=["symbol"], errors="ignore")
+ if len(frame.columns) != 0:
+ frame.insert(0, "symbol", sym)
+ table_exists = write_streamed_frame(
+ conn,
+ frame,
+ Dataset.ticks,
+ table_exists,
+ if_exists,
+ written_columns,
+ )
+ return table_exists
|
@@ -3118,7 +3661,38 @@ when required columns are missing.
Incremental collection
The update_history SDK path uses the same base tables and optional
cash_events / positions_reconstructed views. It additionally maintains
-rate_<symbol>__<timeframe> compatibility views when create_rate_views=True.
+rate_<symbol>__<timeframe> compatibility views when create_rate_views=True.
+Rate view resolution
+Downstream tools can resolve mt5cli-managed compatibility view names from an
+existing SQLite history database without creating files or guessing legacy
+naming schemes:
+from pathlib import Path
+
+from mt5cli.history import resolve_rate_view_name, resolve_rate_view_names
+
+# Single symbol and granularity
+view = resolve_rate_view_name(Path("history.db"), "EURUSD", "M1")
+
+# Batch resolution in row-major order
+views = resolve_rate_view_names(
+ Path("history.db"),
+ ["EURUSD", "GBPUSD"],
+ ["M1", "H1"],
+)
+
+Resolution rules:
+
+- Returns
rate_<symbol>__<timeframe> when a symbol stores one timeframe.
+- Returns
rate_<symbol>__<granularity>_<timeframe> when multiple timeframes
+ are stored for the same symbol.
+- When multiple naming candidates apply, prefers an existing managed
+
rate_*__* view from the candidate list.
+- Falls back to single-timeframe naming when the database path is missing or
+
rates metadata is unavailable.
+- Pass
require_existing=True to raise ValueError instead of returning a
+ best-guess name when the database or view is missing.
+- Accepts either a SQLite path or an open
sqlite3.Connection.
+
diff --git a/api/index.html b/api/index.html
index 66f2878..ca6b72c 100644
--- a/api/index.html
+++ b/api/index.html
@@ -184,34 +184,54 @@
from pathlib import Path
from mt5cli import (
- Mt5CliClient,
- collect_history,
- copy_rates_range,
- detect_format,
- export_dataframe,
-)
-
-# Fetch rates programmatically
-rates = copy_rates_range(
- "EURUSD",
- timeframe="H1",
- date_from="2024-01-01",
- date_to="2024-02-01",
-)
-
-# Detect output format from file extension
-fmt = detect_format(Path("output.parquet")) # Returns "parquet"
-
-# Export a DataFrame
-export_dataframe(rates, Path("output.csv"), "csv")
+ Dataset,
+ IfExists,
+ Mt5CliClient,
+ collect_history,
+ copy_rates_range,
+ detect_format,
+ export_dataframe,
+ export_dataframe_to_sqlite,
+ minimum_margins,
+ recent_ticks,
+)
+from mt5cli.history import resolve_rate_view_name
+
+# Fetch rates programmatically
+rates = copy_rates_range(
+ "EURUSD",
+ timeframe="H1",
+ date_from="2024-01-01",
+ date_to="2024-02-01",
+)
-# Collect history into SQLite
-collect_history(
- Path("history.db"),
- symbols=["EURUSD"],
- date_from=datetime(2024, 1, 1, tzinfo=UTC),
- date_to=datetime(2024, 2, 1, tzinfo=UTC),
-)
+# Detect output format from file extension
+fmt = detect_format(Path("output.parquet")) # Returns "parquet"
+
+# Export a DataFrame
+export_dataframe(rates, Path("output.csv"), "csv")
+
+# Append to SQLite with deduplication
+export_dataframe_to_sqlite(
+ rates,
+ Path("history.db"),
+ "rates",
+ if_exists=IfExists.APPEND,
+ deduplicate_on=("symbol", "timeframe", "time"),
+)
+
+# Resolve rate compatibility views and fetch recent ticks
+view = resolve_rate_view_name(Path("history.db"), "EURUSD", "M1")
+ticks = recent_ticks("EURUSD", seconds=300)
+margins = minimum_margins("EURUSD")
+
+# Collect history into SQLite
+collect_history(
+ Path("history.db"),
+ symbols=["EURUSD"],
+ date_from=datetime(2024, 1, 1, tzinfo=UTC),
+ date_to=datetime(2024, 2, 1, tzinfo=UTC),
+)
Examples
See individual module pages for detailed usage examples and code samples.
diff --git a/api/sdk/index.html b/api/sdk/index.html
index 954f171..8239160 100644
--- a/api/sdk/index.html
+++ b/api/sdk/index.html
@@ -199,16 +199,18 @@
"history_orders",
"last_error",
"market_book",
- "orders",
- "positions",
- "symbol_info",
- "symbol_info_tick",
- "symbols",
- "terminal_info",
- "update_history",
- "update_history_with_config",
- "version",
-]
+ "minimum_margins",
+ "orders",
+ "positions",
+ "recent_ticks",
+ "symbol_info",
+ "symbol_info_tick",
+ "symbols",
+ "terminal_info",
+ "update_history",
+ "update_history_with_config",
+ "version",
+]
@@ -389,61 +391,61 @@
Source code in mt5cli/sdk.py
- | def __init__(
- self,
- *,
- path: str | None = None,
- login: int | None = None,
- password: str | None = None,
- server: str | None = None,
- timeout: int | None = None,
- config: Mt5Config | None = None,
-) -> None:
- """Initialize the SDK client.
-
- Args:
- path: Path to MetaTrader5 terminal EXE file.
- login: Trading account login.
- password: Trading account password.
- server: Trading server name.
- timeout: Connection timeout in milliseconds.
- config: Optional pre-built ``Mt5Config`` (overrides other args).
- """
- self._config = config or build_config(
- path=path,
- login=login,
- password=password,
- server=server,
- timeout=timeout,
- )
- self._client: Mt5DataClient | None = None
+ | def __init__(
+ self,
+ *,
+ path: str | None = None,
+ login: int | None = None,
+ password: str | None = None,
+ server: str | None = None,
+ timeout: int | None = None,
+ config: Mt5Config | None = None,
+) -> None:
+ """Initialize the SDK client.
+
+ Args:
+ path: Path to MetaTrader5 terminal EXE file.
+ login: Trading account login.
+ password: Trading account password.
+ server: Trading server name.
+ timeout: Connection timeout in milliseconds.
+ config: Optional pre-built ``Mt5Config`` (overrides other args).
+ """
+ self._config = config or build_config(
+ path=path,
+ login=login,
+ password=password,
+ server=server,
+ timeout=timeout,
+ )
+ self._client: Mt5DataClient | None = None
|
@@ -525,33 +527,33 @@
Source code in mt5cli/sdk.py
- | def __enter__(self) -> Self:
- """Open a persistent MT5 connection for multiple calls.
-
- Returns:
- This client instance.
- """
- client = Mt5DataClient(config=self._config)
- try:
- client.initialize_and_login_mt5()
- except Exception:
- client.shutdown()
- raise
- self._client = client
- return self
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|