Coverage for src/finbot/cli/main.py: 100%

54 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 17:12 +0000

1import argparse 

2import logging 

3import sys 

4from typing import Sequence 

5 

6from finbot.cli.backtest import add_arguments as add_backtest_arguments 

7from finbot.cli.backtest import add_compare_arguments 

8from finbot.cli.backtest import run as run_backtest 

9from finbot.cli.backtest import run_compare 

10from finbot.logger import configure_logging, get_logger 

11 

12logger = get_logger(__name__) 

13 

14 

15def _test_api(args: argparse.Namespace) -> int: 

16 from finbot.config import load_settings 

17 from finbot.market.alpaca import AlpacaMarketDataProvider 

18 

19 settings = load_settings(api_key=args.api_key, api_secret=args.api_secret) 

20 market_data = AlpacaMarketDataProvider( 

21 api_key=settings.alpaca_api_key, 

22 api_secret=settings.alpaca_api_secret, 

23 feed=settings.alpaca_feed, 

24 ) 

25 

26 bar = market_data.get_latest_bar("NVDA") 

27 logger.info(bar) 

28 

29 return 0 

30 

31 

32def _add_verbose_argument( 

33 parser: argparse.ArgumentParser, 

34 *, 

35 suppress_default: bool = False, 

36) -> None: 

37 kwargs = {} 

38 if suppress_default: 

39 kwargs["default"] = argparse.SUPPRESS 

40 

41 parser.add_argument( 

42 "-v", 

43 "--verbose", 

44 action="store_true", 

45 help="Enable debug logging", 

46 **kwargs, 

47 ) 

48 

49 

50def _build_parser() -> argparse.ArgumentParser: 

51 parser = argparse.ArgumentParser(description="FinBot CLI") 

52 _add_verbose_argument(parser) 

53 subparsers = parser.add_subparsers(dest="command", required=True) 

54 

55 test_api_parser = subparsers.add_parser( 

56 "test_api", 

57 help="Test the configured market data API connection", 

58 ) 

59 _add_verbose_argument(test_api_parser, suppress_default=True) 

60 test_api_parser.add_argument( 

61 "--api-key", 

62 "--api_key", 

63 dest="api_key", 

64 default=None, 

65 help="API key for the market data provider", 

66 ) 

67 test_api_parser.add_argument( 

68 "--api-secret", 

69 "--api_secret", 

70 dest="api_secret", 

71 default=None, 

72 help="API secret for the market data provider", 

73 ) 

74 

75 test_api_parser.set_defaults(func=_test_api) 

76 

77 backtest_parser = subparsers.add_parser( 

78 "backtest", 

79 help="Run or inspect reproducible backtest experiments", 

80 ) 

81 _add_verbose_argument(backtest_parser, suppress_default=True) 

82 add_backtest_arguments(backtest_parser) 

83 backtest_parser.set_defaults(func=run_backtest) 

84 

85 backtest_subparsers = backtest_parser.add_subparsers(dest="backtest_command") 

86 

87 compare_parser = backtest_subparsers.add_parser( 

88 "compare", 

89 help="Compare stored backtest experiments", 

90 ) 

91 _add_verbose_argument(compare_parser, suppress_default=True) 

92 add_compare_arguments(compare_parser) 

93 compare_parser.set_defaults(func=run_compare) 

94 

95 return parser 

96 

97 

98def main(argv: Sequence[str] | None = None) -> int: 

99 parser = _build_parser() 

100 args = parser.parse_args(argv) 

101 

102 level = logging.DEBUG if args.verbose else logging.INFO 

103 configure_logging(level) 

104 

105 try: 

106 return args.func(args) 

107 except (ValueError, RuntimeError) as exc: 

108 if args.verbose: 

109 logger.exception("%s", exc) 

110 else: 

111 logger.error("%s", exc) 

112 return 1 

113 

114 

115if __name__ == "__main__": 

116 sys.exit(main())