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:
Daichi Narushima
2026-06-28 01:23:17 +09:00
committed by GitHub
parent 8028263b24
commit 43f632bc40
5 changed files with 128 additions and 45 deletions
+25 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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"}]
+60
View File
@@ -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
# ---------------------------------------------------------------------------
Generated
+1 -1
View File
@@ -487,7 +487,7 @@ wheels = [
[[package]]
name = "mt5cli"
version = "1.0.0"
version = "1.0.1"
source = { editable = "." }
dependencies = [
{ name = "click" },