Reorganize CLI help text and command grouping for data/execution clarity (#85)
* feat: clarify CLI/docs scope as generic MT5 data and execution infrastructure - Update app help text and module docstring to describe mt5cli as MT5 data and execution utilities rather than export-only tooling - Group CLI commands under rich_help_panel sections: Data / Export, Execution, and Collection; command names are unchanged for compatibility - Expand order-send docstring to explicitly flag it as the expert raw-request live-trading path; preserve --yes gate - Split docs/index.md Trading section into "Trading State" (read-only) and "Execution (live / mutating)" with close-positions now documented - Add TestHelpText tests verifying top-level panel grouping, order-send expert/live language, and close-positions safety gate coverage Closes #78 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018yH6esaqc5D1cmo1dK2Ur9 * chore: trim trailing whitespace in docs/index.md table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018yH6esaqc5D1cmo1dK2Ur9 * chore: bump version to 1.0.1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018yH6esaqc5D1cmo1dK2Ur9 * chore: update uv.lock for version 1.0.1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018yH6esaqc5D1cmo1dK2Ur9 * fix: address review feedback on CLI/docs scope PR - Remove dead help invocation in test_order_send_help_mentions_expert_and_raw (the result was immediately overwritten by result2) - Strengthen assertion from `or` to `and`; both "raw" and "expert" are present in the docstring so disjunction masked a potential regression - Split into two `assert` statements to satisfy PT018 (ruff) - Fix docs/index.md inaccuracy: order-check has no --yes gate; clarify that only order-send and close-positions require confirmation for live execution - Move order-check from "Execution" rich_help_panel to "Data / Export" so the Execution panel name is truthful (order-check is read-only) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018yH6esaqc5D1cmo1dK2Ur9 * docs: move order-check out of Execution section into Trading State order-check is read-only and now lives in the CLI's Data / Export panel, so documenting it under "Execution (live / mutating)" was inconsistent. Moved it to the Trading State table. The Execution section now only lists order-send and close-positions, both of which require --yes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018yH6esaqc5D1cmo1dK2Ur9 --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
+25
-13
@@ -147,20 +147,32 @@ mt5cli --login 12345 --password mypass --server MyBroker-Demo \
|
||||
| `minimum-margins` | Export minimum-volume margin summary |
|
||||
| `market-book` | Export market depth (order book) |
|
||||
|
||||
### Trading
|
||||
### Trading State
|
||||
|
||||
| Command | Description |
|
||||
| ---------------------- | ----------------------------------------------------------- |
|
||||
| `orders` | Export active orders |
|
||||
| `positions` | Export open positions |
|
||||
| `history-orders` | Export historical orders |
|
||||
| `history-deals` | Export historical deals |
|
||||
| `recent-history-deals` | Export historical deals from a trailing window |
|
||||
| `mt5-summary` | Export terminal/account status summary |
|
||||
| `order-check` | Check funds sufficiency for a trade request |
|
||||
| `order-send` | Send a trade request to the trade server (`--yes` required) |
|
||||
| Command | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------- |
|
||||
| `orders` | Export active orders |
|
||||
| `positions` | Export open positions |
|
||||
| `history-orders` | Export historical orders |
|
||||
| `history-deals` | Export historical deals |
|
||||
| `recent-history-deals` | Export historical deals from a trailing window |
|
||||
| `mt5-summary` | Export terminal/account status summary |
|
||||
| `order-check` | Check funds sufficiency for a trade request (read-only, no `--yes`) |
|
||||
|
||||
Use `order-check` to validate a request payload before running `order-send --yes`.
|
||||
### Execution (live / mutating)
|
||||
|
||||
These commands send requests to the live trade server and can place or close
|
||||
real trades. Both require `--yes` for live execution.
|
||||
|
||||
| Command | Description |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `order-send` | Send a **raw** trade request directly to MT5 (`--yes` required; expert path — no extra validation) |
|
||||
| `close-positions` | Close open positions by `--symbol` or `--ticket` (`--yes` required for live; `--dry-run` to preview) |
|
||||
|
||||
Use `order-check` (Trading State) to validate funds before running `order-send --yes`.
|
||||
`close-positions` is the safer high-level alternative that builds correct close
|
||||
requests automatically. `order-send` is the expert raw path — downstream
|
||||
applications should prefer dedicated closing helpers or their own risk controls.
|
||||
|
||||
### Bulk Collection
|
||||
|
||||
@@ -217,7 +229,7 @@ See the [History schema diagram](api/history.md#entity-relationship-diagram) for
|
||||
|
||||
Browse the API documentation for detailed module information:
|
||||
|
||||
- [CLI Module](api/cli.md) - CLI application with export commands
|
||||
- [CLI Module](api/cli.md) - CLI application with data export and execution commands
|
||||
- [SDK Module](api/sdk.md) - Programmatic read-only data collection API
|
||||
- [Utils Module](api/utils.md) - Constants, parameter types, parsers, and export utilities
|
||||
|
||||
|
||||
+41
-30
@@ -1,4 +1,4 @@
|
||||
"""Command-line interface for MetaTrader 5 data export."""
|
||||
"""Command-line interface for MetaTrader 5 data and execution utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -55,7 +55,12 @@ class _ExportContext:
|
||||
|
||||
app = typer.Typer(
|
||||
name="mt5cli",
|
||||
help="Export MetaTrader5 data to CSV, JSON, Parquet, or SQLite3.",
|
||||
help=(
|
||||
"MT5 data and execution utilities — read market data, inspect account"
|
||||
" state, and send trade requests. Data commands write to CSV, JSON,"
|
||||
" Parquet, or SQLite3. Execution commands (order-send, close-positions)"
|
||||
" require --yes for live mutations."
|
||||
),
|
||||
)
|
||||
|
||||
_REQUEST_OPTION_HELP = (
|
||||
@@ -151,7 +156,7 @@ def _callback( # pyright: ignore[reportUnusedFunction]
|
||||
typer.Option("--log-level", help="Logging level."),
|
||||
] = LogLevel.WARNING,
|
||||
) -> None:
|
||||
"""Configure shared options for all export commands.
|
||||
"""Configure shared connection and output options.
|
||||
|
||||
Raises:
|
||||
typer.BadParameter: If the output format cannot be determined.
|
||||
@@ -183,7 +188,7 @@ def _callback( # pyright: ignore[reportUnusedFunction]
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def rates_from(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -210,7 +215,7 @@ def rates_from(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def rates_from_pos(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -236,7 +241,7 @@ def rates_from_pos(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def latest_rates(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -265,7 +270,7 @@ def latest_rates(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def rates_range(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -292,7 +297,7 @@ def rates_range(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def ticks_from(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -316,7 +321,7 @@ def ticks_from(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def ticks_range(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -340,7 +345,7 @@ def ticks_range(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def ticks_recent(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -377,19 +382,19 @@ def ticks_recent(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def account_info(ctx: typer.Context) -> None:
|
||||
"""Export account information."""
|
||||
_export_command(ctx, lambda client: client.account_info())
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def terminal_info(ctx: typer.Context) -> None:
|
||||
"""Export terminal information."""
|
||||
_export_command(ctx, lambda client: client.terminal_info())
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def symbols(
|
||||
ctx: typer.Context,
|
||||
group: Annotated[
|
||||
@@ -401,7 +406,7 @@ def symbols(
|
||||
_export_command(ctx, lambda client: client.symbols(group=group))
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def symbol_info(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -410,7 +415,7 @@ def symbol_info(
|
||||
_export_command(ctx, lambda client: client.symbol_info(symbol))
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def minimum_margins(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -419,7 +424,7 @@ def minimum_margins(
|
||||
_export_command(ctx, lambda client: client.minimum_margins(symbol))
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def orders(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
|
||||
@@ -433,7 +438,7 @@ def orders(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def positions(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str | None, typer.Option(help="Symbol filter.")] = None,
|
||||
@@ -447,7 +452,7 @@ def positions(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def history_orders(
|
||||
ctx: typer.Context,
|
||||
date_from: Annotated[
|
||||
@@ -477,7 +482,7 @@ def history_orders(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def history_deals(
|
||||
ctx: typer.Context,
|
||||
date_from: Annotated[
|
||||
@@ -507,7 +512,7 @@ def history_deals(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def recent_history_deals(
|
||||
ctx: typer.Context,
|
||||
hours: Annotated[float, typer.Option(help="Lookback window in hours.")],
|
||||
@@ -530,25 +535,25 @@ def recent_history_deals(
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def mt5_summary(ctx: typer.Context) -> None:
|
||||
"""Export a compact terminal/account status summary."""
|
||||
_export_command(ctx, lambda client: client.mt5_summary_as_df())
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def version(ctx: typer.Context) -> None:
|
||||
"""Export MetaTrader5 version information."""
|
||||
_export_command(ctx, lambda client: client.version())
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def last_error(ctx: typer.Context) -> None:
|
||||
"""Export the last error information."""
|
||||
_export_command(ctx, lambda client: client.last_error())
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def symbol_info_tick(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -557,7 +562,7 @@ def symbol_info_tick(
|
||||
_export_command(ctx, lambda client: client.symbol_info_tick(symbol))
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def market_book(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[str, typer.Option(help="Symbol name.")],
|
||||
@@ -566,7 +571,7 @@ def market_book(
|
||||
_export_command(ctx, lambda client: client.market_book(symbol))
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Data / Export")
|
||||
def order_check(
|
||||
ctx: typer.Context,
|
||||
request: Annotated[
|
||||
@@ -578,7 +583,7 @@ def order_check(
|
||||
_export_command(ctx, lambda client: client.order_check(request))
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Execution")
|
||||
def order_send(
|
||||
ctx: typer.Context,
|
||||
request: Annotated[
|
||||
@@ -590,7 +595,13 @@ def order_send(
|
||||
typer.Option("--yes", help="Confirm the live trade request."),
|
||||
] = False,
|
||||
) -> None:
|
||||
"""Send a trading operation request to the trade server.
|
||||
"""Send a raw trade request to the trade server (expert path, live execution).
|
||||
|
||||
Passes the request JSON directly to MT5 ``order_send``. This is the
|
||||
low-level expert path — it places real trades on the connected account
|
||||
with no additional validation beyond what MT5 itself performs. Use
|
||||
``order-check`` first to validate funds sufficiency. Prefer
|
||||
``close-positions`` for closing open positions. ``--yes`` is required.
|
||||
|
||||
Raises:
|
||||
typer.BadParameter: If --yes is not provided.
|
||||
@@ -628,7 +639,7 @@ def _execution_results_to_df(results: list[OrderExecutionResult]) -> pd.DataFram
|
||||
return pd.DataFrame(rows)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Execution")
|
||||
def close_positions(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[
|
||||
@@ -691,7 +702,7 @@ def close_positions(
|
||||
_execute_export(ctx, lambda: df)
|
||||
|
||||
|
||||
@app.command()
|
||||
@app.command(rich_help_panel="Collection")
|
||||
def collect_history(
|
||||
ctx: typer.Context,
|
||||
symbol: Annotated[
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mt5cli"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
description = "Generic MT5 data and execution infrastructure for Python applications"
|
||||
authors = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
|
||||
maintainers = [{name = "dceoy", email = "dceoy@users.noreply.github.com"}]
|
||||
|
||||
@@ -740,6 +740,66 @@ class TestCommands:
|
||||
assert "must be a JSON object" in normalize_cli_output(result.output)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Help text / scope tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestHelpText:
|
||||
"""Tests verifying CLI help text matches the documented scope."""
|
||||
|
||||
def test_top_level_help_mentions_execution(self) -> None:
|
||||
"""Top-level help must describe execution utilities, not export only."""
|
||||
result = runner.invoke(app, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
output = normalize_cli_output(result.output)
|
||||
assert "execution" in output.lower()
|
||||
|
||||
def test_top_level_help_has_execution_panel(self) -> None:
|
||||
"""Top-level help must show an Execution command group."""
|
||||
result = runner.invoke(app, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Execution" in result.output
|
||||
|
||||
def test_top_level_help_has_data_export_panel(self) -> None:
|
||||
"""Top-level help must show a Data / Export command group."""
|
||||
result = runner.invoke(app, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "Data / Export" in result.output
|
||||
|
||||
def test_order_send_help_mentions_expert_and_raw(self) -> None:
|
||||
"""order-send help must communicate it is the expert raw-request path."""
|
||||
result2 = runner.invoke(
|
||||
app,
|
||||
["-o", "out.csv", "order-send", "--help"],
|
||||
)
|
||||
assert result2.exit_code == 0
|
||||
output = normalize_cli_output(result2.output)
|
||||
assert "raw" in output.lower()
|
||||
assert "expert" in output.lower()
|
||||
|
||||
def test_order_send_help_mentions_live_execution(self) -> None:
|
||||
"""order-send help must warn about live execution."""
|
||||
result = runner.invoke(
|
||||
app,
|
||||
["-o", "out.csv", "order-send", "--help"],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
output = normalize_cli_output(result.output)
|
||||
assert "live" in output.lower()
|
||||
|
||||
def test_close_positions_help_mentions_dry_run_and_yes(self) -> None:
|
||||
"""close-positions help must document both safety gates."""
|
||||
result = runner.invoke(
|
||||
app,
|
||||
["-o", "out.csv", "close-positions", "--help"],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
output = normalize_cli_output(result.output)
|
||||
assert "--dry-run" in output
|
||||
assert "--yes" in output
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# close-positions command
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user