bbstrader 2.0.3__cp312-cp312-macosx_11_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. bbstrader/__init__.py +27 -0
  2. bbstrader/__main__.py +92 -0
  3. bbstrader/api/__init__.py +96 -0
  4. bbstrader/api/handlers.py +245 -0
  5. bbstrader/api/metatrader_client.cpython-312-darwin.so +0 -0
  6. bbstrader/api/metatrader_client.pyi +624 -0
  7. bbstrader/assets/bbs_.png +0 -0
  8. bbstrader/assets/bbstrader.ico +0 -0
  9. bbstrader/assets/bbstrader.png +0 -0
  10. bbstrader/assets/qs_metrics_1.png +0 -0
  11. bbstrader/btengine/__init__.py +54 -0
  12. bbstrader/btengine/backtest.py +358 -0
  13. bbstrader/btengine/data.py +737 -0
  14. bbstrader/btengine/event.py +229 -0
  15. bbstrader/btengine/execution.py +287 -0
  16. bbstrader/btengine/performance.py +408 -0
  17. bbstrader/btengine/portfolio.py +393 -0
  18. bbstrader/btengine/strategy.py +588 -0
  19. bbstrader/compat.py +28 -0
  20. bbstrader/config.py +100 -0
  21. bbstrader/core/__init__.py +27 -0
  22. bbstrader/core/data.py +628 -0
  23. bbstrader/core/strategy.py +466 -0
  24. bbstrader/metatrader/__init__.py +48 -0
  25. bbstrader/metatrader/_copier.py +720 -0
  26. bbstrader/metatrader/account.py +865 -0
  27. bbstrader/metatrader/broker.py +418 -0
  28. bbstrader/metatrader/copier.py +1487 -0
  29. bbstrader/metatrader/rates.py +495 -0
  30. bbstrader/metatrader/risk.py +667 -0
  31. bbstrader/metatrader/trade.py +1692 -0
  32. bbstrader/metatrader/utils.py +402 -0
  33. bbstrader/models/__init__.py +39 -0
  34. bbstrader/models/nlp.py +932 -0
  35. bbstrader/models/optimization.py +182 -0
  36. bbstrader/scripts.py +665 -0
  37. bbstrader/trading/__init__.py +33 -0
  38. bbstrader/trading/execution.py +1159 -0
  39. bbstrader/trading/strategy.py +362 -0
  40. bbstrader/trading/utils.py +69 -0
  41. bbstrader-2.0.3.dist-info/METADATA +396 -0
  42. bbstrader-2.0.3.dist-info/RECORD +45 -0
  43. bbstrader-2.0.3.dist-info/WHEEL +5 -0
  44. bbstrader-2.0.3.dist-info/entry_points.txt +3 -0
  45. bbstrader-2.0.3.dist-info/licenses/LICENSE +21 -0
bbstrader/scripts.py ADDED
@@ -0,0 +1,665 @@
1
+ import argparse
2
+ import asyncio
3
+ import importlib
4
+ import importlib.util
5
+ import json
6
+ import multiprocessing
7
+ import multiprocessing as mp
8
+ import os
9
+ import sys
10
+ import textwrap
11
+ import time
12
+ from datetime import datetime, timedelta
13
+ from types import ModuleType
14
+ from typing import Any, Dict, List, Literal, Type
15
+
16
+ import nltk
17
+ from loguru import logger
18
+ from sumy.nlp.tokenizers import Tokenizer
19
+ from sumy.parsers.plaintext import PlaintextParser
20
+ from sumy.summarizers.text_rank import TextRankSummarizer
21
+
22
+ from bbstrader.btengine.backtest import run_backtest
23
+ from bbstrader.btengine.data import (
24
+ CSVDataHandler,
25
+ DataHandler,
26
+ EODHDataHandler,
27
+ FMPDataHandler,
28
+ MT5DataHandler,
29
+ YFDataHandler,
30
+ )
31
+ from bbstrader.btengine.execution import (
32
+ ExecutionHandler,
33
+ MT5ExecutionHandler,
34
+ SimExecutionHandler,
35
+ )
36
+ from bbstrader.btengine.strategy import BaseStrategy
37
+ from bbstrader.core.data import FinancialNews
38
+ from bbstrader.core.strategy import Strategy
39
+ from bbstrader.metatrader._copier import main as RunCopyApp
40
+ from bbstrader.metatrader.copier import RunCopier, config_copier, copier_worker_process
41
+ from bbstrader.metatrader.trade import create_trade_instance
42
+ from bbstrader.trading.execution import RunMt5Engine
43
+ from bbstrader.trading.strategy import LiveStrategy
44
+ from bbstrader.trading.utils import send_telegram_message
45
+
46
+ EXECUTION_PATH = os.path.expanduser("~/.bbstrader/execution/execution.py")
47
+ CONFIG_PATH = os.path.expanduser("~/.bbstrader/execution/execution.json")
48
+ BACKTEST_PATH = os.path.expanduser("~/.bbstrader/backtest/backtest.py")
49
+ CONFIG_PATH = os.path.expanduser("~/.bbstrader/backtest/backtest.json")
50
+
51
+
52
+ DATA_HANDLER_MAP: Dict[str, Type[DataHandler]] = {
53
+ "csv": CSVDataHandler,
54
+ "mt5": MT5DataHandler,
55
+ "yf": YFDataHandler,
56
+ "eodh": EODHDataHandler,
57
+ "fmp": FMPDataHandler,
58
+ }
59
+
60
+ EXECUTION_HANDLER_MAP: Dict[str, Type[ExecutionHandler]] = {
61
+ "sim": SimExecutionHandler,
62
+ "mt5": MT5ExecutionHandler,
63
+ }
64
+
65
+
66
+ __all__ = ["load_module", "load_class"]
67
+
68
+
69
+ def load_module(file_path: str) -> ModuleType:
70
+ """Load a module from a file path.
71
+ Args:
72
+ file_path: Path to the file to load.
73
+ Returns:
74
+ The loaded module.
75
+ """
76
+ if not os.path.exists(file_path):
77
+ raise FileNotFoundError(
78
+ f"Strategy file {file_path} not found. Please create it."
79
+ )
80
+ spec = importlib.util.spec_from_file_location("bbstrader.cli", file_path)
81
+ if spec is None or spec.loader is None:
82
+ raise ImportError(f"Could not load spec for module at {file_path}")
83
+ module = importlib.util.module_from_spec(spec)
84
+ spec.loader.exec_module(module)
85
+ return module
86
+
87
+
88
+ def load_class(module: ModuleType, class_name: str, base_class: Type) -> Type:
89
+ """Load a class from a module.
90
+ Args:
91
+ module: The module to load the class from.
92
+ class_name: The name of the class to load.
93
+ base_class: The base class that the class must inherit from.
94
+ """
95
+ if not hasattr(module, class_name):
96
+ raise AttributeError(f"{class_name} not found in {module}")
97
+ class_ = getattr(module, class_name)
98
+ if not issubclass(class_, base_class):
99
+ raise TypeError(f"{class_name} must inherit from {base_class}.")
100
+ return class_
101
+
102
+
103
+ ##############################################################
104
+ ###################### BACKTESTING ###########################
105
+ ##############################################################
106
+
107
+
108
+ def load_exc_handler(module: ModuleType, handler_name: str) -> Type[ExecutionHandler]:
109
+ return load_class(module, handler_name, ExecutionHandler) # type: ignore
110
+
111
+
112
+ def load_data_handler(module: ModuleType, handler_name: str) -> Type[DataHandler]:
113
+ return load_class(module, handler_name, DataHandler) # type: ignore
114
+
115
+
116
+ def load_strategy(module: ModuleType, strategy_name: str) -> Type[Strategy]:
117
+ return load_class(module, strategy_name, (Strategy, BaseStrategy)) # type: ignore
118
+
119
+
120
+ def load_backtest_config(config_path: str, strategy_name: str) -> Dict[str, Any]:
121
+ if not os.path.exists(config_path):
122
+ raise FileNotFoundError(
123
+ f"Configuration file {config_path} not found. Please create it."
124
+ )
125
+
126
+ with open(config_path, "r") as f:
127
+ config = json.load(f)
128
+ try:
129
+ config = config[strategy_name]
130
+ except KeyError:
131
+ raise ValueError(
132
+ f"Strategy {strategy_name} not found in the configuration file."
133
+ )
134
+
135
+ required_fields = ["symbol_list", "start_date", "data_handler", "execution_handler"]
136
+ for field in required_fields:
137
+ if not config.get(field):
138
+ raise ValueError(f"{field} is required in the configuration file.")
139
+
140
+ config["start_date"] = datetime.strptime(config["start_date"], "%Y-%m-%d")
141
+
142
+ if config.get("execution_handler") not in EXECUTION_HANDLER_MAP:
143
+ try:
144
+ backtest_module = load_module(BACKTEST_PATH)
145
+ exc_handler_class = load_exc_handler(
146
+ backtest_module, config["execution_handler"]
147
+ )
148
+ except Exception as e:
149
+ raise ValueError(f"Invalid execution handler: {e}")
150
+ else:
151
+ exc_handler_class = EXECUTION_HANDLER_MAP[config["execution_handler"]]
152
+
153
+ if config.get("data_handler") not in DATA_HANDLER_MAP:
154
+ try:
155
+ backtest_module = load_module(BACKTEST_PATH)
156
+ data_handler_class = load_data_handler(
157
+ backtest_module, config["data_handler"]
158
+ )
159
+ except Exception as e:
160
+ raise ValueError(f"Invalid data handler: {e}")
161
+ else:
162
+ data_handler_class = DATA_HANDLER_MAP[config["data_handler"]]
163
+
164
+ config["execution_handler"] = exc_handler_class
165
+ config["data_handler"] = data_handler_class
166
+
167
+ return config
168
+
169
+
170
+ def backtest(unknown: List[str]) -> None:
171
+ HELP_MSG = """
172
+ Usage:
173
+ python -m bbstrader --run backtest [options]
174
+
175
+ Options:
176
+ -s, --strategy: Strategy class name to run
177
+ -c, --config: Configuration file path (default: ~/.bbstrader/backtest/backtest.json)
178
+ -p, --path: Path to the backtest file (default: ~/.bbstrader/backtest/backtest.py)
179
+
180
+ Note:
181
+ The configuration file must contain all the required parameters
182
+ for the data handler and execution handler and strategy.
183
+ See bbstrader.btengine.BacktestEngine for more details on the parameters.
184
+ """
185
+ if "-h" in unknown or "--help" in unknown:
186
+ print(HELP_MSG)
187
+ sys.exit(0)
188
+
189
+ parser = argparse.ArgumentParser(description="Backtesting Engine CLI")
190
+ parser.add_argument(
191
+ "-s", "--strategy", type=str, required=True, help="Strategy class name to run"
192
+ )
193
+ parser.add_argument(
194
+ "-c", "--config", type=str, default=CONFIG_PATH, help="Configuration file path"
195
+ )
196
+ parser.add_argument(
197
+ "-p",
198
+ "--path",
199
+ type=str,
200
+ default=BACKTEST_PATH,
201
+ help="Path to the backtest file",
202
+ )
203
+ args = parser.parse_args(unknown)
204
+ config = load_backtest_config(args.config, args.strategy)
205
+ strategy_module = load_module(args.path)
206
+ strategy_class = load_strategy(strategy_module, args.strategy)
207
+
208
+ symbol_list = config.pop("symbol_list")
209
+ start_date = config.pop("start_date")
210
+ data_handler = config.pop("data_handler")
211
+ execution_handler = config.pop("execution_handler")
212
+
213
+ try:
214
+ run_backtest(
215
+ symbol_list,
216
+ start_date,
217
+ data_handler,
218
+ strategy_class,
219
+ exc_handler=execution_handler,
220
+ **config,
221
+ )
222
+ except Exception as e:
223
+ print(f"Error: {e}")
224
+
225
+
226
+ ##############################################################
227
+ ###################### LIVE EXECUTION ########################
228
+ ##############################################################
229
+
230
+
231
+ def load_live_config(config_path, strategy_name, account=None):
232
+ if not os.path.exists(config_path):
233
+ raise FileNotFoundError(f"Configuration file not found at {config_path}")
234
+ with open(config_path, "r") as f:
235
+ config = json.load(f)
236
+ try:
237
+ config = config[strategy_name]
238
+ except KeyError:
239
+ raise ValueError(
240
+ f"Strategy {strategy_name} not found in the configuration file."
241
+ )
242
+ if account is not None:
243
+ try:
244
+ config = config[account]
245
+ except KeyError:
246
+ raise ValueError(f"Account {account} not found in the configuration file.")
247
+ if config.get("symbol_list") is None:
248
+ raise ValueError("symbol_list is required in the configuration file.")
249
+ if config.get("trades_kwargs") is None:
250
+ raise ValueError("trades_kwargs is required in the configuration file.")
251
+ return config
252
+
253
+
254
+ def worker_function(account, args):
255
+ strategy_module = load_module(args.path)
256
+ strategy_class = load_class(
257
+ strategy_module, args.strategy, (LiveStrategy, Strategy)
258
+ )
259
+
260
+ config = load_live_config(args.config, args.strategy, account)
261
+ symbol_list = config.pop("symbol_list")
262
+ trades_kwargs = config.pop("trades_kwargs")
263
+ trades = create_trade_instance(symbol_list, trades_kwargs)
264
+
265
+ kwargs = {
266
+ "symbol_list": symbol_list,
267
+ "trades_instances": trades,
268
+ "strategy_cls": strategy_class,
269
+ "account": account,
270
+ **config,
271
+ }
272
+ RunMt5Engine(account, **kwargs)
273
+
274
+
275
+ def RunMt5Terminal(args):
276
+ if args.parallel:
277
+ if len(args.account) == 0:
278
+ raise ValueError(
279
+ "account or accounts are required when running in parallel"
280
+ )
281
+
282
+ processes = []
283
+ try:
284
+ for account in args.account:
285
+ p = mp.Process(target=worker_function, args=(account, args))
286
+ p.start()
287
+ processes.append(p)
288
+
289
+ for p in processes:
290
+ p.join()
291
+ except Exception as e:
292
+ print(f"Error in parallel execution: {e}")
293
+ raise e
294
+ except KeyboardInterrupt:
295
+ print("\nTerminating Execution...")
296
+ for p in processes:
297
+ p.terminate()
298
+ for p in processes:
299
+ p.join()
300
+ print("Execution terminated")
301
+ else:
302
+ worker_function(args.account[0], args)
303
+
304
+
305
+ def RunTWSTerminal(args):
306
+ raise NotImplementedError("RunTWSTerminal is not implemented yet")
307
+
308
+
309
+ def execute_strategy(unknown):
310
+ HELP_MSG = """
311
+ Execute a strategy on one or multiple MT5 accounts.
312
+
313
+ Usage:
314
+ python -m bbstrader --run execution [options]
315
+
316
+ Options:
317
+ -s, --strategy: Strategy class name to run
318
+ -a, --account: Account(s) name(s) or ID(s) to run the strategy on (must be the same as in the configuration file)
319
+ -p, --path: Path to the execution file (default: ~/.bbstrader/execution/execution.py)
320
+ -c, --config: Path to the configuration file (default: ~/.bbstrader/execution/execution.json)
321
+ -l, --parallel: Run the strategy in parallel (default: False)
322
+ -t, --terminal: Terminal to use (default: MT5)
323
+ -h, --help: Show this help message and exit
324
+
325
+ Note:
326
+ The configuration file must contain all the required parameters
327
+ to create trade instances for each account and strategy.
328
+ The configuration file must be a dictionary with the following structure:
329
+ If parallel is True:
330
+ {
331
+ "strategy_name": {
332
+ "account_name": {
333
+ "symbol_list": ["symbol1", "symbol2"],
334
+ "trades_kwargs": {"param1": "value1", "param2": "value2"}
335
+ **other_parameters (for the strategy and the execution engine)
336
+ }
337
+ }
338
+ }
339
+ If parallel is False:
340
+ {
341
+ "strategy_name": {
342
+ "symbol_list": ["symbol1", "symbol2"],
343
+ "trades_kwargs": {"param1": "value1", "param2": "value2"}
344
+ **other_parameters (for the strategy and the execution engine)
345
+ }
346
+ }
347
+ See bbstrader.metatrader.trade.create_trade_instance for more details on the trades_kwargs.
348
+ See bbstrader.trading.execution.Mt5ExecutionEngine for more details on the other parameters.
349
+
350
+ All other paramaters must be python built-in types.
351
+ If you have custom type you must set them in your strategy class
352
+ or run the Mt5ExecutionEngine directly, don't run on CLI.
353
+ """
354
+ if "-h" in unknown or "--help" in unknown:
355
+ print(HELP_MSG)
356
+ sys.exit(0)
357
+
358
+ parser = argparse.ArgumentParser()
359
+ parser.add_argument("-s", "--strategy", type=str, required=True)
360
+ parser.add_argument("-a", "--account", type=str, nargs="*", default=[])
361
+ parser.add_argument("-p", "--path", type=str, default=EXECUTION_PATH)
362
+ parser.add_argument("-c", "--config", type=str, default=CONFIG_PATH)
363
+ parser.add_argument("-l", "--parallel", action="store_true")
364
+ parser.add_argument(
365
+ "-t", "--terminal", type=str, default="MT5", choices=["MT5", "TWS"]
366
+ )
367
+ args = parser.parse_args(unknown)
368
+
369
+ if args.terminal == "MT5":
370
+ RunMt5Terminal(args)
371
+ elif args.terminal == "TWS":
372
+ RunTWSTerminal(args)
373
+
374
+
375
+ ##########################################################
376
+ ##################### TRADE COPIER #######################
377
+ ##########################################################
378
+
379
+
380
+ def copier_args(parser: argparse.ArgumentParser):
381
+ parser.add_argument(
382
+ "-m",
383
+ "--mode",
384
+ type=str,
385
+ default="CLI",
386
+ choices=("CLI", "GUI"),
387
+ help="Run the copier in the terminal or using the GUI",
388
+ )
389
+ parser.add_argument(
390
+ "-s", "--source", type=str, nargs="?", default=None, help="Source section name"
391
+ )
392
+ parser.add_argument(
393
+ "-I", "--id", type=int, default=0, help="Source Account unique ID"
394
+ )
395
+ parser.add_argument(
396
+ "-U",
397
+ "--unique",
398
+ action="store_true",
399
+ help="Specify if the source account is only master",
400
+ )
401
+ parser.add_argument(
402
+ "-d",
403
+ "--destinations",
404
+ type=str,
405
+ nargs="*",
406
+ default=None,
407
+ help="Destination section names",
408
+ )
409
+ parser.add_argument(
410
+ "-i", "--interval", type=float, default=0.1, help="Update interval in seconds"
411
+ )
412
+ parser.add_argument(
413
+ "-c",
414
+ "--config",
415
+ nargs="?",
416
+ default=None,
417
+ type=str,
418
+ help="Config file name or path",
419
+ )
420
+ parser.add_argument(
421
+ "-t",
422
+ "--start",
423
+ type=str,
424
+ nargs="?",
425
+ default=None,
426
+ help="Start time in HH:MM format",
427
+ )
428
+ parser.add_argument(
429
+ "-e",
430
+ "--end",
431
+ type=str,
432
+ nargs="?",
433
+ default=None,
434
+ help="End time in HH:MM format",
435
+ )
436
+ parser.add_argument(
437
+ "-M",
438
+ "--multiprocess",
439
+ action="store_true",
440
+ help="Run each destination account in a separate process.",
441
+ )
442
+ return parser
443
+
444
+
445
+ def copy_trades(unknown):
446
+ HELP_MSG = """
447
+ Usage:
448
+ python -m bbstrader --run copier [options]
449
+
450
+ Options:
451
+ -m, --mode: CLI for terminal app and GUI for Desktop app
452
+ -s, --source: Source Account section name
453
+ -I, --id: Source Account unique ID
454
+ -U, --unique: Specify if the source account is only master
455
+ -d, --destinations: Destination Account section names (multiple allowed)
456
+ -i, --interval: Update interval in seconds
457
+ -M, --multiprocess: When set to True, each destination account runs in a separate process.
458
+ -c, --config: .ini file or path (default: ~/.bbstrader/copier/copier.ini)
459
+ -t, --start: Start time in HH:MM format
460
+ -e, --end: End time in HH:MM format
461
+ """
462
+ if "-h" in unknown or "--help" in unknown:
463
+ print(HELP_MSG)
464
+ sys.exit(0)
465
+
466
+ copy_parser = argparse.ArgumentParser("Trades Copier", add_help=False)
467
+ copy_parser = copier_args(copy_parser)
468
+ copy_args = copy_parser.parse_args(unknown)
469
+
470
+ if copy_args.mode == "GUI":
471
+ RunCopyApp()
472
+
473
+ elif copy_args.mode == "CLI":
474
+ source, destinations = config_copier(
475
+ source_section=copy_args.source,
476
+ dest_sections=copy_args.destinations,
477
+ inifile=copy_args.config,
478
+ )
479
+ source["id"] = copy_args.id
480
+ source["unique"] = copy_args.unique
481
+ if copy_args.multiprocess:
482
+ copier_processes = []
483
+ for dest_config in destinations:
484
+ process = multiprocessing.Process(
485
+ target=copier_worker_process,
486
+ args=(
487
+ source,
488
+ dest_config,
489
+ copy_args.interval,
490
+ copy_args.start,
491
+ copy_args.end,
492
+ ),
493
+ )
494
+ process.start()
495
+ copier_processes.append(process)
496
+ for process in copier_processes:
497
+ process.join()
498
+ else:
499
+ RunCopier(
500
+ source,
501
+ destinations,
502
+ copy_args.interval,
503
+ copy_args.start,
504
+ copy_args.end,
505
+ )
506
+
507
+
508
+ ############################################################
509
+ ##################### NEWS FEED ############################
510
+ ############################################################
511
+
512
+
513
+ def summarize_text(text: str, sentences_count: int = 5) -> str:
514
+ """
515
+ Generate a summary using TextRank algorithm.
516
+ """
517
+ parser = PlaintextParser.from_string(text, Tokenizer("english"))
518
+ summarizer = TextRankSummarizer()
519
+ summary = summarizer(parser.document, sentences_count)
520
+ return " ".join(str(sentence) for sentence in summary)
521
+
522
+
523
+ def format_coindesk_article(article: Dict[str, Any]) -> str:
524
+ if not all(
525
+ k in article
526
+ for k in (
527
+ "body",
528
+ "title",
529
+ "published_on",
530
+ "sentiment",
531
+ "keywords",
532
+ "status",
533
+ "url",
534
+ )
535
+ ):
536
+ return ""
537
+ summary = summarize_text(article["body"], sentences_count=3)
538
+ text = (
539
+ f"📰 {article['title']}\n"
540
+ f"Published Date: {article['published_on']}\n"
541
+ f"Sentiment: {article['sentiment']}\n"
542
+ f"Status: {article['status']}\n"
543
+ f"Keywords: {article['keywords']}\n\n"
544
+ f"🔍 Summary\n"
545
+ f"{textwrap.fill(summary, width=80)}"
546
+ f"\n\n👉 Visit {article['url']} for full article."
547
+ )
548
+ return text
549
+
550
+
551
+ def format_fmp_article(article: Dict[str, Any]) -> str:
552
+ if not all(k in article for k in ("title", "date", "content", "tickers")):
553
+ return ""
554
+ summary = summarize_text(article["content"], sentences_count=3)
555
+ text = (
556
+ f"📰 {article['title']}\n"
557
+ f"Published Date: {article['date']}\n"
558
+ f"Keywords: {article['tickers']}\n\n"
559
+ f"🔍 Summary\n"
560
+ f"{textwrap.fill(summary, width=80)}"
561
+ )
562
+ return text
563
+
564
+
565
+ async def send_articles(
566
+ articles: List[Dict[str, Any]],
567
+ token: str,
568
+ id: str,
569
+ source: Literal["coindesk", "fmp"],
570
+ interval: int = 15,
571
+ ) -> None:
572
+ for article in articles:
573
+ message = ""
574
+ if source == "coindesk":
575
+ published_on = article.get("published_on")
576
+ if isinstance(
577
+ published_on, datetime
578
+ ) and published_on >= datetime.now() - timedelta(minutes=interval):
579
+ article["published_on"] = published_on.strftime("%Y-%m-%d %H:%M:%S")
580
+ message = format_coindesk_article(article)
581
+ else:
582
+ message = format_fmp_article(article)
583
+ if message == "":
584
+ continue
585
+ await asyncio.sleep(2) # To avoid hitting Telegram rate limits
586
+ await send_telegram_message(token, id, text=message)
587
+
588
+
589
+ def send_news_feed(unknown: List[str]) -> None:
590
+ HELP_MSG = """
591
+ Send news feed from Coindesk to Telegram channel.
592
+ This script fetches the latest news articles from Coindesk, summarizes them,
593
+ and sends them to a specified Telegram channel at regular intervals.
594
+
595
+ Usage:
596
+ python -m bbstrader --run news_feed [options]
597
+
598
+ Options:
599
+ -q, --query: The news to look for (default: "")
600
+ -t, --token: Telegram bot token
601
+ -I, --id: Telegram Chat id
602
+ --fmp: Financial Modeling Prop Api Key
603
+ -i, --interval: Interval in minutes to fetch news (default: 15)
604
+
605
+ Note:
606
+ The script will run indefinitely, fetching news every 15 minutes.
607
+ Use Ctrl+C to stop the script.
608
+ """
609
+
610
+ if "-h" in unknown or "--help" in unknown:
611
+ print(HELP_MSG)
612
+ sys.exit(0)
613
+
614
+ parser = argparse.ArgumentParser()
615
+ parser.add_argument(
616
+ "-q", "--query", type=str, default="", help="The news to look for"
617
+ )
618
+ parser.add_argument(
619
+ "-t",
620
+ "--token",
621
+ type=str,
622
+ required=True,
623
+ help="Telegram bot token",
624
+ )
625
+ parser.add_argument("-I", "--id", type=str, required=True, help="Telegram Chat id")
626
+ parser.add_argument(
627
+ "--fmp", type=str, default="", help="Financial Modeling Prop Api Key"
628
+ )
629
+ parser.add_argument(
630
+ "-i",
631
+ "--interval",
632
+ type=int,
633
+ default=15,
634
+ help="Interval in minutes to fetch news (default: 15)",
635
+ )
636
+ args = parser.parse_args(unknown)
637
+
638
+ nltk.download("punkt", quiet=True)
639
+ news = FinancialNews()
640
+ fmp_news = news.get_fmp_news(api=args.fmp) if args.fmp else None
641
+ logger.info(f"Starting the News Feed on {args.interval} minutes")
642
+ while True:
643
+ try:
644
+ fmp_articles: List[Dict[str, Any]] = []
645
+ if fmp_news is not None:
646
+ fmp_articles = fmp_news.get_latest_articles(limit=5)
647
+ coindesk_articles = news.get_coindesk_news(query=args.query)
648
+ if coindesk_articles and isinstance(coindesk_articles, list):
649
+ asyncio.run(
650
+ send_articles(
651
+ coindesk_articles, # type: ignore
652
+ args.token,
653
+ args.id,
654
+ "coindesk",
655
+ interval=args.interval,
656
+ )
657
+ )
658
+ if len(fmp_articles) != 0:
659
+ asyncio.run(send_articles(fmp_articles, args.token, args.id, "fmp"))
660
+ time.sleep(args.interval * 60)
661
+ except KeyboardInterrupt:
662
+ logger.info("Stopping the News Feed ...")
663
+ sys.exit(0)
664
+ except Exception as e:
665
+ logger.error(e)
@@ -0,0 +1,33 @@
1
+ """
2
+ Overview
3
+ ========
4
+
5
+ The Trading Module is responsible for the execution of trading strategies. It provides a
6
+ structured framework for implementing and managing trading strategies, from signal generation
7
+ to order execution. This module is designed to be flexible and extensible, allowing for the
8
+ customization of trading logic and integration with various execution handlers.
9
+
10
+ Features
11
+ ========
12
+
13
+ - **Strategy Execution Framework**: Defines a clear structure for creating and executing trading strategies.
14
+ - **Signal Generation**: Supports the generation of trading signals based on market data and strategy logic.
15
+ - **Order Management**: Manages the creation and execution of orders based on generated signals.
16
+ - **Extensibility**: Allows for the implementation of custom strategies and execution handlers.
17
+
18
+ Components
19
+ ==========
20
+
21
+ - **Execution**: Handles the execution of trades, with a base class for creating custom execution handlers.
22
+ - **Strategy**: Defines the core logic of the trading strategy, including signal generation and order creation.
23
+ - **Utils**: Provides utility functions to support the trading process.
24
+
25
+ Notes
26
+ =====
27
+
28
+ This module can be used in both backtesting and live trading environments by swapping out the
29
+ execution handler.
30
+ """
31
+
32
+ from bbstrader.trading.execution import * # noqa: F403
33
+ from bbstrader.trading.strategy import * # noqa: F403