From 01f8c4af7dfaf937a53e1fa401f5c3dbb4de40c7 Mon Sep 17 00:00:00 2001 From: Ichinga Samuel Date: Sat, 30 Nov 2024 23:10:31 +0100 Subject: [PATCH] v4.0.5 --- docs/lib/order.md | 9 ++ docs/lib/positions.md | 37 ++++- docs/lib/ram.md | 9 ++ examples/sample_bot.py | 14 +- examples/trade_records/Chaos.csv | 151 ++++++++++++++++++ src/aiomql/_utils.py | 79 ++++++++- .../core/backtesting/backtest_controller.py | 3 +- src/aiomql/core/config.py | 6 +- src/aiomql/core/constants.py | 3 +- src/aiomql/core/task_queue.py | 41 +++-- src/aiomql/lib/account.py | 2 +- src/aiomql/lib/backtester.py | 1 + src/aiomql/lib/bot.py | 4 +- src/aiomql/lib/executor.py | 38 ++++- src/aiomql/lib/order.py | 9 ++ src/aiomql/lib/positions.py | 27 +++- src/aiomql/lib/ram.py | 5 + src/aiomql/lib/result.py | 3 +- src/aiomql/lib/sessions.py | 16 +- tests/live/configs/test2.json | 14 ++ tests/live/integration/test_bot.py | 4 +- tests/live/test.json | 14 ++ 22 files changed, 431 insertions(+), 58 deletions(-) create mode 100644 examples/trade_records/Chaos.csv create mode 100644 tests/live/configs/test2.json create mode 100644 tests/live/test.json diff --git a/docs/lib/order.md b/docs/lib/order.md index 3630bee..72f8af1 100644 --- a/docs/lib/order.md +++ b/docs/lib/order.md @@ -12,6 +12,7 @@ - [calc_profit](#order.calc_profit) - [calc_loss](#order.calc_loss) - [request](#order.request) +- [modify](#order.modify) ### Order @@ -160,3 +161,11 @@ Return the trade request object as a dict | Type | Description | |--------|----------------------------------| | `dict` | Returns the trade request object | + + + +### modify +```python +def modify(**kwargs) +``` +Modify the order object with keyword arguments. diff --git a/docs/lib/positions.md b/docs/lib/positions.md index 2fb307b..d3da353 100644 --- a/docs/lib/positions.md +++ b/docs/lib/positions.md @@ -10,6 +10,7 @@ - [close_position_by_ticket](#positions.close_position_by_ticket) - [close_position](#positions.close_position) - [close_all](#positions.close_all) +- [get_total_positions](#positions.get_total_positions) ### Positions @@ -19,10 +20,11 @@ class Positions Get and handle Open positions. #### Attributes -| Name | Type | Description | -|-------------|-----------------------------|----------------------------| -| `positions` | `tuple[TradePosition, ...]` | Financial instrument name. | -| `mt5` | `MetaTrader` | MetaTrader instance. | +| Name | Type | Description | +|-------------|-----------------------------|-------------------------------------------------------------------------------------| +| `positions` | `tuple[TradePosition, ...]` | Financial instrument name. | +| `mt5` | `MetaTrader` | MetaTrader instance. | +|`total_positions`| `int` | Total number of open positions. Can be set in `get_positions` or `get_total_positions`. | ### \_\_init\_\_ @@ -35,11 +37,19 @@ Initialize a position instance ### get_positions ```python -async def get_positions(self) -> tuple[TradePosition, ...]: +async def get_positions(*, symbol: str = None, ticket: int = None, group: str = None) -> tuple[TradePosition, ...]: ``` -Get open positions +Get open positions, with the ability to filter by symbol, ticket, or group. -#### Returns +#### Parameters: +| Name | Type | Description | +|----------|-------|-----------------| +| `symbol` | `str` | Symbol | +| `ticket` | `int` | Position ticket | +| `group` | `str` | Group name | + + +#### Returns: | Type | Description | |-----------------------------|--------------------------------| | `tuple[TradePosition, ...]` | A list of open trade positions | @@ -150,3 +160,16 @@ Close all open positions for the trading account. | Type | Description | |-------|--------------------------------------| | `int` | Return total number of closed trades | + + + +### get_total_positions +```python +async def get_total_positions() -> int +``` +Get the total number of open positions and set the `total_positions` attribute. + +#### Returns: +| Type | Description | +|-------|--------------------------------------| +| `int` | Return total number of open trades | diff --git a/docs/lib/ram.md b/docs/lib/ram.md index 2ab19eb..34fe951 100644 --- a/docs/lib/ram.md +++ b/docs/lib/ram.md @@ -6,6 +6,7 @@ - [get_amount](#ram.get_amount) - [check_losing_positions](#ram.check_losing_positions) - [check_open_positions](#ram.check_open_positions) +- [modify_ram](#ram.modify_ram) ### RAM @@ -75,3 +76,11 @@ Check if the number of open positions is less than or equal the loss limit. | Type | Description | |--------|---------------------------------------------------------------------------------------| | `bool` | True if the number of open losing trades is more than the loss limit, False otherwise | + + + +### modify_ram +```python +def modify_ram(**kwargs): +``` +Modify the RAM attributes. All provided keyword arguments are set as attributes. diff --git a/examples/sample_bot.py b/examples/sample_bot.py index 74a6fe1..b6cfacb 100644 --- a/examples/sample_bot.py +++ b/examples/sample_bot.py @@ -1,7 +1,8 @@ import logging +import asyncio from aiomql.lib.bot import Bot -from aiomql.contrib.strategies import FingerTrap +from aiomql.contrib.strategies import FingerTrap, Chaos from aiomql.contrib.symbols import ForexSymbol @@ -9,10 +10,19 @@ def sample_bot(): logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") syms = ["Volatility 75 Index", "Volatility 100 Index", "Volatility 50 Index"] symbols = [ForexSymbol(name=sym) for sym in syms] - strategies = [FingerTrap(symbol=symbol) for symbol in symbols] + strategies = [Chaos(symbol=symbol) for symbol in symbols] bot = Bot() + bot.add_coroutine(coroutine=pray_sleep) bot.add_strategies(strategies=strategies) + bot.timeout = 10 bot.execute() +async def pray_sleep(): + while True: + print("I'm praying") + await asyncio.sleep(10) + print("I'm done praying") + + sample_bot() diff --git a/examples/trade_records/Chaos.csv b/examples/trade_records/Chaos.csv new file mode 100644 index 0000000..5febd50 --- /dev/null +++ b/examples/trade_records/Chaos.csv @@ -0,0 +1,151 @@ +hcc,symbol,expected_profit,win,date,closed,ask,bid,lcc,deal,order,price,htf,slow_ema,name,actual_profit,volume,ltf,fast_ema +100,Volatility 75 Index,0,False,2024-11-30 19:07:29.043539,False,78828.04,78806.16,100,8085845438,8177248286,78806.16,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:07:29.044543,False,1314.12,1313.68,100,8085845455,8177248293,1314.12,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:07:31.025102,False,324.4566,324.4146,100,8085845467,8177248312,324.4566,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:17:11.021084,False,78735.37,78713.49,100,8085851022,8177253835,78735.37,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:17:13.020086,False,1314.32,1313.88,100,8085851041,8177253853,1313.88,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:17:15.008086,False,324.0193,323.9773,100,8085851069,8177253868,323.9773,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:18:53.017326,False,78704.04,78682.16,100,8085852163,8177254989,78704.04,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:18:55.011474,False,1316.37,1315.93,100,8085852178,8177254993,1315.93,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:18:55.013474,False,324.2283,324.1863,100,8085852215,8177255002,324.2283,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:23:27.010827,False,78778.03,78756.15,100,8085854936,8177257787,78756.15,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:23:29.006832,False,1314.3,1313.86,100,8085854954,8177257793,1313.86,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:23:31.003828,False,323.2684,323.2264,100,8085854975,8177257823,323.2264,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:26:09.025801,False,323.7008,323.6588,100,8085856583,8177259466,323.7008,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:26:11.005802,False,78884.84,78862.96,100,8085856572,8177259463,78884.84,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:26:11.006799,False,1310.66,1310.22,100,8085856599,8177259477,1310.22,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:28:03.006334,False,78939.57,78917.69,100,8085857526,8177260436,78917.69,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:28:03.009335,False,324.7318,324.6898,100,8085857558,8177260446,324.7318,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:28:05.005331,False,1310.94,1310.5,100,8085857566,8177260475,1310.5,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:29:27.015041,False,1315.82,1315.38,100,8085858344,8177261262,1315.82,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:29:28.999563,False,324.8365,324.7945,100,8085858386,8177261317,324.8365,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:29:31.012566,False,78947.5,78925.62,100,8085858423,8177261341,78925.62,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:30:01.032756,False,79079.21,79057.33,100,8085858955,8177261893,79079.21,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:30:05.024759,False,324.6382,324.5962,100,8085859011,8177261936,324.5962,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:30:05.024759,False,1314.79,1314.35,100,8085858996,8177261910,1314.79,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:31:33.026074,False,79188.35,79166.47,100,8085859867,8177262831,79188.35,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:31:35.021076,False,324.5792,324.5372,100,8085859885,8177262838,324.5792,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:31:37.016075,False,1315.72,1315.28,100,8085859896,8177262856,1315.72,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:32:03.020861,False,324.5508,324.5088,100,8085860182,8177263172,324.5088,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:32:03.020861,False,79179.99,79158.11,100,8085860212,8177263178,79179.99,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:32:05.021850,False,1316.68,1316.24,100,8085860224,8177263198,1316.68,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:40:05.004954,False,78914.24,78892.36,100,8085864656,8177267909,78914.24,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:40:09.002958,False,1319.72,1319.28,100,8085864692,8177267937,1319.72,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:40:09.002958,False,324.1071,324.0651,100,8085864666,8177267919,324.0651,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:43:33.009120,False,1320.3,1319.86,100,8085866996,8177270427,1319.86,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:43:34.999654,False,78827.52,78805.64,100,8085867046,8177270431,78805.64,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:43:37.008651,False,324.4128,324.3708,100,8085867079,8177270484,324.4128,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:48:24.003478,False,78766.18,78744.3,100,8085870210,8177274131,78744.3,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:48:25.018483,False,1315.64,1315.2,100,8085870233,8177274174,1315.2,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:48:27.009492,False,324.5763,324.5343,100,8085870256,8177274178,324.5763,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:50:01.007484,False,323.9372,323.8952,100,8085871271,8177275350,323.9372,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:50:03.060484,False,1317.61,1317.17,100,8085871292,8177275364,1317.17,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:50:05.028515,False,78781.57,78759.69,100,8085871329,8177275399,78781.57,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 19:59:45.021172,False,1321.83,1321.39,100,8085877825,8177282928,1321.83,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 19:59:45.021172,False,79163.75,79141.87,100,8085877857,8177282969,79141.87,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 19:59:47.000175,False,323.1256,323.0836,100,8085877884,8177283006,323.1256,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:00:00.990594,False,79155.16,79133.28,100,8085878145,8177283326,79133.28,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:00:05.003599,False,323.1065,323.0645,100,8085878237,8177283399,323.0645,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:00:05.002594,False,1321.13,1320.69,100,8085878211,8177283349,1321.13,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:07:15.071021,False,79195.7,79173.82,100,8085882944,8177288643,79173.82,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:07:15.071021,False,1327.97,1327.53,100,8085882911,8177288633,1327.53,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:07:16.994022,False,323.3865,323.3445,100,8085882970,8177288685,323.3865,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:11:13.010367,False,79335.81,79313.93,100,8085885622,8177291571,79313.93,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:11:13.011365,False,1331.92,1331.48,100,8085885650,8177291589,1331.48,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:11:15.003365,False,323.7561,323.7141,100,8085885678,8177291620,323.7561,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:20:45.002184,False,79546.91,79525.03,100,8085892281,8177298716,79546.91,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:20:47.006180,False,324.5659,324.5239,100,8085892320,8177298720,324.5239,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:26:03.185662,False,1327.57,1327.13,100,8085895725,8177302365,1327.13,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:26:09.067255,False,79305.74,79283.86,100,8085895753,8177302415,79305.74,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:26:10.995026,False,324.5796,324.5376,100,8085895762,8177302423,324.5796,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:27:24.995204,False,79276.47,79254.59,100,8085896466,8177303186,79254.59,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:27:24.996204,False,324.3086,324.2666,100,8085896491,8177303215,324.2666,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:27:27.202795,False,1325.12,1324.68,100,8085896500,8177303231,1325.12,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:28:27.002144,False,1327.2,1326.76,100,8085897009,8177303797,1326.76,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:28:27.005145,False,79278.85,79256.97,100,8085897062,8177303803,79278.85,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:28:28.984144,False,323.8883,323.8463,100,8085897065,8177303851,323.8463,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:30:37.011482,False,323.6916,323.6496,100,8085898139,8177305049,323.6916,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:30:37.013482,False,79355.87,79333.99,100,8085898161,8177305066,79333.99,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:30:38.987481,False,1323.99,1323.55,100,8085898234,8177305138,1323.55,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:32:02.983816,False,79441.61,79419.73,100,8085899199,8177306191,79419.73,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:32:02.983816,False,324.0681,324.0261,100,8085899220,8177306207,324.0681,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:32:06.983816,False,1323.5,1323.06,100,8085899268,8177306245,1323.06,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:34:23.020570,False,79326.19,79304.31,100,8085900581,8177307689,79326.19,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:34:23.021571,False,1322.72,1322.28,100,8085900539,8177307655,1322.28,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:34:25.418569,False,324.5077,324.4657,100,8085900595,8177307723,324.4657,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:36:27.244005,False,79153.28,79131.4,100,8085901660,8177308881,79153.28,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:36:29.162006,False,1327.12,1326.68,100,8085901692,8177308905,1326.68,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:36:31.090003,False,324.2543,324.2123,100,8085901712,8177308927,324.2543,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:37:55.001817,False,1330.68,1330.24,100,8085902742,8177310025,1330.24,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:37:55.005820,False,324.3003,324.2583,100,8085902726,8177310019,324.3003,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:37:57.137818,False,79212.21,79190.33,100,8085902754,8177310052,79212.21,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:38:00.997822,False,79235.35,79213.47,100,8085902806,8177310123,79213.47,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:38:02.991111,False,1330.38,1329.94,100,8085902841,8177310137,1329.94,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:38:05.006113,False,324.2726,324.2306,100,8085902911,8177310210,324.2306,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:38:55.053360,False,79305.75,79283.87,100,8085903412,8177310761,79283.87,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:38:55.053360,False,324.3933,324.3513,100,8085903371,8177310737,324.3513,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:38:57.001887,False,1331.91,1331.47,100,8085903430,8177310799,1331.47,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:40:03.262107,False,79163.49,79141.61,100,8085904300,8177311720,79163.49,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:40:03.264107,False,324.0259,323.9839,100,8085904337,8177311725,323.9839,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:40:05.003108,False,1330.35,1329.91,100,8085904359,8177311739,1330.35,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:40:46.988140,False,79151.03,79129.15,100,8085904778,8177312217,79151.03,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:40:48.985138,False,324.3079,324.2659,100,8085904790,8177312229,324.2659,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:40:50.986140,False,1330.57,1330.13,100,8085904803,8177312244,1330.13,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:42:01.031448,False,324.4332,324.3912,100,8085905489,8177313005,324.3912,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:42:02.998450,False,1330.59,1330.15,100,8085905509,8177313007,1330.15,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:42:04.974450,False,79049.18,79027.3,100,8085905524,8177313022,79049.18,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:44:49.011685,False,79043.68,79021.8,100,8085907248,8177314937,79043.68,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:44:49.018687,False,1327.85,1327.41,100,8085907276,8177314945,1327.85,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:44:50.994684,False,324.38,324.338,100,8085907281,8177314958,324.38,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:46:10.984008,False,79014.9,78993.02,100,8085908427,8177316197,79014.9,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:46:10.988010,False,1325.75,1325.31,100,8085908465,8177316206,1325.75,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:46:12.986011,False,324.4139,324.3719,100,8085908501,8177316232,324.3719,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:47:22.986968,False,1327.04,1326.6,100,8085909571,8177317543,1326.6,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:47:25.026967,False,78945.11,78923.23,100,8085909774,8177317561,78923.23,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:47:26.985965,False,324.397,324.355,100,8085909787,8177317592,324.355,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:48:02.999449,False,1324.0,1323.56,100,8085910188,8177318015,1323.56,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:48:04.993447,False,78947.31,78925.43,100,8085910198,8177318021,78947.31,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:48:07.015973,False,324.4016,324.3596,100,8085910207,8177318037,324.4016,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 20:56:02.985263,False,1316.4,1315.96,100,8085915185,8177323496,1316.4,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 20:56:07.096263,False,78879.35,78857.47,100,8085915321,8177323587,78879.35,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 20:56:07.097263,False,325.0526,325.0106,100,8085915213,8177323504,325.0106,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:00:26.983944,False,78862.56,78840.68,100,8085918435,8177327000,78862.56,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:00:26.984945,False,1313.12,1312.68,100,8085918457,8177327025,1312.68,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:00:30.984611,False,325.5656,325.5236,100,8085918494,8177327056,325.5656,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:02:00.977445,False,1311.5,1311.06,100,8085919286,8177327975,1311.5,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:02:04.990740,False,325.6578,325.6158,100,8085919367,8177328006,325.6578,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:02:04.991741,False,78846.58,78824.7,100,8085919309,8177327988,78824.7,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:03:45.003292,False,78840.3,78818.42,100,8085920342,8177329082,78818.42,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:03:46.993293,False,1313.65,1313.21,100,8085920366,8177329117,1313.65,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:03:48.986293,False,325.4783,325.4363,100,8085920381,8177329134,325.4363,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:04:00.984264,False,325.4041,325.3621,100,8085920473,8177329247,325.3621,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:04:03.007493,False,78868.29,78846.41,100,8085920496,8177329261,78846.41,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:04:04.977495,False,1312.89,1312.45,100,8085920516,8177329279,1312.45,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:06:02.973242,False,325.5582,325.5162,100,8085921686,8177330570,325.5582,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:06:02.974244,False,78756.09,78734.21,100,8085921702,8177330577,78756.09,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:06:05.004242,False,1312.65,1312.21,100,8085921716,8177330589,1312.65,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:07:20.984882,False,78874.77,78852.89,100,8085922565,8177331499,78852.89,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:07:20.986878,False,1312.95,1312.51,100,8085922587,8177331522,1312.51,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:07:22.988876,False,325.8438,325.8018,100,8085922602,8177331544,325.8438,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:08:44.970353,False,1312.78,1312.34,100,8085923629,8177332630,1312.78,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:08:44.971353,False,78969.9,78948.02,100,8085923623,8177332606,78969.9,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:08:46.973352,False,325.8749,325.8329,100,8085923655,8177332646,325.8329,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:10:06.974890,False,326.2115,326.1695,100,8085924362,8177333420,326.2115,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:10:06.973888,False,78988.92,78967.04,100,8085924384,8177333426,78967.04,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:10:06.974890,False,1311.71,1311.27,100,8085924404,8177333462,1311.27,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:12:03.085605,False,326.2136,326.1716,100,8085925439,8177334592,326.1716,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:12:04.971308,False,79113.59,79091.71,100,8085925422,8177334585,79113.59,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:12:07.002310,False,1311.24,1310.8,100,8085925464,8177334616,1311.24,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:13:52.980316,False,79117.98,79096.1,100,8085926462,8177335712,79096.1,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:33:52.228712,False,1311.99,1311.55,100,8085937713,8177348003,1311.99,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:33:52.229715,False,79027.4,79005.52,100,8085937719,8177348008,79027.4,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:33:54.259839,False,325.8998,325.8578,100,8085937731,8177348021,325.8578,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:34:04.565096,False,1310.56,1310.12,100,8085937810,8177348106,1310.56,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:34:04.565096,False,325.9881,325.9461,100,8085937790,8177348082,325.9461,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:34:06.221092,False,79026.49,79004.61,100,8085937814,8177348126,79026.49,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:36:02.252949,False,78804.93,78783.05,100,8085939146,8177349529,78804.93,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:36:04.244949,False,326.2347,326.1927,100,8085939198,8177349581,326.1927,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:36:06.223949,False,1308.0,1307.56,100,8085939217,8177349600,1307.56,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 75 Index,0,False,2024-11-30 21:38:02.266525,False,78772.16,78750.28,100,8085940552,8177351016,78750.28,TIMEFRAME_M2,20,Chaos,0,0.001,TIMEFRAME_M1,8 +100,Volatility 100 Index,0,False,2024-11-30 21:38:06.239554,False,1310.95,1310.51,100,8085940602,8177351089,1310.95,TIMEFRAME_M2,20,Chaos,0,0.5,TIMEFRAME_M1,8 +100,Volatility 50 Index,0,False,2024-11-30 21:38:06.239554,False,325.7652,325.7232,100,8085940571,8177351044,325.7652,TIMEFRAME_M2,20,Chaos,0,4.0,TIMEFRAME_M1,8 diff --git a/src/aiomql/_utils.py b/src/aiomql/_utils.py index 23f1763..16e61c2 100644 --- a/src/aiomql/_utils.py +++ b/src/aiomql/_utils.py @@ -10,7 +10,35 @@ from .core.config import Config logger = getLogger(__name__) -config = Config() + +async def backtest_sleep(secs: float): + config = Config() + btc = config.backtest_controller + try: + if btc.parties == 2: + steps = int(secs) // config.backtest_engine.speed + steps = max(steps, 1) + config.backtest_engine.fast_forward(steps=steps) + btc.wait() + + elif btc.parties > 2: + _time = config.backtest_engine.cursor.time + secs + while _time > config.backtest_engine.cursor.time: + btc.wait() + else: + btc.wait() + except Exception as err: + btc.wait() + logger.error("Error: %s in backtest_sleep", err) + + +# async def backtest_sleep(secs): +# """An async sleep function for use during backtesting.""" +# btc = BackTestController() +# config = Config() +# sleep = config.backtest_engine.cursor.time + secs +# while sleep > config.backtest_engine.cursor.time: +# btc.wait() def dict_to_string(data: dict, multi=False) -> str: @@ -28,6 +56,13 @@ def dict_to_string(data: dict, multi=False) -> str: def backoff_decorator(func=None, *, max_retries: int = 2, retries: int = 0, error="") -> callable: + """A decorator to retry a function a number of times before giving up. + Args: + func (callable, optional): The function to decorate. Defaults to None. + max_retries (int, optional): The maximum number of retries. Defaults to 2. + retries (int, optional): The number of retries. Defaults to 0. + error (Any, optional): The error to raise when the maximum number of retries is reached. Defaults to "". + """ if func is None: return partial(backoff_decorator, max_retries=max_retries, retries=retries, error=error) @@ -47,7 +82,7 @@ def backoff_decorator(func=None, *, max_retries: int = 2, retries: int = 0, erro return res except Exception as err: logger.error("Error in %s: %s", func.__name__, err) - if config.mode != "backtest": + if Config().mode != "backtest": await asyncio.sleep(2**retries + random.uniform(0, max_retries)) await wrapper(*args, **kwargs) @@ -55,6 +90,14 @@ def backoff_decorator(func=None, *, max_retries: int = 2, retries: int = 0, erro def error_handler(func=None, *, msg="", exe=Exception, response=None, log_error_msg=True): + """A decorator to handle errors in an async function. + Args: + func (callable, optional): The function to decorate. Defaults to None. + msg (str, optional): The error message to log. Defaults to "". + exe (Exception, optional): The exception to catch. Defaults to Exception. + response (Any, optional): The response to return when an error occurs. Defaults to None. + log_error_msg (bool, optional): If True, log the error message. Defaults to True. + """ if func is None: return partial(error_handler, msg=msg, exe=exe, response=response, log_error_msg=log_error_msg) @@ -72,6 +115,15 @@ def error_handler(func=None, *, msg="", exe=Exception, response=None, log_error_ def error_handler_sync(func=None, *, msg="", exe=Exception, response=None, log_error_msg=True): + """A decorator to handle errors in a synchronous function. + + Args: + func (callable, optional): The function to decorate. Defaults to None. + msg (str, optional): The error message to log. Defaults to "". + exe (Exception, optional): The exception to catch. Defaults to Exception. + response (Any, optional): The response to return when an error occurs. Defaults to None. + log_error_msg (bool, optional): If True, log the error message. Defaults to True. + """ if func is None: return partial(error_handler, msg=msg, exe=exe, response=response, log_error_msg=log_error_msg) @@ -88,7 +140,15 @@ def error_handler_sync(func=None, *, msg="", exe=Exception, response=None, log_e return wrapper -def round_down(value: int | float, base: int) -> int: +def round_down(value: int | float, base: int) -> int | float: + """Round down a number to the nearest base. + Args: + value (int | float): The number to round down. + base (int): The base to round down to. + + Returns: + (int | float): The rounded down number. + """ return int(value) if value % base == 0 else int(value - (value % base)) @@ -98,19 +158,28 @@ def round_up(value: int | float, base: int) -> int: # noinspection PyShadowingNames def round_off(value: float, step: float, round_down: bool = False) -> float: - """Round off a number to the nearest step.""" + """Round off a number to the nearest step. + + Args: + value (float): The number to round off. + step (float): The step to round off to. + round_down (bool, optional): If True, the number is rounded down otherwise it is rounded up. Defaults to False. + + Returns: + float: The rounded off number. + """ with decimal.localcontext() as ctx: ctx.rounding = decimal.ROUND_DOWN if round_down else decimal.ROUND_UP return float(decimal.Decimal(str(value)).quantize(decimal.Decimal(str(step)))) def async_cache(fun): + """A decorator to cache the result of an async function.""" @wraps(fun) async def wrapper(*args, **kwargs): key = (args, frozenset(kwargs.items())) with wrapper.lock: if key not in wrapper.cache: - # print(key) wrapper.cache[key] = await fun(*args, **kwargs) return wrapper.cache[key] diff --git a/src/aiomql/core/backtesting/backtest_controller.py b/src/aiomql/core/backtesting/backtest_controller.py index 34eadf6..9369358 100644 --- a/src/aiomql/core/backtesting/backtest_controller.py +++ b/src/aiomql/core/backtesting/backtest_controller.py @@ -13,7 +13,7 @@ logger = getLogger(__name__) class BackTestController: """The controller for the backtesting engine. - It also act's as a synchronizier for running multiple strategies (tasks) using a threading.Barrier primitive. + It also acts as a synchronizer for running multiple strategies (tasks) using a threading.Barrier primitive. It handles the updating of open positions and close them when necessary. It handles the iterator for the backtesting engine and handles it movement in time by moving it to the next time step. @@ -33,6 +33,7 @@ class BackTestController: if not hasattr(cls, "_instance"): cls._instance = super().__new__(cls) cls._instance.config = Config() + cls._instance.config.backtest_controller = cls._instance cls._instance.barrier = Barrier(1) cls._instance.tasks = [] return cls._instance diff --git a/src/aiomql/core/config.py b/src/aiomql/core/config.py index 49300a0..211265f 100644 --- a/src/aiomql/core/config.py +++ b/src/aiomql/core/config.py @@ -9,6 +9,7 @@ from .task_queue import TaskQueue logger = getLogger(__name__) Bot = TypeVar("Bot") BackTestEngine = TypeVar("BackTestEngine") +BackTestController = TypeVar("BackTestController") class Config: @@ -28,6 +29,7 @@ class Config: task_queue: TaskQueue _backtest_engine: BackTestEngine bot: Bot + backtest_controller: BackTestController _instance: Self mode: Literal["backtest", "live"] use_terminal_for_backtesting: bool @@ -54,9 +56,11 @@ class Config: if not hasattr(cls, "_instance"): cls._instance = super().__new__(cls) cls._instance.state = {} - cls._instance.task_queue = TaskQueue() + cls._instance.task_queue = TaskQueue(mode='infinite', workers=10) cls._instance.set_attributes(**cls._defaults) cls._instance._backtest_engine = None + cls._instance.bot = None + cls._instance.backtest_controller = None # cls._instance.load_config(**kwargs) return cls._instance diff --git a/src/aiomql/core/constants.py b/src/aiomql/core/constants.py index 12696e7..47afb58 100644 --- a/src/aiomql/core/constants.py +++ b/src/aiomql/core/constants.py @@ -123,7 +123,8 @@ class OrderType(Repr, IntEnum): Returns: int: integer value of opposite order type """ - return {0: 1, 1: 0, 2: 3, 3: 2, 4: 5, 5: 4, 6: 7, 7: 6, 8: 8}[self] + _type = {0: 1, 1: 0, 2: 3, 3: 2, 4: 5, 5: 4, 6: 7, 7: 6, 8: 8}[self] + return OrderType(_type) class BookType(Repr, IntEnum): diff --git a/src/aiomql/core/task_queue.py b/src/aiomql/core/task_queue.py index 9b70398..1037226 100644 --- a/src/aiomql/core/task_queue.py +++ b/src/aiomql/core/task_queue.py @@ -83,10 +83,14 @@ class TaskQueue: except asyncio.QueueFull: logger.error("Queue is full") - async def worker(self): + async def worker(self, wid=None): """Worker function to run tasks in the queue.""" while True: try: + if self.mode == 'infinite' and self.queue.qsize() <= 1: + dummy = QueueItem(self.dummy_task) + self.add(item=dummy) + if isinstance(self.queue, asyncio.PriorityQueue): _, item = self.queue.get_nowait() else: @@ -96,6 +100,7 @@ class TaskQueue: await item.run() self.queue.task_done() + self.priority_tasks.discard(item) if self.stop and (self.on_exit == 'cancel' or len(self.priority_tasks) == 0): @@ -107,16 +112,15 @@ class TaskQueue: break if self.mode == 'finite': + self.stop = True break - - # add dummy task to prevent worker from exiting - sleep = QueueItem(asyncio.sleep, 1) - self.add(item=sleep) - await asyncio.sleep(self.worker_timeout) except Exception as err: logger.error("%s: Error occurred in worker", err) break + async def dummy_task(self): + await asyncio.sleep(self.worker_timeout) + async def run(self, timeout: int = 0): """Run the queue until all tasks are completed or the timeout is reached. @@ -128,13 +132,13 @@ class TaskQueue: """ start = time.perf_counter() try: - self.worker_tasks.extend([asyncio.create_task(self.worker()) for _ in range(self.workers)]) + self.worker_tasks.extend(asyncio.create_task(self.worker()) for _ in range(self.workers)) timeout = timeout or self.timeout self.queue_task = asyncio.create_task(self.queue.join()) if timeout: await asyncio.wait_for(self.queue_task, timeout=timeout) - self.stop = True + else: await self.queue_task @@ -142,22 +146,26 @@ class TaskQueue: logger.warning("Timed out after %d seconds, %d tasks remaining", time.perf_counter() - start, self.queue.qsize()) self.stop = True + await self.clean_up() except asyncio.CancelledError: self.stop = True + await self.clean_up() except Exception as err: logger.warning("%s: An error occurred in %s.run", err, self.__class__.__name__) self.stop = True + await self.clean_up() finally: - await self.clean_up() + print("Finally") + async def clean_up(self): """Clean up tasks in the queue, completing priority tasks if `on_exit` is `complete_priority`""" + self.stop = True try: logger.info('cleaning up tasks...') - if self.on_exit == 'complete_priority' and (pt := len(self.priority_tasks)) > 0: logger.info('Completing %d priority tasks...', pt) self.queue_task = asyncio.create_task(self.queue.join()) @@ -166,20 +174,16 @@ class TaskQueue: self.cancel() except asyncio.CancelledError: - self.stop = True + self.cancel() except Exception as err: logger.error(f"%s: Error occurred in %s", err, self.__class__.__name__) - - finally: self.cancel() def cancel(self): """Cancel all tasks in the queue""" try: - self.queue_task.cancel() - except asyncio.CancelledError: ... @@ -188,8 +192,11 @@ class TaskQueue: def sigint_handle(self, sig, frame): logger.info('SIGINT received, cleaning up...') - self.stop = True - self.cancel() + if self.stop is False: + self.stop = True + else: + self.stop = True + self.cancel() TaskQueue.__doc__ = """TaskQueue is a class that allows you to queue tasks and run them concurrently with a specified number of workers. diff --git a/src/aiomql/lib/account.py b/src/aiomql/lib/account.py index cdfe8f1..6af1f7c 100644 --- a/src/aiomql/lib/account.py +++ b/src/aiomql/lib/account.py @@ -38,7 +38,7 @@ class Account(_Base, AccountInfo): await self.mt5.initialize() self.connected = await self.mt5.login() if not self.connected: - raise LoginError("Login failed") + raise LoginError(f"Login failed: {self.mt5.error}") await self.refresh() return self diff --git a/src/aiomql/lib/backtester.py b/src/aiomql/lib/backtester.py index 636dfd8..2acfdb5 100644 --- a/src/aiomql/lib/backtester.py +++ b/src/aiomql/lib/backtester.py @@ -183,6 +183,7 @@ class BackTester: self.executor.add_strategy(strategy=strategy) return True else: + self.mt._symbol_select(strategy.symbol.name, True) self.mt._market_book_add(strategy.symbol.name) info = self.mt._symbol_info(strategy.symbol.name) time = datetime.fromtimestamp(self.backtest_engine.cursor.time, tz=UTC) diff --git a/src/aiomql/lib/bot.py b/src/aiomql/lib/bot.py index 99c42b4..8552b0e 100644 --- a/src/aiomql/lib/bot.py +++ b/src/aiomql/lib/bot.py @@ -68,7 +68,7 @@ class Bot: if len(self.executor.strategy_runners) == 0: logger.warning("No strategies were added to the bot. Exiting in five seconds") - await asyncio.sleep(5) + await asyncio.sleep(1) self.config.shutdown = True except Exception as err: logger.error("%s: Bot initialization failed", err) @@ -94,7 +94,7 @@ class Bot: if len(self.executor.strategy_runners) == 0: logger.warning("No strategies were added to the bot. Exiting in 5 seconds") - time.sleep(5) + time.sleep(1) self.config.shutdown = True except Exception as err: logger.error("%s: Bot initialization failed", err) diff --git a/src/aiomql/lib/executor.py b/src/aiomql/lib/executor.py index 30cbac2..d357f80 100644 --- a/src/aiomql/lib/executor.py +++ b/src/aiomql/lib/executor.py @@ -1,7 +1,8 @@ import asyncio -from concurrent.futures import ThreadPoolExecutor -from typing import Coroutine, Callable import os +from concurrent.futures import ThreadPoolExecutor +from signal import signal, SIGINT +from typing import Coroutine, Callable from logging import getLogger from ..core.config import Config @@ -32,6 +33,7 @@ class Executor: self.tasks = [] self.config = Config() self.timeout = None # Timeout for the executor. For testing purposes only + signal(SIGINT, self.sigint_handle) def add_function(self, *, function: Callable, kwargs: dict = None): kwargs = kwargs or {} @@ -40,6 +42,7 @@ class Executor: def add_coroutine(self, *, coroutine: Callable | Coroutine, kwargs: dict = None, on_separate_thread=False): kwargs = kwargs or {} coroutine = coroutine(**kwargs) + print('coroutine', coroutine) self.coroutines.append(coroutine) if on_separate_thread is False else self.coroutine_threads.append(coroutine) def add_strategies(self, *, strategies: tuple[Strategy]): @@ -79,8 +82,10 @@ class Executor: async def create_coroutines_task(self): """""" task = asyncio.create_task(asyncio.gather(*self.coroutines, return_exceptions=True)) + print('task', task) self.tasks.append(task) - await task + res = await task + print('res', res) def run_coroutine_tasks(self): """Run all coroutines in the executor""" @@ -102,23 +107,40 @@ class Executor: except Exception as err: logger.error(f"Error: {err}. Unable to run function: {function.__name__}") + def sigint_handle(self, signum, frame): + print("SIGINT") + self.config.shutdown = True + async def exit(self): """Shutdown the executor""" start = asyncio.get_event_loop().time() + print('exit', start) try: while self.config.shutdown is False and self.config.force_shutdown is False: + print('running exits') + print("Timeout", self.timeout, asyncio.get_event_loop().time() - start) if self.timeout is not None and self.timeout < (asyncio.get_event_loop().time() - start): + print("light Timeout", self.timeout, asyncio.get_event_loop().time() - start) self.config.shutdown = True - + await asyncio.sleep(10) + print('exiting') for strategy in self.strategy_runners: strategy.running = False - for task in self.tasks: - task.cancel() + + if self.config.backtest_engine is not None: + self.config.backtest_engine.stop_testing = True + self.executor.shutdown(wait=False, cancel_futures=True) + + # for task in self.tasks: + # task.cancel() + if self.config.force_shutdown: os._exit(1) except Exception as err: - logger.error(f"Error: {err}. Unable to shutdown executor") + print('error in timeout') + # logger.error(f"Error: {err}. Unable to shutdown executor") + os._exit(1) def execute(self, *, workers: int = 5): """Run the strategies with a threadpool executor. @@ -129,7 +151,7 @@ class Executor: Notes: No matter the number specified, the executor will always use a minimum of 5 workers. """ - workers_ = len(self.strategy_runners) + len(self.functions) + len(self.coroutine_threads) + 2 + workers_ = len(self.strategy_runners) + len(self.functions) + len(self.coroutine_threads) + 3 workers = max(workers, workers_) with ThreadPoolExecutor(max_workers=workers) as executor: self.executor = executor diff --git a/src/aiomql/lib/order.py b/src/aiomql/lib/order.py index 56c06f2..224c102 100644 --- a/src/aiomql/lib/order.py +++ b/src/aiomql/lib/order.py @@ -28,6 +28,15 @@ class Order(_Base, TradeRequest): kwargs = {"action": TradeAction.DEAL, "type_time": OrderTime.DAY, "type_filling": OrderFilling.FOK, **kwargs} super().__init__(**kwargs) + def modify(self, **kwargs): + """Modify the order object with keyword arguments. + + Args: + **kwargs: Keyword arguments must match the attributes of TradeRequest as well as the attributes of + Order class as specified in the annotations in the class definition. + """ + self.set_attributes(**kwargs) + async def orders_total(self): """Get the number of active pending orders. diff --git a/src/aiomql/lib/positions.py b/src/aiomql/lib/positions.py index 4c9e934..798fd78 100644 --- a/src/aiomql/lib/positions.py +++ b/src/aiomql/lib/positions.py @@ -24,23 +24,38 @@ class Positions: mt5: MetaTrader | MetaBackTester positions: tuple[TradePosition, ...] + total_positions: int def __init__(self): """Get Open Positions""" self.config = Config() self.mt5 = MetaTrader() if self.config.mode != "backtest" else MetaBackTester() self.positions = () + self.total_positions = 0 @backoff_decorator - async def get_positions(self) -> tuple[TradePosition, ...]: - """Get open positions with the ability to filter by symbol or ticket. + async def get_positions(self, *, symbol: str = None, ticket: int = None, group: str = None) -> tuple[TradePosition, ...]: + """Get open positions with the ability to filter by symbol, ticket or group of symbols. + Args: + symbol (Optional[str]): Financial instrument name. If symbol is provided, ticket is ignored. + ticket (Optional[int]): Position ticket. + group (Optional[str]): Group of symbols. Returns: tuple[TradePosition, ...]: A tuple of open trade positions """ - positions = await self.mt5.positions_get() + kwargs = {} + if symbol is not None: + kwargs["symbol"] = symbol + ticket = None + if ticket is not None: + kwargs["ticket"] = ticket + if group is not None: + kwargs["group"] = group + positions = await self.mt5.positions_get(**kwargs) if positions is not None: self.positions = tuple(TradePosition(**pos._asdict()) for pos in positions) + self.total_positions = len(self.positions) return self.positions logger.warning("Failed to get open positions") return () @@ -130,3 +145,9 @@ class Positions: *(self.close_position(position=position) for position in positions), return_exceptions=True ) return len([res for res in results if (isinstance(res, OrderSendResult) and res.retcode == 10009)]) + + async def get_total_positions(self) -> int: + """Get total number of open positions.""" + total = await self.mt5.positions_total() + self.total_positions = total or self.total_positions + return self.total_positions diff --git a/src/aiomql/lib/ram.py b/src/aiomql/lib/ram.py index 9656de6..9fabd3f 100644 --- a/src/aiomql/lib/ram.py +++ b/src/aiomql/lib/ram.py @@ -36,6 +36,11 @@ class RAM: self.open_limit = kwargs.get("open_limit", 3) self.fixed_amount = kwargs.get("fixed_amount", None) + def modify_ram(self, **kwargs): + """Modify the Risk Assessment and Management with the provided keyword arguments. + """ + [setattr(self, key, value) for key, value in kwargs.items()] + async def get_amount(self) -> float: """Calculate the amount to risk per trade as a percentage of margin_free. diff --git a/src/aiomql/lib/result.py b/src/aiomql/lib/result.py index c8a8d10..5e20969 100644 --- a/src/aiomql/lib/result.py +++ b/src/aiomql/lib/result.py @@ -78,7 +78,8 @@ class Result: """Serialize the trade records and strategy parameters""" try: return str(value) - except (ValueError, TypeError) as _: + except Exception as err: + logger.error("%s: Unable to serialize value", err) return "" async def to_json(self): diff --git a/src/aiomql/lib/sessions.py b/src/aiomql/lib/sessions.py index 259918f..103f346 100644 --- a/src/aiomql/lib/sessions.py +++ b/src/aiomql/lib/sessions.py @@ -5,8 +5,8 @@ from logging import getLogger from ..core.models import OrderSendResult, TradePosition from ..core.config import Config -from ..core.backtesting.backtest_controller import BackTestController from .positions import Positions +from .._utils import backtest_sleep logger = getLogger(__name__) @@ -26,13 +26,13 @@ def delta(obj: time) -> timedelta: return timedelta(hours=obj.hour, minutes=obj.minute, seconds=obj.second, microseconds=obj.microsecond) -async def backtest_sleep(secs): - """An async sleep function for use during backtesting.""" - btc = BackTestController() - config = Config() - sleep = config.backtest_engine.cursor.time + secs - while sleep > config.backtest_engine.cursor.time: - btc.wait() +# async def backtest_sleep(secs): +# """An async sleep function for use during backtesting.""" +# btc = BackTestController() +# config = Config() +# sleep = config.backtest_engine.cursor.time + secs +# while sleep > config.backtest_engine.cursor.time: +# btc.wait() class Session: diff --git a/tests/live/configs/test2.json b/tests/live/configs/test2.json new file mode 100644 index 0000000..fbaa0a3 --- /dev/null +++ b/tests/live/configs/test2.json @@ -0,0 +1,14 @@ +{ + "login": 31288540, + "password": "nwa0#anaEze", + "server": "Deriv-Demo", + "path": "./terminals/deriv/terminal64.exe", + "default-password": "nwa0#anaEze", + "demo": 5463204, + "fin": 24251812, + "deriv-demo": 5463204, + "deriv-demo2": 31288540, + "test-demo": 5400820, + "test-password": "Gr@ndE1f", + "mode": "live" +} \ No newline at end of file diff --git a/tests/live/integration/test_bot.py b/tests/live/integration/test_bot.py index af471d4..65378cd 100644 --- a/tests/live/integration/test_bot.py +++ b/tests/live/integration/test_bot.py @@ -5,7 +5,8 @@ from aiomql.contrib.strategies import Chaos from aiomql.contrib.symbols import ForexSymbol -async def test_bot(): +async def test_bot(capsys): + print('testing') logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") syms = ["BTCUSD", "SOLUSD", "ETHUSD"] symbols = [ForexSymbol(name=sym) for sym in syms] @@ -14,6 +15,7 @@ async def test_bot(): bot.config.task_queue.worker_timeout = 2 bot.executor.timeout = 10 bot.add_strategies(strategies=strategies) + print(bot.executor.strategy_runners) await bot.initialize() bot.executor.execute() assert len(bot.executor.coroutines) == 1 diff --git a/tests/live/test.json b/tests/live/test.json new file mode 100644 index 0000000..fbaa0a3 --- /dev/null +++ b/tests/live/test.json @@ -0,0 +1,14 @@ +{ + "login": 31288540, + "password": "nwa0#anaEze", + "server": "Deriv-Demo", + "path": "./terminals/deriv/terminal64.exe", + "default-password": "nwa0#anaEze", + "demo": 5463204, + "fin": 24251812, + "deriv-demo": 5463204, + "deriv-demo2": 31288540, + "test-demo": 5400820, + "test-password": "Gr@ndE1f", + "mode": "live" +} \ No newline at end of file