This commit is contained in:
Ichinga Samuel
2024-09-09 06:10:44 +01:00
parent 31c9b4bcb7
commit bda2dbf308
33 changed files with 995 additions and 1678 deletions
+1
View File
@@ -75,3 +75,4 @@ target/
config.json
aiomql.json
config/
test_data/
-730
View File
@@ -1,730 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"id": "f4500c8d-0e58-4d3f-8dd3-06a4896f397f",
"metadata": {},
"outputs": [],
"source": [
"from datetime import datetime, timedelta\n",
"from aiomql import MetaTrader, TimeFrame, AccountInfo, TimeFrame, CopyTicks, Account, Symbol\n",
"from MetaTrader5 import SymbolInfo\n",
"import pandas as pd\n",
"from pandas import DataFrame\n",
"import pytz"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "f2ef126c-6edc-4651-8a81-06bb97eed6f9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True\n"
]
}
],
"source": [
"res = await Account().sign_in()\n",
"print(res)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "113d9327-bebc-4e99-8562-e6dbef29b108",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.58\n",
"OrderCheckResult(retcode=0, balance=65.76, equity=63.35, profit=-2.41, margin=1.74, margin_free=61.61, margin_level=3640.8045977011498, comment='Done', request=TradeRequest(action=1, magic=0, order=0, symbol='Volatility 25 (1s) Index', volume=0.005, price=464167.9, stoplimit=0.0, sl=0.0, tp=0.0, deviation=0, type=0, type_filling=0, type_time=0, expiration=0, comment='', position=0, position_by=0))\n"
]
},
{
"data": {
"text/plain": [
"OrderSendResult(retcode=10009, deal=3978355266, order=8068179971, volume=0.005, price=464076.06, bid=464042.01, ask=464076.06, comment='Request executed', request_id=943226517, retcode_external=0, request=TradeRequest(action=1, magic=0, order=0, symbol='Volatility 25 (1s) Index', volume=0.005, price=464167.9, stoplimit=0.0, sl=0.0, tp=0.0, deviation=0, type=0, type_filling=0, type_time=0, expiration=0, comment='', position=0, position_by=0))"
]
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sym = 'Volatility 25 (1s) Index'\n",
"# sym = 'EURUSD'\n",
"symb = Symbol(name=sym)\n",
"await symb.init()\n",
"op = symb.tick.ask + 100\n",
"cl = round((symb.trade_stops_level + symb.spread) * symb.point + symb.ask, symb.digits)\n",
"pr = await symb.mt5.order_calc_profit(action=0, symbol=sym, volume=symb.volume_min, price_open=op, price_close=cl)\n",
"rp = symb.volume_min * symb.trade_contract_size * (cl - op)\n",
"order = {'symbol': sym, 'price': op, 'volume': symb.volume_min, 'action': MetaTrader._TRADE_ACTION_A}\n",
"res = await symb.mt5.order_calc_margin(MetaTrader._ORDER_TYPE_BUY, sym, symb.volume_min, op)\n",
"print(res)\n",
"ocr = await symb.mt5.order_check(order)\n",
"print(ocr)\n",
"await symb.mt5.order_send(order)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "f9189016-55fb-42d4-af77-e389767bc96c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(410, 7710.71, 0.01, 553.9999999999964)"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"symb.trade_stops_level, op, symb.point, abs(cl-op) / symb.point"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "73109885-8291-42cd-90a6-8a04f5f25466",
"metadata": {},
"outputs": [],
"source": [
"acc = Account()\n",
"await acc.refresh()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "dbb5e4f3-286d-4a1f-b59c-60a5af1dc94a",
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "readonly attribute",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[1;32mIn[16], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# acc = await acc.mt5.account_info()\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m \u001b[43macc\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmargin\u001b[49m \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m9\u001b[39m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;66;03m# acc._replace(margin=0)\u001b[39;00m\n",
"\u001b[1;31mAttributeError\u001b[0m: readonly attribute"
]
}
],
"source": [
"# acc = await acc.mt5.account_info()\n",
"acc.margin += 9\n",
"# acc._replace(margin=0)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2c598a85-1e90-49b0-abf1-87329597feae",
"metadata": {},
"outputs": [],
"source": [
"sym = Symbol(name='Volatility 25 Index')\n",
"await sym.init()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a7d319c-5760-4058-994f-27e8d10df108",
"metadata": {},
"outputs": [],
"source": [
"await sym.mt5.order_calc_margin(0, 'Volatility 25 Index', 1, sym.tick.ask)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4ec498ea-5e4e-4ff6-85da-051acc2eaf28",
"metadata": {},
"outputs": [],
"source": [
"sy = await sym.mt5.symbol_info('Volatility 25 Index')\n",
"await sym.mt5.symbol_select('Volatility 25 Index', enable=True)\n",
"sy.trade_calc_mode"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9307e3df-a5f3-42cf-9d3d-e65e5254c0e5",
"metadata": {},
"outputs": [],
"source": [
"# margin\n",
"# worked for forex.\n",
"tcs = 1 * sym.trade_contract_size\n",
"lv = Account().leverage /0.125\n",
"tcs * sym.tick.ask / lv"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e8413f70-244f-47a2-97b3-2be6fcd87589",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "63050d62-443a-4d30-b2a6-4ec6d4f00362",
"metadata": {},
"outputs": [],
"source": [
"symbols = {'Volatility 10 Index', 'Volatility 100 (1s) Index', 'Volatility 25 Index'}\n",
"timeframes = {TimeFrame.M5, TimeFrame.H1, TimeFrame.M1, TimeFrame.H4, TimeFrame.M30, TimeFrame.M15}\n",
"gd = GetData(st, et, timeframes, symbols, name='data')\n",
"await gd.fail()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "49d86767-d5cc-40a8-894e-c8f5b920f8cf",
"metadata": {},
"outputs": [],
"source": [
"symbols = {'Volatility 10 Index', 'Volatility 100 (1s) Index', 'Volatility 25 Index'}\n",
"timeframes = {TimeFrame.M5, TimeFrame.H1, TimeFrame.M1, TimeFrame.H4, TimeFrame.M30, TimeFrame.M15}\n",
"async with MetaTester(st, et, timeframes, symbols, name='data') as mt:\n",
" # res = await mt.copy_ticks_range('Volatility 100 Index', st, et, CopyTicks.ALL)\n",
" # print(res)\n",
" await mt.get_and_save_data()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c4f8c64b-dcba-40cf-8342-53bb4b3fa6e6",
"metadata": {},
"outputs": [],
"source": [
"df = pd.DataFrame(res)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "970126ba-a143-4b45-a82e-8cf6b885763c",
"metadata": {},
"outputs": [],
"source": [
"# df.set_index(list(range(secs)))\n",
"df.drop_duplicates(subset=['time'], keep='last', )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf6d4786-4ee1-4bd4-81e2-fa43bdc527a4",
"metadata": {},
"outputs": [],
"source": [
"df = df.set_index('time', drop=False, verify_integrity=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e788c43f-42d3-4c01-9bd9-5224e2175c1f",
"metadata": {},
"outputs": [],
"source": [
"bg = int(st.timestamp())\n",
"en = int(secs) + bg\n",
"index = range(bg, en)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fd9cffe6-c550-4f54-be8a-5e55b23b81a9",
"metadata": {},
"outputs": [],
"source": [
"bf = len(df.index)\n",
"df = df.reindex(index=index, method='nearest')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c0b60fb9-0c27-4435-900d-898ea6979e11",
"metadata": {},
"outputs": [],
"source": [
"last = df.iloc[-1].time\n",
"print(datetime.fromtimestamp(last, tz=tz), et)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60afd149-9600-4f0e-b3ff-1e8ad10247f3",
"metadata": {},
"outputs": [],
"source": [
"acc = AccountInfo(balance=500)\n",
"data = MetaTester(start, end).load_data('data')\n",
"td = TestData(acc, data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be5704d5-4bff-4ec1-8cbe-2e96bfaf4f11",
"metadata": {},
"outputs": [],
"source": [
"ticks = data['ticks']['Volatility 10 Index']"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12b3d5cb-0d31-4b70-b583-1f2becb74a0f",
"metadata": {},
"outputs": [],
"source": [
"ticks[-1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee0c4ccb-0e7c-434e-a655-fe89b09e606c",
"metadata": {},
"outputs": [],
"source": [
"y = TimeFrame.M5\n",
"y.time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7a28ce72-0409-4231-92f7-f4e61fc0be94",
"metadata": {},
"outputs": [],
"source": [
"end.timestamp()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4aeeb64b-d3d2-4053-9808-2539e2755d09",
"metadata": {},
"outputs": [],
"source": [
"len(ticks)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "29647ef4-01ad-46a0-afb2-f43a4988a529",
"metadata": {},
"outputs": [],
"source": [
"type(ticks[0])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bc593a4b-53d6-4203-a986-33b5267b9244",
"metadata": {},
"outputs": [],
"source": [
"ticks.reshape(8, 86160)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dc6440a3-4333-4363-b462-edee4facdd0f",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a246c6d-d35c-4878-a128-52ed1abb0108",
"metadata": {},
"outputs": [],
"source": [
"res = np.reshape(ticks, (-1, 8))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6e9de365-0f13-4d88-8302-f5e3418d489b",
"metadata": {},
"outputs": [],
"source": [
"ticks.shape = (86160, 8)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "451b8e09-d17c-4662-a25d-39f00ff01788",
"metadata": {},
"outputs": [],
"source": [
"res = np.hstack(ticks)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "395a1b7b-309d-4e38-b579-1b6b4854b19d",
"metadata": {},
"outputs": [],
"source": [
"v = np.array((*ticks[0]))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1bb16ff4-d92e-4474-a235-d8be87d094d6",
"metadata": {},
"outputs": [],
"source": [
"r = next(iter(ticks))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f2241c9c-68eb-4371-b3e3-202ebe25f7cd",
"metadata": {},
"outputs": [],
"source": [
"v.shape"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee0d3713-8578-46e2-969c-aebc295fae98",
"metadata": {},
"outputs": [],
"source": [
"ar = list(range(4))\n",
"t = np.array(ar)\n",
"t.shape = (1, 4)\n",
"t"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8502f7da-793e-4bfe-b469-bc109f051507",
"metadata": {},
"outputs": [],
"source": [
"mt.config.login"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4d818ddd-f78c-4a50-b4a7-dc266321091c",
"metadata": {},
"outputs": [],
"source": [
"acc = Account()\n",
"await acc.sign_in()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c4c32d9-9c45-4ee7-afee-e04d1f5dca4a",
"metadata": {},
"outputs": [],
"source": [
"mt5 = MetaTrader()\n",
"sym = await mt5.symbol_info('Volatility 100 (1s) Index')\n",
"print(sym._asdict)"
]
},
{
"cell_type": "code",
"execution_count": 70,
"id": "e47bd638-42fa-40d9-b573-474a39884dbc",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" time open high low close tick_volume spread \\\n",
"0 1724859360 6770.27 6775.26 6765.13 6765.13 60 144 \n",
"1 1724859420 6766.28 6769.70 6761.25 6761.35 60 144 \n",
"2 1724859480 6762.58 6777.91 6762.58 6776.98 58 144 \n",
"3 1724859540 6778.78 6784.64 6771.89 6782.54 60 144 \n",
"4 1724859600 6781.56 6787.20 6779.02 6784.20 60 144 \n",
".. ... ... ... ... ... ... ... \n",
"495 1724889060 6622.30 6626.43 6620.74 6620.76 60 144 \n",
"496 1724889120 6620.70 6623.06 6611.94 6623.06 60 144 \n",
"497 1724889180 6623.13 6625.32 6618.51 6623.52 60 144 \n",
"498 1724889240 6622.50 6627.44 6621.98 6626.63 60 144 \n",
"499 1724889300 6626.77 6626.77 6626.48 6626.48 2 144 \n",
"\n",
" real_volume \n",
"0 0 \n",
"1 0 \n",
"2 0 \n",
"3 0 \n",
"4 0 \n",
".. ... \n",
"495 0 \n",
"496 0 \n",
"497 0 \n",
"498 0 \n",
"499 0 \n",
"\n",
"[500 rows x 8 columns]"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"await symb.copy_rates_from_pos(timeframe=TimeFrame.M1)\n"
]
},
{
"cell_type": "code",
"execution_count": 72,
"id": "a4bc7b28-6dac-4863-b257-64d2c5c62575",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Empty DataFrame\n",
"Columns: [time, bid, ask, last, volume, time_msc, flags, volume_real]\n",
"Index: []"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tz = pytz.timezone('Etc/UTC')\n",
"now = datetime.now(tz=tz)\n",
"# now.replace(tz=tz-tz)\n",
"await symb.copy_ticks_from(date_from=now)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "90c17d01-4ed0-468d-85c4-64988338b8a9",
"metadata": {},
"outputs": [],
"source": [
"print(start)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9a9141a1-7049-48e5-a96f-0b637b817d40",
"metadata": {},
"outputs": [],
"source": [
"data = shelve.open('./data/01-08-24_17-08-24', writeback=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2d4d4d7-5bfa-43e3-a455-012cf754a106",
"metadata": {},
"outputs": [],
"source": [
"data = dict(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "95931276-fb8c-4a97-b440-330efc5e4d93",
"metadata": {},
"outputs": [],
"source": [
"data = pickle.dumps(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "57c620ad-3fa5-41b9-a372-9a090e18ff85",
"metadata": {},
"outputs": [],
"source": [
"data = zlib.compress(data, level=9)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d85bcdb-a86a-43f0-9b93-7d8c0ce7cf9e",
"metadata": {},
"outputs": [],
"source": [
"_data = lzma.compress(data)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eec34d71-7455-4064-9367-52156407335e",
"metadata": {},
"outputs": [],
"source": [
"fh = lzma.open('./data/ldata.xz', 'w')\n",
"fh.write(_data)\n",
"fh.close()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f38b857-ed2d-4819-ba7f-4f70dc2fb3f9",
"metadata": {},
"outputs": [],
"source": [
"rb = lzma.open('./data/ldata.xz')\n",
"rbb = rb.read()\n",
"rb.close()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "28220ba7-46af-409b-826d-2979ceb19f65",
"metadata": {},
"outputs": [],
"source": [
"rbd = lzma.decompress(rbb)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3275fd6-70ff-4596-b4ba-9d9e4b6e52fe",
"metadata": {},
"outputs": [],
"source": [
"rdata = pickle.loads(rbd)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5bbcbc06-f353-4255-9792-89263793ba0f",
"metadata": {},
"outputs": [],
"source": [
"data.keys()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f9830b77-5e73-45c9-bc6a-40c19c3816c5",
"metadata": {},
"outputs": [],
"source": [
"fh = open('./data/pdata', 'wb')\n",
"pickle.dump(rdata, fh)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "064b8628-1331-4fa3-abf3-66f88c75da33",
"metadata": {},
"outputs": [],
"source": [
"fh.close()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe60fc41-8844-4e00-b254-bb95d28528f2",
"metadata": {},
"outputs": [],
"source": [
"6 / (0 or 5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ceb79fa6-4713-43a5-9850-884cf6262f87",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
-443
View File
@@ -1,443 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "f1039720-9692-4605-adf9-d37651958e4c",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"from pandas import DataFrame, Series"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "8d39819f-2cac-437f-b5fc-633ca7443f8a",
"metadata": {},
"outputs": [],
"source": [
"rs = DataFrame({0: range(10, 101, 10), 1: range(10, 20), 2: range(20, 40, 2), \"symbols\": [chr(i) for i in [65]*5 + [68]*5]})"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "d7976bb8-05cb-4924-a6e2-90ea8af85d9d",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>1</th>\n",
" <th>2</th>\n",
" <th>symbols</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>10</td>\n",
" <td>10</td>\n",
" <td>20</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>20</td>\n",
" <td>11</td>\n",
" <td>22</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>30</td>\n",
" <td>12</td>\n",
" <td>24</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>40</td>\n",
" <td>13</td>\n",
" <td>26</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>50</td>\n",
" <td>14</td>\n",
" <td>28</td>\n",
" <td>A</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 1 2 symbols\n",
"0 10 10 20 A\n",
"1 20 11 22 A\n",
"2 30 12 24 A\n",
"3 40 13 26 A\n",
"4 50 14 28 A"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rs[rs.symbols == 'A']"
]
},
{
"cell_type": "code",
"execution_count": 71,
"id": "eb69c292-79e3-4105-b10c-6f4f5c22074f",
"metadata": {},
"outputs": [
{
"ename": "KeyError",
"evalue": "None",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[1;32mIn[71], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m ind \u001b[38;5;241m=\u001b[39m rs[rs[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124msymbols\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m!=\u001b[39m \u001b[38;5;124m'\u001b[39m\u001b[38;5;124mA\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;241m.\u001b[39miloc[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mindex\u001b[38;5;241m.\u001b[39mname\n\u001b[1;32m----> 2\u001b[0m \u001b[43mrs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mloc\u001b[49m\u001b[43m[\u001b[49m\u001b[43mind\u001b[49m\u001b[43m]\u001b[49m\n",
"File \u001b[1;32m~\\OneDrive - UBA\\Documents\\Personal\\VS\\aiomql\\venv\\Lib\\site-packages\\pandas\\core\\indexing.py:1191\u001b[0m, in \u001b[0;36m_LocationIndexer.__getitem__\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 1189\u001b[0m maybe_callable \u001b[38;5;241m=\u001b[39m com\u001b[38;5;241m.\u001b[39mapply_if_callable(key, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mobj)\n\u001b[0;32m 1190\u001b[0m maybe_callable \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_deprecated_callable_usage(key, maybe_callable)\n\u001b[1;32m-> 1191\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_getitem_axis\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmaybe_callable\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[1;32m~\\OneDrive - UBA\\Documents\\Personal\\VS\\aiomql\\venv\\Lib\\site-packages\\pandas\\core\\indexing.py:1431\u001b[0m, in \u001b[0;36m_LocIndexer._getitem_axis\u001b[1;34m(self, key, axis)\u001b[0m\n\u001b[0;32m 1429\u001b[0m \u001b[38;5;66;03m# fall thru to straight lookup\u001b[39;00m\n\u001b[0;32m 1430\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_validate_key(key, axis)\n\u001b[1;32m-> 1431\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_get_label\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[1;32m~\\OneDrive - UBA\\Documents\\Personal\\VS\\aiomql\\venv\\Lib\\site-packages\\pandas\\core\\indexing.py:1381\u001b[0m, in \u001b[0;36m_LocIndexer._get_label\u001b[1;34m(self, label, axis)\u001b[0m\n\u001b[0;32m 1379\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_get_label\u001b[39m(\u001b[38;5;28mself\u001b[39m, label, axis: AxisInt):\n\u001b[0;32m 1380\u001b[0m \u001b[38;5;66;03m# GH#5567 this will fail if the label is not present in the axis.\u001b[39;00m\n\u001b[1;32m-> 1381\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mobj\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mxs\u001b[49m\u001b[43m(\u001b[49m\u001b[43mlabel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43maxis\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43maxis\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[1;32m~\\OneDrive - UBA\\Documents\\Personal\\VS\\aiomql\\venv\\Lib\\site-packages\\pandas\\core\\generic.py:4301\u001b[0m, in \u001b[0;36mNDFrame.xs\u001b[1;34m(self, key, axis, level, drop_level)\u001b[0m\n\u001b[0;32m 4299\u001b[0m new_index \u001b[38;5;241m=\u001b[39m index[loc]\n\u001b[0;32m 4300\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m-> 4301\u001b[0m loc \u001b[38;5;241m=\u001b[39m \u001b[43mindex\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 4303\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(loc, np\u001b[38;5;241m.\u001b[39mndarray):\n\u001b[0;32m 4304\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m loc\u001b[38;5;241m.\u001b[39mdtype \u001b[38;5;241m==\u001b[39m np\u001b[38;5;241m.\u001b[39mbool_:\n",
"File \u001b[1;32m~\\OneDrive - UBA\\Documents\\Personal\\VS\\aiomql\\venv\\Lib\\site-packages\\pandas\\core\\indexes\\range.py:417\u001b[0m, in \u001b[0;36mRangeIndex.get_loc\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 415\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01merr\u001b[39;00m\n\u001b[0;32m 416\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(key, Hashable):\n\u001b[1;32m--> 417\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key)\n\u001b[0;32m 418\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_check_indexing_error(key)\n\u001b[0;32m 419\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key)\n",
"\u001b[1;31mKeyError\u001b[0m: None"
]
}
],
"source": [
"ind = rs[rs['symbols'] != 'A'].iloc[0].index.name\n",
"rs.loc[ind]"
]
},
{
"cell_type": "code",
"execution_count": 44,
"id": "cce9fa9e-d841-4f45-8813-313f17e7b12f",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>1</th>\n",
" <th>2</th>\n",
" <th>symbols</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>10</td>\n",
" <td>10</td>\n",
" <td>20</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>20</td>\n",
" <td>11</td>\n",
" <td>22</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>30</td>\n",
" <td>12</td>\n",
" <td>24</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>40</td>\n",
" <td>13</td>\n",
" <td>26</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>50</td>\n",
" <td>14</td>\n",
" <td>28</td>\n",
" <td>A</td>\n",
" </tr>\n",
" <tr>\n",
" <th>5</th>\n",
" <td>60</td>\n",
" <td>15</td>\n",
" <td>30</td>\n",
" <td>D</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>70</td>\n",
" <td>16</td>\n",
" <td>32</td>\n",
" <td>D</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>80</td>\n",
" <td>17</td>\n",
" <td>34</td>\n",
" <td>D</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>90</td>\n",
" <td>18</td>\n",
" <td>36</td>\n",
" <td>D</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>100</td>\n",
" <td>19</td>\n",
" <td>38</td>\n",
" <td>D</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 1 2 symbols\n",
"0 10 10 20 A\n",
"1 20 11 22 A\n",
"2 30 12 24 A\n",
"3 40 13 26 A\n",
"4 50 14 28 A\n",
"5 60 15 30 D\n",
"6 70 16 32 D\n",
"7 80 17 34 D\n",
"8 90 18 36 D\n",
"9 100 19 38 D"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rs"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "3c456100-4931-4fbd-a63d-531eaf735e70",
"metadata": {},
"outputs": [
{
"ename": "InvalidIndexError",
"evalue": "RangeIndex(start=0, stop=10, step=1)",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mInvalidIndexError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[1;32mIn[31], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mrs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_loc\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrs\u001b[49m\u001b[43m[\u001b[49m\u001b[43mrs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m<\u001b[39;49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m31\u001b[39;49m\u001b[43m]\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[1;32m~\\OneDrive - UBA\\Documents\\Personal\\VS\\aiomql\\venv\\Lib\\site-packages\\pandas\\core\\indexes\\range.py:418\u001b[0m, in \u001b[0;36mRangeIndex.get_loc\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 416\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(key, Hashable):\n\u001b[0;32m 417\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key)\n\u001b[1;32m--> 418\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_check_indexing_error\u001b[49m\u001b[43m(\u001b[49m\u001b[43mkey\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 419\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(key)\n",
"File \u001b[1;32m~\\OneDrive - UBA\\Documents\\Personal\\VS\\aiomql\\venv\\Lib\\site-packages\\pandas\\core\\indexes\\base.py:6059\u001b[0m, in \u001b[0;36mIndex._check_indexing_error\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 6055\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_check_indexing_error\u001b[39m(\u001b[38;5;28mself\u001b[39m, key):\n\u001b[0;32m 6056\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m is_scalar(key):\n\u001b[0;32m 6057\u001b[0m \u001b[38;5;66;03m# if key is not a scalar, directly raise an error (the code below\u001b[39;00m\n\u001b[0;32m 6058\u001b[0m \u001b[38;5;66;03m# would convert to numpy arrays and raise later any way) - GH29926\u001b[39;00m\n\u001b[1;32m-> 6059\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m InvalidIndexError(key)\n",
"\u001b[1;31mInvalidIndexError\u001b[0m: RangeIndex(start=0, stop=10, step=1)"
]
}
],
"source": [
"rs.index.get_loc(rs[rs.index <= 31].index)"
]
},
{
"cell_type": "code",
"execution_count": 73,
"id": "1a5a99bb-3fc4-458d-95b8-4e031f3e04c1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Index([20, 22, 24, 26, 28, 30, 32, 34, 36, 38], dtype='int64', name=2)"
]
},
"execution_count": 73,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rs.index"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "2d53ccef-72e1-4378-818f-b60682bbd8b7",
"metadata": {},
"outputs": [],
"source": [
"g = rs[rs.index <= 31].iloc[-1]"
]
},
{
"cell_type": "code",
"execution_count": 87,
"id": "7318351e-e185-4695-ada5-43596b4844e6",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"np.int64(20)"
]
},
"execution_count": 87,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"rs.index[0]"
]
},
{
"cell_type": "code",
"execution_count": 114,
"id": "55441116-8c00-4fd1-94c8-939320c6dfc4",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[25, 26, 27, 28, 29]"
]
},
"execution_count": 114,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"t = list(range(30))\n",
"t[25:30]"
]
},
{
"cell_type": "code",
"execution_count": 121,
"id": "6ef28e4c-15a5-4fe5-aa66-10ba464f1256",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[6, 7, 8, 9]"
]
},
"execution_count": 121,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"t[6: 10]"
]
},
{
"cell_type": "code",
"execution_count": 126,
"id": "f15cf18c-54cb-4624-8d18-3998bde2b9ba",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"None\n"
]
}
],
"source": [
"p = 0 or None\n",
"print(p)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "47dd4064-dde6-4824-a12b-ecf334bf4ebe",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+29
View File
@@ -0,0 +1,29 @@
import asyncio
from aiomql import MetaTester, StrategyTester, FingerTrapTest, ForexSymbol, Account, TestData, GetData, Config, \
TestStrategy, EventManager
async def st_testr():
config = Config()
da = GetData.load_data(name=f"{config.test_data_dir}/01-08-24_31-08-24")
# print(da)
td = TestData(da)
st1 = FingerTrapTest(symbol=ForexSymbol(name='Volatility 100 (1s) Index'))
st2 = FingerTrapTest(symbol=ForexSymbol(name='Volatility 25 Index'))
st = StrategyTester(strategies=[st1, st2], test_data=td)
await st.run()
async def st_one():
sym = ForexSymbol(name='Volatility 100 (1s) Index')
st1 = FingerTrapTest(symbol=sym)
config = Config()
da = GetData.load_data(name=f"{config.test_data_dir}/01-08-24_31-08-24")
config.test_data = TestData(da)
st1.set_up()
await sym.init()
await st1.test_single()
asyncio.run(st_testr())
+2 -1
View File
@@ -16,5 +16,6 @@ from .history import History
from .trader import Trader
from .terminal import Terminal
from .sessions import Session, Sessions
from .utils import dict_to_string, round_off, find_bearish_fractal, find_bullish_fractal
from .utils import dict_to_string, round_off, backoff_decorator, error_handler, error_handler_sync, round_up, round_down
from .lib import *
from .contrib import *
+6 -5
View File
@@ -58,14 +58,14 @@ class Account(AccountInfo):
await self.mt5.shutdown()
self.connected = False
async def sign_in(self) -> bool:
async def sign_in(self, **kwargs) -> bool:
"""Connect to a trading account.
Returns:
bool: True if login was successful else False
"""
acc = self.get_dict(include={'login', 'server', 'password'})
self.connected = await self._login(acc=acc)
self.connected = await self._login(acc=acc, **kwargs)
if self.connected:
await self.refresh()
self.symbols = await self.symbols_get()
@@ -73,18 +73,19 @@ class Account(AccountInfo):
await self.mt5.shutdown()
return False
async def _login(self, *, acc: dict, tries=3):
async def _login(self, *, acc: dict, tries=3, **kwargs) -> bool:
res = False
if tries == 0:
return False
ini = await self.mt5.initialize(**acc, path=self.config.path)
init_args = {**acc} | {'path': self.config.path} | {**kwargs}
ini = await self.mt5.initialize(**init_args)
if ini:
res = await self.mt5.login(**acc)
if ini and res:
return True
else:
await asyncio.sleep(5+tries)
return await self._login(acc=acc, tries=tries-1)
return await self._login(acc=acc, tries=tries-1, **kwargs)
def has_symbol(self, symbol: str | SymbolInfo):
"""Checks to see if a symbol is available for a trading account.
+1
View File
@@ -0,0 +1 @@
from .backtester import *
@@ -0,0 +1,7 @@
from .meta_tester import MetaTester
from .test_data import TestData
from .get_data import GetData
from .test_strategy import TestStrategy
from .event_manager import EventManager
from .strategy_tester import StrategyTester
# from .test_executor import FingerTrapTest
+95
View File
@@ -0,0 +1,95 @@
import signal
import asyncio
from asyncio import Condition, Task
import random
class EventManager:
def __init__(self, num_tasks: int, lock = None):
self.event = Condition(lock=lock)
self.num_tasks = num_tasks
self.counter = 0
self.state = 0
self.tasks: list[Task] = []
async def sleep(self, secs):
while secs > self.state:
await self.wait()
async def acquire(self):
await self.event.acquire()
def notify_all(self):
self.event.notify_all()
def sigint_handler(self, sig, frame):
for task in self.tasks:
print(task.get_name())
task.cancel() if not task.done() else ...
async def event_monitor(self):
while True:
async with self.event:
if self.counter == self.num_tasks:
self.counter = 0
self.state += 1
self.event.notify_all()
await asyncio.sleep(0)
async def wait(self):
self.counter += 1
await self.event.wait()
def release(self):
self.event.release()
async def long_task1(event: EventManager):
counter = 0
while True:
await event.acquire()
try:
await event.wait()
sleep = random.randint(1, 2)
await asyncio.sleep(sleep)
counter += 1
print(f'task 1: {event.state}-{counter}')
finally:
event.release()
async def long_task2(event: EventManager):
counter = 0
while True:
await event.acquire()
try:
await event.wait()
sleep = random.randint(1, 2)
await asyncio.sleep(sleep)
counter += 1
print(f'task 2: {event.state}-{counter}')
finally:
event.release()
async def long_task3(event: EventManager):
sleep = 10
while True:
await event.acquire()
try:
await event.wait()
await event.sleep(sleep)
print(f'task 3: {event.state}-{sleep}')
sleep += 10
finally:
event.release()
async def main():
manager = EventManager(num_tasks=3)
signal.signal(signal.SIGINT, manager.sigint_handler)
task1 = asyncio.create_task(long_task1(manager), name='task1')
task2 = asyncio.create_task(long_task2(manager), name='task2')
task3 = asyncio.create_task(long_task3(manager), name='task3')
control = asyncio.create_task(manager.event_monitor(), name='monitor')
manager.tasks.extend([task1, task2, task3, control])
res = await asyncio.gather(task1, task2, task3, control)
asyncio.run(main())
+43
View File
@@ -0,0 +1,43 @@
from functools import wraps
from dataclasses import dataclass, fields, field
from typing import ClassVar
def dd(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__)
return func(*args, **kwargs)
return wrapper
class C:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls)
cls._instance.tasks = []
# cls.__init__(a)
[setattr(cls._instance, k, v) for k, v in kwargs.items()]
return cls._instance
def __init__(self, *args, **kwargs):
print('receiving args')
@dataclass
class D:
b: int = 0
c: str = ''
_fields: list[ClassVar[str]] = field(default_factory=list)
@dd
def setattrs(self, **kwargs):
[setattr(self, k, v) for k, v in kwargs.items() if k in self.fields]
@property
def fields(self):
return self._fields or [name for f in fields(self) if (name := f.name) != '_fields']
d = D()
d.setattrs(r=3)
@@ -0,0 +1,57 @@
import asyncio
from asyncio import Condition, Task
from typing import Self
from ...core import Config
class EventManager:
_instance: Self
task_tracker: int
config: Config
tasks: list[Task]
num_main_tasks: int # main tasks that are directly controlled by the Condition Synchronization primitives
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls)
cls._instance.config = Config()
cls._instance.condition = Condition()
cls._instance.num_main_tasks = 0
cls._instance.task_tracker = 0
cls._instance.tasks = []
return cls._instance
def __init__(self, *, num_tasks: int = 0):
self.num_main_tasks = num_tasks or self.num_main_tasks
def add_task(self, *task: Task):
self.tasks.extend(task)
def sigint_handler(self, sig, frame):
for task in self.tasks:
task.cancel() if not task.done() else ...
async def acquire(self):
await self.condition.acquire()
def notify_all(self):
self.condition.notify_all()
async def event_monitor(self):
while True:
async with self.condition:
if self.task_tracker == self.num_main_tasks:
self.task_tracker = 0
await self.config.test_data.tracker()
self.config.test_data.next()
print(self.config.test_data.cursor.time)
self.condition.notify_all()
await asyncio.sleep(0)
async def wait(self):
self.task_tracker += 1
await self.condition.wait()
def release(self):
self.condition.release()
@@ -1,9 +1,11 @@
from dataclasses import dataclass
from dataclasses import dataclass, field, fields
import pickle
from pathlib import Path
import lzma
from datetime import datetime
from logging import getLogger
import asyncio
from typing import Sequence, ClassVar
import pytz
import pandas as pd
@@ -18,83 +20,127 @@ from ...utils import backoff_decorator
logger = getLogger(__name__)
from MetaTrader5 import TradePosition, TradeOrder, TradeDeal
tof = list(TradeOrder._fields)
tof = list(TradeOrder.__match_args__)
tof.append('symbol')
tpf = list(TradePosition._fields)
tpf = list(TradePosition.__match_args__)
tpf.append('symbol')
tdf = list(TradeDeal._fields)
tdf = list(TradeDeal.__match_args__)
tdf.append('symbol')
@dataclass
class Data:
account: dict
symbols: dict[str, dict]
prices: dict[str, DataFrame]
ticks: dict[str, DataFrame]
rates: dict[str, dict[str, DataFrame]]
span: range
range: range
history_orders: DataFrame = DataFrame([], columns=tof)
history_deals: DataFrame = DataFrame([], columns=tdf)
positions: DataFrame = DataFrame([], columns=tpf)
name: str = ''
terminal: dict[str, [str | int | bool | float]] = field(default_factory=dict)
version: tuple[int, int, str] = (0, 0, '')
account: dict = field(default_factory=dict)
symbols: dict[str, dict] = field(default_factory=dict)
prices: dict[str, DataFrame] = field(default_factory=dict)
ticks: dict[str, DataFrame] = field(default_factory=dict)
rates: dict[str, dict[str, DataFrame]] = field(default_factory=dict)
span: range = range(0)
range: range = range(0)
history_orders: DataFrame = field(default_factory=lambda: DataFrame([], columns=tof))
history_deals: DataFrame = field(default_factory=lambda: DataFrame([], columns=tdf))
positions: dict[str, DataFrame] = field(default_factory=dict)
orders: dict[str, DataFrame] = field(default_factory=dict)
_fields: list[ClassVar[str]] = field(default_factory=list)
def setattrs(self, **kwargs):
[setattr(self, k, v) for k, v in kwargs.items() if k in self.fields]
@property
def fields(self):
return self._fields or [name for f in fields(self) if (name := f.name) != '_fields']
class GetData:
data: Data | None
def __init__(self, *, start: datetime, end: datetime, timeframes: set[TimeFrame], symbols: set[str],
name: str = '', tz: str = 'Etc/UTC'):
def __init__(self, *, start: datetime, end: datetime, symbols: Sequence[str],
timeframes: Sequence[TimeFrame], name: str = '', tz: str = 'Etc/UTC'):
""""""
self.config = Config()
self.tz = pytz.timezone(tz)
self.start = start.replace(tzinfo=self.tz)
self.end = end.replace(tzinfo=self.tz)
self.symbols = symbols
self.timeframes = timeframes
self.symbols = set(symbols)
self.timeframes = set(timeframes)
self.name = name or f"{start:%d-%m-%y}_{end:%d-%m-%y}"
diff = int((self.end - self.start).total_seconds())
self.range = range(diff)
self.span = range(start := int(self.start.timestamp()), diff + start)
self.data = Data(name=name)
self.mt5 = MetaTrader()
async def get_data(self) -> Data:
async def get_data(self):
""""""
rates, ticks, prices, symbols, account = await asyncio.gather(self.get_symbols_rates(), self.get_symbols_ticks(),
self.get_symbols_prices(), self.get_symbols_info(),
self.get_account_info())
return Data(account=account, symbols=symbols, prices=prices, ticks=ticks, rates=rates,
span=self.span, range=self.range)
terminal, version = await asyncio.gather(self.get_terminal_info(), self.get_version())
async def pickle_data(self) -> None:
self.data.setattrs(account=account, symbols=symbols, prices=prices, ticks=ticks, rates=rates,
span=self.span, range=self.range, terminal=terminal, version=version, name=self.name)
def pickle_data(self):
""""""
data = await self.get_data()
fh = open(f'{self.config.root}/data/{self.name}', 'wb')
pickle.dump(data, fh)
fh = open(f'{self.config.test_data_dir}/{self.name}', 'wb')
pickle.dump(self.data, fh)
fh.close()
async def compress_data(self):
""""""
data = await self.get_data()
bdata = pickle.dumps(data)
bdata = pickle.dumps(self.data)
name = self.name + 'xz'
with lzma.open(name, 'w') as fh:
with lzma.open(f'{self.config.test_data_dir}/{name}', 'w') as fh:
fh.write(bdata)
@classmethod
def load_data(cls, name: str, compressed=False) -> dict:
def dump_data(cls, data: Data, name: str | Path, compress: bool = False) -> None:
""""""
fo = open(f'{cls.config.root}/data/{name}', 'rb')
data = fo.read()
try:
fo = open(name, 'wb')
if compress:
data = lzma.compress(pickle.dumps(data))
else:
data = pickle.dumps(data)
if compressed:
data = lzma.decompress(data)
else:
data = pickle.loads(data)
fo.write(data)
fo.close()
except Exception as err:
logger.error(f"Error in dump_data: {err}")
fo.close()
return data
@classmethod
def load_data(cls, *, name: str | Path, compressed=False) -> Data | None:
""""""
try:
fo = open(name, 'rb')
data = fo.read()
if compressed:
data = lzma.decompress(data)
else:
data = pickle.loads(data)
fo.close()
return data
except Exception as err:
logger.error(f"Error: {err}")
return None
async def get_terminal_info(self) -> dict[str, [str | int | bool | float]]:
""""""
terminal = await self.mt5.terminal_info()
return terminal._asdict()
async def get_version(self) -> tuple[int, int, str]:
""""""
version = await self.mt5.version()
return version
async def get_symbols_info(self) -> dict[str, dict]:
""""""
@@ -0,0 +1,188 @@
from datetime import datetime
from logging import getLogger
from numpy import ndarray
from MetaTrader5 import (Tick, SymbolInfo, AccountInfo, TerminalInfo, TradeOrder, TradePosition, TradeDeal,
OrderCheckResult, OrderSendResult)
from .test_data import TestData
from .get_data import GetData
from ...core.meta_trader import MetaTrader
from ...core.constants import TimeFrame, CopyTicks, OrderType
from ...utils import error_handler
logger = getLogger(__name__)
class MetaTester(MetaTrader):
"""A class for testing trading strategies in the MetaTrader 5 terminal. A subclass of MetaTrader."""
def __init__(self, test_data: TestData = None):
super().__init__()
if self.test_data:
self.config.test_data = test_data
@property
def test_data(self) -> TestData | None:
test_data = self.config.test_data
if test_data is None:
...
# logger.error('No Test Data Available')
return test_data
@test_data.setter
def test_data(self, value: TestData):
self.config.test_data = value
async def initialize(self, path: str = "", login: int = 0, password: str = "", server: str = "",
timeout: int | None = None, portable=False, load_test_data: bool = False,
test_data_file: str = '', use_terminal: bool = True) -> bool:
success = True
if self.config.use_terminal_for_backtesting:
success = await super().initialize(path=path, login=login, password=password, server=server, timeout=timeout)
try:
if load_test_data:
name = f"{self.config.test_data_dir_name}/{test_data_file}"
data = GetData.load_data(name=name, compressed=self.config.compress_test_data)
if data is not None:
self.test_data = TestData(data)
success = True
except Exception as err:
logger.error(f'{err}: unable to load test data')
success = False
return success
async def login(self, login: int, password: str, server: str, timeout: int = 60000) -> bool:
return await super().login(login, password, server, timeout) if self.config.use_terminal_for_backtesting else True
async def shutdown(self) -> None:
await super().shutdown() if self.config.use_terminal_for_backtesting else ...
self.test_data.save()
name = self.test_data.data.name
if self.config.compress_test_data:
name += '.xz'
name = self.config.test_data_dir/name
GetData.dump_data(data=self.test_data.data, name=name, compress=self.config.compress_test_data)
@error_handler(msg='test data not available', exe=AttributeError)
async def terminal_info(self) -> TerminalInfo:
return self.test_data.get_terminal_info()
@error_handler(msg='test data not available', exe=AttributeError)
async def account_info(self) -> AccountInfo:
""""""
return self.test_data.get_account_info()
@error_handler(msg='test data not available', exe=AttributeError)
async def symbol_select(self, symbol: str, enable: bool = True) -> bool:
return symbol in self.test_data.symbols and enable
@error_handler(msg='test data not available', exe=AttributeError)
async def symbols_total(self) -> int:
return self.test_data.get_symbols_total()
@error_handler(msg='test data not available', exe=AttributeError)
async def symbols_get(self, group: str = "") -> tuple[SymbolInfo, ...] | None:
""""""
return self.test_data.get_symbols(group)
@error_handler(msg='test data not available', exe=AttributeError)
async def symbol_info(self, symbol: str) -> SymbolInfo | None:
return self.test_data.symbols.get(symbol)
@error_handler(msg='test data not available', exe=AttributeError)
async def symbol_info_tick(self, symbol: str) -> Tick | None:
return self.test_data.get_symbol_info_tick(symbol)
@error_handler(msg='test data not available', exe=AttributeError)
async def copy_rates_from(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float,
count: int) -> ndarray | None:
return self.test_data.get_rates_from(symbol, timeframe, date_from, count)
@error_handler(msg='test data not available', exe=AttributeError)
async def copy_rates_from_pos(self, symbol: str, timeframe: TimeFrame, start_pos: int,
count: int) -> ndarray | None:
return self.test_data.get_rates_from_pos(symbol, timeframe, start_pos, count)
@error_handler(msg='test data not available', exe=AttributeError)
async def copy_rates_range(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float,
date_to: datetime | float) -> ndarray | None:
return self.test_data.get_rates_range(symbol, timeframe, date_from, date_to)
@error_handler(msg='test data not available', exe=AttributeError)
async def copy_ticks_from(self, symbol: str, date_from: datetime | float, count: int,
flags: CopyTicks) -> ndarray | None:
return self.test_data.get_ticks_from(symbol, date_from, count, flags)
@error_handler(msg='test data not available', exe=AttributeError)
async def copy_ticks_range(self, symbol: str, date_from: datetime | float, date_to: datetime | float,
flags: CopyTicks) -> ndarray | None:
return self.test_data.get_ticks_range(symbol, date_from, date_to, flags)
@error_handler(msg='test data not available', exe=AttributeError)
async def orders_total(self) -> int:
return self.test_data.get_orders_total()
@error_handler(msg='test data not available', exe=AttributeError)
async def orders_get(self, group: str = "", ticket: int = 0, symbol: str = "") -> tuple[TradeOrder, ...] | None:
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('symbol', symbol)) if value}
return self.test_data.get_orders(**kwargs)
@error_handler(msg='test data not available', exe=AttributeError)
async def order_calc_margin(self, action: OrderType, symbol: str, volume: float,
price: float, use_terminal: bool = True) -> float | None:
res = await self.test_data.order_calc_margin(action, symbol, volume, price, use_terminal=use_terminal)
return res
@error_handler(msg='test data not available', exe=AttributeError)
async def order_calc_profit(self, action: OrderType, symbol: str, volume: float, price_open: float,
price_close: float, use_terminal: bool = True) -> float | None:
return await self.test_data.order_calc_profit(action, symbol, volume,
price_open, price_close, use_terminal=use_terminal)
@error_handler(msg='test data not available', exe=AttributeError)
async def order_check(self, request: dict, use_terminal: bool = True) -> OrderCheckResult:
return await self.test_data.order_check(request, use_terminal=use_terminal)
async def order_send(self, request: dict, use_terminal: bool = True) -> OrderSendResult:
return await self.test_data.order_send(request, use_terminal=use_terminal)
@error_handler(msg='test data not available', exe=AttributeError)
async def positions_total(self) -> int:
return self.test_data.get_positions_total()
@error_handler(msg='test data not available', exe=AttributeError)
async def positions_get(self, group: str = "", ticket: int = None,
symbol: str = "") -> tuple[TradePosition, ...] | None:
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('symbol', symbol)) if value}
return self.test_data.get_positions(**kwargs)
@error_handler(msg='test data not available', exe=AttributeError)
async def history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
return self.test_data.get_history_orders_total(date_from, date_to)
@error_handler(msg='test data not available', exe=AttributeError)
async def history_orders_get(self, date_from: datetime | float = None, date_to: datetime | float = None,
group: str = '', ticket: int = None,
position: int = None) -> tuple[TradeOrder, ...] | None:
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('position', position)) if value}
args = tuple(arg for arg in (date_from, date_to) if arg)
return self.test_data.get_history_orders(*args, **kwargs)
@error_handler(msg='test data not available', exe=AttributeError)
async def history_deals_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
return self.test_data.get_history_deals_total(date_from, date_to)
@error_handler(msg='test data not available', exe=AttributeError)
async def history_deals_get(self, date_from: datetime | float = None, date_to: datetime | float = None,
group: str = '', ticket: int = None,
position: int = None) -> tuple[TradeDeal, ...] | None:
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('position', position)) if value}
args = tuple(arg for arg in (date_from, date_to) if arg)
return self.test_data.get_history_deals(*args, **kwargs)
@@ -0,0 +1,35 @@
import asyncio
from .event_manager import EventManager
from .get_data import GetData
from .test_data import TestData
from .meta_tester import MetaTester
from ...core import Config
class StrategyTester:
def __init__(self, *, strategies: list = None, test_data: TestData = None, test_data_file: str = ''):
self.config = Config()
self.mt5 = MetaTester()
self.strategies = strategies or []
self.test_data = test_data or self.get_test_data(name=test_data_file)
self.config.test_data = self.test_data
self.event_manager = EventManager(num_tasks=len(self.strategies))
def get_test_data(self, name: str) -> TestData | None:
name = f"{self.config.test_data_dir_name}/{name or self.config.test_data_file}"
data = GetData.load_data(name=name, compressed=self.config.compress_test_data)
return TestData(data) if data is not None else None
async def start(self):
acc = self.config.account_info()
await self.mt5.initialize(**acc)
await self.mt5.login(**acc)
async def run(self):
await self.start()
tasks = [*[asyncio.create_task(strategy.test()) for strategy in self.strategies],
asyncio.create_task(self.event_manager.event_monitor())]
self.event_manager.add_task(*tasks)
await asyncio.gather(*tasks)
await self.mt5.shutdown()
@@ -0,0 +1,37 @@
from dataclasses import dataclass, asdict
from ...core.constants import AccountTradeMode, AccountMarginMode, AccountStopOutMode
@dataclass
class AccountInfo:
login: int = 0
server: str = ''
trade_mode: AccountTradeMode = AccountTradeMode.DEMO
balance: float = 0
leverage: float = 0
profit: float = 0
equity: float = 0
credit: float = 0
margin: float = 0
margin_level: float = 0
margin_free: float = 0
margin_mode: AccountMarginMode = AccountMarginMode.EXCHANGE
margin_so_mode: AccountStopOutMode = AccountStopOutMode.PERCENT
margin_so_call: float = 0
margin_so_so: float = 0
margin_initial: float = 0
margin_maintenance: float = 0
fifo_close: bool = False
limit_orders: float = 0
currency: str = "USD"
trade_allowed: bool = True
trade_expert: bool = True
currency_digits: int = 2
assets: float = 0
liabilities: float = 0
commission_blocked: float = 0
name: str = ''
company: str = ''
def asdict(self):
return asdict(self)
@@ -1,20 +1,23 @@
import asyncio
from collections import namedtuple
from datetime import datetime
from typing import Literal
from itertools import zip_longest
import random
import pandas as pd
import pytz
import numpy as np
from pandas import DataFrame
from MetaTrader5 import (Tick, SymbolInfo, AccountInfo, TradeOrder, TradePosition, TradeDeal,
ORDER_TYPE_BUY, ORDER_TYPE_SELL, TradeRequest, OrderCheckResult, OrderSendResult,
ACCOUNT_STOPOUT_MODE_PERCENT)
from ..meta_trader import MetaTrader
from ..constants import TimeFrame, CopyTicks, OrderType, TradeAction
TradeRequest, OrderCheckResult, OrderSendResult, TerminalInfo)
from ...core.meta_trader import MetaTrader
from ...core.constants import TimeFrame, CopyTicks, OrderType, TradeAction, AccountStopOutMode
from .get_data import Data
from ...account import Account
from ...utils import round_down, round_up
from .test_account import AccountInfo as Account
from ...utils import round_down, round_up, error_handler, error_handler_sync
# from .event_manager import EventManager
tz = pytz.timezone('Etc/UTC')
Cursor = namedtuple('Cursor', ['index', 'time'])
@@ -26,16 +29,17 @@ class TestData:
def __init__(self, data: Data):
self._data = data
self.account = Account(**data.account)
self.symbols = {symbol: SymbolInfo(**info) for symbol, info in data.symbols.items()}
self.prices = data.prices
self.ticks = data.ticks
self.rates = data.rates
self.span = data.span
self.range = data.range
self.cursor = Cursor(index=self.range[0], time=self.span[0])
self.iter = zip_longest(self.range, self.span)
self.version: tuple[int, int, str] = data.version
self.terminal_info = TerminalInfo(data.terminal)
self.account: Account = Account(**data.account)
self.symbols: dict[str, SymbolInfo] = {symbol: SymbolInfo(info) for symbol, info in data.symbols.items()}
self.prices: dict[str, DataFrame] = data.prices
self.ticks: dict[str, DataFrame] = data.ticks
self.rates: dict[str, dict[str, DataFrame]] = data.rates
self.span: range = data.span
self.range: range = data.range
self.orders: dict[str, dict[int, TradeOrder]] = {}
self.deals: dict[str, dict[int, TradeDeal]] = {}
self.open_orders: dict[int, TradeOrder] = {}
self.positions: dict[str, dict[int, TradePosition]] = {}
self.open_positions: dict[int, TradePosition] = {}
@@ -43,38 +47,261 @@ class TestData:
self.history_deals = data.history_deals
self.margins: dict[int, float] = {}
self.mt5 = MetaTrader()
self.iter = zip_longest(self.range, self.span)
self.cursor = next(self)
# self.event_manager = EventManager()
def __next__(self):
def __next__(self) -> Cursor:
index, time = next(self.iter)
self.cursor = Cursor(index=index, time=time)
return self.cursor
def next(self) -> Cursor:
return next(self)
@property
def data(self):
return self._data
def reset(self):
self.iter = zip_longest(self.range, self.span)
self.cursor = Cursor(index=self.range[0], time=self.span[0])
return self.cursor
def go_to(self, index: int, time: int):
range_ = range(time, self.range.stop, self.range.step)
span = range(index, self.span.stop, self.span.step)
self.iter = zip_longest(range_, span)
self.cursor = next(self)
def get_dtype(self, df: DataFrame) -> list[tuple[str, str]]:
return [(c, t) for c, t in zip(df.columns, df.dtypes)]
async def tracker(self):
pos_tasks = [self.check_position(ticket) for ticket in self.open_positions]
await asyncio.gather(*pos_tasks)
order_tasks = [self.check_order(ticket) for ticket in self.open_orders]
await asyncio.gather(*order_tasks)
def save(self):
self._data.history_deals = self.history_deals
self._data.history_orders = self.history_orders
for symbol in self.orders:
self.history_orders = pd.concat([DataFrame(self.orders[symbol].values()), self.history_orders])
self._data.history_orders = self.history_orders
for symbol in self.deals:
self.history_deals = pd.concat([DataFrame(self.deals[symbol].values()), self.history_deals])
self._data.history_deals = self.history_deals
@error_handler
async def check_order(self, ticket: int):
order = self.open_orders[ticket]
order_type, symbol = order.type, order.symbol
tick = self.prices[symbol].loc[self.cursor.time]
tp, sl = order.tp, order.sl
match order_type:
case OrderType.BUY:
if tp >= tick.bid or sl <= tick.bid:
self.close_position(ticket)
case OrderType.SELL:
if tp <= tick.ask or sl >= tick.ask:
self.close_position(ticket)
case _:
...
@error_handler
async def check_position(self, ticket: int, use_terminal=True):
pos = self.open_positions[ticket]
order_type, symbol, volume, price_open, prev_profit = pos.type, pos.symbol, pos.volume, pos.price_open, pos.profit
tick = self.prices[symbol].loc[self.cursor.time]
price_current = tick.bid if order_type == OrderType.BUY else tick.ask
profit = await self.order_calc_profit(order_type, symbol, volume, price_open, price_current, use_terminal)
self.update_account(equity=profit - prev_profit)
pos = pos._asdict()
pos.update(profit=profit, price_current=price_current, time_update=self.cursor.time)
pos = TradePosition(pos)
self.open_positions[ticket] = pos
self.positions[symbol][ticket] = pos
def close_position(self, ticket: int):
position = self.open_positions.pop(ticket)
margin = self.margins.pop(position.ticket)
order = self.open_orders.pop(ticket)
order = order._asdict()
order.update(time_done=self.cursor.time)
self.orders[order['symbol']][ticket] = TradeOrder(order)
self.update_account(profit=position.profit, margin=-margin)
def modify_stops(self, ticket: int, sl: int = None, tp: int = None):
pos = self.open_positions.pop(ticket)
order = self.open_orders.pop(ticket)
sl = sl or pos.sl
tp = tp or pos.tp
pos = pos._asdict()
pos.update(tp=tp, sl=sl, time_update=self.cursor.time)
sl = sl or order.sl
tp = tp or order.tp
order = order._asdict()
order.update(tp=tp, sl=sl)
pos = TradePosition(pos)
order = TradeOrder(order)
self.open_positions[ticket] = pos
self.open_orders[ticket] = order
self.positions[pos.symbol][ticket] = pos
self.orders[order.symbol][ticket] = order
def update_account(self, *, profit: float = 0, margin: float = 0, equity: float = 0):
self.account.balance += profit
self.account.equity += equity
self.account.margin += margin
self.account.margin_free = self.account.equity - self.account.margin
self.account.margin_level = (self.account.equity / (self.account.margin or 1)) * 100 \
if self.account.margin_mode == AccountStopOutMode.PERCENT else self.account.margin_free
@error_handler
async def order_send(self, request: dict, use_terminal: bool = True) -> OrderSendResult:
osr = {'retcode': 10009, 'comment': 'Request completed', 'request': TradeRequest(request)}
if (position := request.get('position')) in self.open_positions:
pos = self.open_positions[position]
order_type = OrderType(request['type'])
pos_type = OrderType(pos.type)
if order_type.opposite == pos_type: # ToDo: is there another way to check if the order is a close order?
# close position
self.close_position(pos.ticket)
return OrderSendResult(osr) # ToDo: Create a deal object here
action = request['action']
if action == TradeAction.SLTP:
self.modify_stops(position, request['sl'], request['tp'])
return OrderSendResult(osr)
if (action := request.get('action')) == TradeAction.DEAL:
ocr = await self.order_check(request, use_terminal=use_terminal)
if ocr.retcode != 0:
osr.update({'comment': ocr.comment, 'retcode': ocr.retcode})
return OrderSendResult(osr)
ticket = random.randint(100_000_000, 999_999_999)
deal_ticket = random.randint(100_000_000, 999_999_999)
tick = self.get_symbol_info_tick(request['symbol'])
order_type = request['type']
price = tick.ask if request['type'] == OrderType.BUY else tick.bid
volume = request['volume']
sl, tp = request.get('sl', 0), request.get('tp', 0)
symbol = request['symbol']
pos = {'comment': 'open position', 'ticket': ticket, 'symbol': symbol, 'volume': volume,
'price_open': price, 'price_current': price, 'type': order_type, 'profit': 0,
'sl': sl, 'tp': tp, 'time': tick.time,
'time_msc': tick.time_msc}
order = {'ticket': ticket, 'symbol': symbol, 'volume': volume, 'price': price, 'price_current': price,
'price_open': price, 'type': order_type, 'time_setup': tick.time,
'time_setup_msc': tick.time_msc, 'volume_current': volume, 'sl': sl, 'tp': tp, }
pos = TradePosition(pos)
order = TradeOrder(order)
# ToDo: Create a deal object here
self.open_positions[pos.ticket] = pos
self.open_orders[order.ticket] = order
self.orders.setdefault(order.symbol, {})[order.ticket] = order
self.positions.setdefault(pos.symbol, {})[pos.ticket] = pos
osr.update({'order': ticket, 'price': price, 'volume': volume, 'bid': tick.bid,
'ask': tick.ask, 'deal': deal_ticket})
margin = await self.order_calc_margin(action, symbol, volume, price, use_terminal=use_terminal)
self.margins[ticket] = margin
self.update_account(margin=margin)
return OrderSendResult(osr)
@error_handler
async def order_check(self, request: dict, use_terminal=True) -> OrderCheckResult:
action, symbol, volume = request.get('action'), request.get('symbol'), request.get('volume')
price = request.get('price')
ocr = {'retcode': 0, 'balance': 0, 'profit': 0, 'margin': 0, 'equity': 0, 'margin_free': 0,
'margin_level': 0, 'comment': 'Done', request: TradeRequest(request)}
margin = 0
if all([action, symbol, volume, price]):
margin = await self.order_calc_margin(action, symbol, volume, price, use_terminal=use_terminal)
acc = self.get_account_info()
equity = acc.equity
used_margin = acc.margin + margin
free_margin = acc.margin_free - margin
margin_level = (equity / used_margin) * 100 if (
acc.margin_mode == AccountStopOutMode.PERCENT and used_margin > 0) else free_margin
if use_terminal and self.mt5.config.use_terminal_for_backtesting:
ocr_t = await self.mt5.order_check(request)
# return order check result if invalid stops level are detected or bad request
if ocr_t.retcode in (10016, 10013, 10014):
return ocr_t
sym = self.symbols[symbol]
tsl = sym.trade_stops_level
sl, tp = request.get('sl', 0), request.get('tp', 0)
# check if the stops level is valid
if tp or sl:
min_sl = min(sl, tp)
dsl = abs(price - min_sl) / sym.point
if dsl < tsl:
ocr['retcode'] = 10016
ocr['comment'] = 'Invalid stops'
return OrderCheckResult(ocr)
# check if the account has enough money
if margin_level < acc.margin_so_call:
ocr['retcode'] = 10019
ocr['comment'] = 'No money'
# check volume
if volume < sym.volume_min or volume > sym.volume_max:
ocr['retcode'] = 10014
ocr['comment'] = 'Invalid volume'
ocr.update({'balance': acc.balance, 'profit': acc.profit, 'margin': used_margin, 'equity': equity,
'margin_free': free_margin, 'margin_level': margin_level})
return OrderCheckResult(ocr)
@error_handler_sync
def get_terminal_info(self) -> TerminalInfo:
return self.terminal_info
@error_handler_sync
def get_version(self) -> tuple[int, int, str]:
return self.version
@error_handler_sync
def get_symbols_total(self) -> int:
return len(self.symbols)
def get_symbols(self) -> list:
return list(self.symbols.keys())
@error_handler_sync
def get_symbols(self, group: str = '') -> tuple[SymbolInfo, ...]:
return tuple(list(self.symbols.values()))
@error_handler_sync
def get_account_info(self) -> AccountInfo:
return AccountInfo(**self.account._asdict())
return AccountInfo(self.account.asdict())
@error_handler_sync
def get_symbol_info_tick(self, symbol: str) -> Tick:
tick = self.prices[symbol].iloc[self.cursor.index]
return Tick(**tick)
return Tick(tick)
@error_handler_sync
def get_symbol_info(self, symbol: str) -> SymbolInfo:
info = self.symbols[symbol]
tick = self.get_symbol_info_tick(symbol)
info = info._asdict()
info |= {'bid': tick.bid, 'bidhigh': tick.bid, 'bidlow': tick.bid, 'ask': tick.ask,
'askhigh': tick.ask, 'asklow': tick.bid, 'last': tick.last, 'volume_real': tick.volume_real}
return SymbolInfo(**info)
return SymbolInfo(info)
@error_handler_sync
def get_rates_from(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float, count: int) -> np.ndarray:
rates = self.rates[symbol][timeframe.name]
start = int(datetime.timestamp(date_from)) if isinstance(date_from, datetime) else int(date_from)
@@ -82,14 +309,16 @@ class TestData:
start = rates[rates.index <= start].iloc[-1].name
start = rates.index.get_loc(start)
end = start + count
return rates.iloc[start:end].to_numpy()
return np.fromiter((tuple(i) for i in rates.iloc[start:end].iloc), dtype=self.get_dtype(rates))
@error_handler_sync
def get_rates_from_pos(self, symbol: str, timeframe: TimeFrame, start_pos: int, count: int) -> np.ndarray:
rates = self.rates[symbol][timeframe.name]
end = -start_pos + count
end = end or None
return rates.iloc[-start_pos:end].to_numpy()
return np.fromiter((tuple(i) for i in rates.iloc[-start_pos:end].iloc), dtype=self.get_dtype(rates))
@error_handler_sync
def get_rates_range(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float, date_to: datetime | float) -> np.ndarray:
rates = self.rates[symbol][timeframe.name]
start = int(datetime.timestamp(date_from)) if isinstance(date_from, datetime) else int(date_from)
@@ -98,180 +327,50 @@ class TestData:
end = int(datetime.timestamp(date_to)) if isinstance(date_to, datetime) else int(date_to)
end = round_up(end, timeframe.time)
end = rates[rates.index >= end].iloc[-1].name
return rates.loc[start:end].to_numpy()
return np.fromiter((tuple(i) for i in rates.loc[start:end].iloc), dtype=self.get_dtype(rates))
@error_handler_sync
def get_ticks_from(self, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks) -> np.ndarray:
ticks = self.ticks[symbol]
start = int(datetime.timestamp(date_from)) if isinstance(date_from, datetime) else int(date_from)
start = ticks[ticks.index <= start].iloc[-1].name
start = ticks.index.get_loc(start)
end = start + count
return ticks.iloc[start:end]
return np.fromiter((tuple(i) for i in ticks.iloc[start:end].iloc), dtype=self.get_dtype(ticks))
@error_handler_sync
def get_ticks_range(self, symbol: str, date_from: datetime | float, date_to: datetime | float, flags) -> np.ndarray:
ticks = self.ticks[symbol]
start = int(datetime.timestamp(date_from)) if isinstance(date_from, datetime) else int(date_from)
start = ticks[ticks.index <= start].iloc[-1].index
end = int(datetime.timestamp(date_to)) if isinstance(date_to, datetime) else int(date_to)
end = ticks[ticks.index >= end].iloc[-1].index
return ticks.loc[start:end].to_numpy()
return np.fromiter((tuple(i) for i in ticks.loc[start:end].iloc), dtype=self.get_dtype(ticks))
@error_handler
async def order_calc_margin(self, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float,
price: float, use_terminal=False):
if use_terminal and self.mt5.config.use_terminal_for_backtesting:
return await self.mt5.order_calc_margin(OrderType(action), symbol, volume, price)
return await self.mt5.order_calc_margin(action, symbol, volume, price)
sym = self.symbols[symbol]
margin = (volume * sym.trade_contract_size * price) / (self.account.leverage / (sym.margin_initial or 1))
return margin
return round(margin, self.account.currency_digits)
@error_handler
async def order_calc_profit(self, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float,
price_open: float, price_close: float, use_terminal=True):
if use_terminal and self.mt5.config.use_terminal_for_backtesting:
return await self.mt5.order_calc_profit(action, symbol, volume, price_open, price_close)
sym = self.symbols[symbol]
profit = volume * sym.trade_contract_size * (price_close - price_open)
return profit
profit = (volume * sym.trade_contract_size *
((price_close - price_open) if action == OrderType.BUY else (price_open - price_close)))
return round(profit, self.account.currency_digits)
def check_order(self, ticket: int) -> bool:
order = self.open_orders[ticket]
order_type, symbol = order.type, order.symbol
tick = self.prices[symbol].loc[self.cursor.time]
tp, sl = order.tp, order.sl
match order_type:
case self.mt5._ORDER_TYPE_BUY:
if tp >= tick.bid or sl <= tick.bid:
self.close_position(ticket)
case self.mt5.ORDER_TYPE_SELL:
if tp <= tick.ask or sl >= tick.ask:
self.close_position(ticket)
case _:
...
def check_position(self, ticket: int) -> bool:
...
def close_position(self, ticket: int):
position = self.open_positions.pop(ticket)
margin = self.margins.pop(position.ticket)
profit = position.profit
self.update_account(profit, margin=margin)
async def modify_stops(self, ticket: int, sl: int = None, tp: int = None):
pos = self.open_positions.pop(ticket)
sl = sl or pos.sl
tp = tp or pos.tp
order_type, symbol, volume, price_open = pos.order_type, pos.symbol, pos.volume
pos = pos._asdict()
pos.update(tp=tp, sl=sl, time_update=self.cursor.time)
profit = await self.mt5.order_calc_profit(order_type, symbol, volume, price_open, sl)
self.open_positions[ticket] = TradePosition(**pos)
def update_account(self, profit: float, margin: float = 0):
self.account.balance += profit
self.account.equity += profit
self.account.margin -= margin
self.account.margin_free = self.account.equity - self.account.margin
self.account.margin_level = (self.account.equity / self.account.margin) * 100 if self.account.margin_mode
async def order_send(self, request: dict, use_terminal: bool = True) -> OrderSendResult:
osr = {'retcode': 10009, 'comment': 'Request completed', 'request': TradeRequest(**request)}
if (position := request.get('position')) in self.open_positions:
pos = self.open_positions[position]
order_type = OrderType(request['type'])
pos_type = OrderType(pos.type)
if order_type.opposite == pos_type: # ToDo: is there another way to check if the order is a close order?
# close position
self.close_position(pos)
return OrderSendResult(**osr) # ToDo: Create a deal object here
action = request['action']
if action == TradeAction.SLTP:
self.modify_stops(position, request['sl'], request['tp'])
return OrderSendResult(**osr)
if (action := request.get('action')) == TradeAction.DEAL:
ocr = await self.order_check(request, use_terminal=use_terminal)
if ocr.retcode != 0:
osr.update({'comment': ocr.comment, 'retcode': ocr.retcode})
return OrderSendResult(**osr)
ticket = random.randint(100_000_000, 999_999_999)
deal_ticket = random.randint(100_000_000, 999_999_999)
tick = self.get_symbol_info_tick(request['symbol'])
order_type = request['type']
price = tick.ask if request['type'] == ORDER_TYPE_BUY else tick.bid
volume = request['volume']
sl, tp = request.get('sl', 0), request.get('tp', 0)
symbol = request['symbol']
pos = {'comment': 'open position', 'ticket': ticket, 'symbol': symbol, 'volume': volume,
'price_open': price, 'price_current': price, 'type': order_type, 'profit': 0,
'sl': sl, 'tp': tp, 'time': tick.time,
'time_msc': tick.time_msc}
order = {'ticket': ticket, 'symbol': symbol, 'volume': volume, 'price': price, 'price_current': price,
'price_open': price, 'type': order_type, 'time_setup': tick.time,
'time_setup_msc': tick.time_msc, 'volume_current': volume, 'sl': sl, 'tp': tp,}
pos = TradePosition(**pos)
order = TradeOrder(**order)
# ToDo: Create a deal object here
self.open_positions[pos.ticket] = pos
self.open_orders[order.ticket] = order
self.orders.setdefault(order.symbol, {})[order.ticket] = order
self.positions.setdefault(pos.symbol, {})[pos.ticket] = pos
osr.update({'order': ticket, 'price': price, 'volume': volume, 'bid': tick.bid,
'ask': tick.ask, 'deal': deal_ticket})
margin = await self.order_calc_margin(action, symbol, volume, price)
self.margins[ticket] = margin
return OrderSendResult(**osr)
async def order_check(self, request: dict, use_terminal=True) -> OrderCheckResult:
action, symbol, volume = request.get('action'), request.get('symbol'), request.get('volume')
price = request.get('price')
ocr = {'retcode': 0, 'balance': 0, 'profit': 0, 'margin': 0, 'equity': 0, 'margin_free': 0,
'margin_level': 0, 'comment': 'Done', request: TradeRequest(**request)}
margin = 0
if all([action, symbol, volume, price]):
margin = await self.order_calc_margin(action, symbol, volume, price)
acc = self.get_account_info()
equity = acc.equity
used_margin = acc.margin + margin
free_margin = acc.margin_free - margin
margin_level = (equity / used_margin) * 100 if (acc.margin_mode == ACCOUNT_STOPOUT_MODE_PERCENT and used_margin > 0) else free_margin
if use_terminal and self.mt5.config.use_terminal_for_backtesting:
ocr_t = await self.mt5.order_check(request)
# return order check result if invalid stops level are detected or bad request
if ocr_t.retcode in (10016, 10013, 10014):
return ocr_t
else:
sym = self.symbols[symbol]
tsl = sym.trade_stops_level
sl, tp = request.get('sl', 0), request.get('tp', 0)
if tp or sl:
min_sl = min(sl, tp)
dsl = abs(price - min_sl) / sym.point
if dsl < tsl:
ocr['retcode'] = 10016
ocr['comment'] = 'Invalid stops'
return OrderCheckResult(**ocr)
if margin_level < acc.margin_so_call:
ocr['retcode'] = 10019
ocr['comment'] = 'No money'
ocr.update({'balance': acc.balance, 'profit': acc.profit, 'margin': used_margin, 'equity': equity,
'margin_free': free_margin, 'margin_level': margin_level})
return OrderCheckResult(**ocr)
@error_handler_sync
def get_orders_total(self) -> int:
return len(self.open_orders)
@error_handler_sync
def get_orders(self, symbol: str = '', group: str = '', ticket: int = None) -> tuple[TradeOrder, ...]:
if ticket:
order = self.open_orders.get(ticket)
@@ -286,9 +385,11 @@ class TestData:
else:
return tuple(order for order in self.open_orders.values())
def get_positions_total(self):
@error_handler_sync
def get_positions_total(self) -> int:
return len(self.open_positions)
@error_handler_sync
def get_positions(self, symbol: str = '', group: str = '', ticket: int = None) -> tuple[TradePosition, ...]:
if ticket:
position = self.open_positions.get(ticket)
@@ -302,15 +403,17 @@ class TestData:
else:
return tuple(position for position in self.open_positions.values())
def history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
@error_handler_sync
def get_history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
start = int(date_from.timestamp()) if isinstance(date_from, datetime) else int(date_from)
end = int(date_to.timestamp()) if isinstance(date_to, datetime) else int(date_to)
start = self.history_orders[self.history_orders.index >= start].iloc[0].name
end = self.history_orders[self.history_orders.index <= end].iloc[-1].name
return self.history_orders.loc[start:end].shape[0]
def history_orders_get(self, date_from: datetime | float, date_to: datetime | float, group: str = '',
@error_handler_sync
def get_history_orders(self, date_from: datetime | float, date_to: datetime | float, group: str = '',
ticket: int = None, position: int = None) -> tuple[TradeOrder, ...]:
start = int(date_from.timestamp()) if isinstance(date_from, datetime) else int(date_from)
end = int(date_to.timestamp()) if isinstance(date_to, datetime) else int(date_to)
@@ -328,8 +431,9 @@ class TestData:
...
orders.drop(columns=['symbol'], inplace=True)
return tuple(TradeOrder(**order) for order in orders.to_dict(orient='records'))
return tuple(TradeOrder(order) for order in orders.iloc)
@error_handler_sync
def get_history_deals_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
start = int(date_from.timestamp()) if isinstance(date_from, datetime) else int(date_from)
end = int(date_to.timestamp()) if isinstance(date_to, datetime) else int(date_to)
@@ -337,6 +441,7 @@ class TestData:
end = self.history_deals[self.history_deals.index <= end].iloc[-1].name
return self.history_deals.loc[start:end].shape[0]
@error_handler_sync
def get_history_deals(self, date_from: datetime | float, date_to: datetime | float, group: str = '',
position: int = None, ticket: int = None) -> tuple[TradeDeal, ...]:
start = int(date_from.timestamp()) if isinstance(date_from, datetime) else int(date_from)
@@ -354,5 +459,4 @@ class TestData:
elif group:
...
deals.drop(columns=['symbol'], inplace=True)
return tuple(TradeDeal(**deal) for deal in deals.to_dict(orient='records'))
return tuple(TradeDeal(deal) for deal in deals.iloc)
@@ -0,0 +1,2 @@
class FingerTrapTest:
...
@@ -0,0 +1,20 @@
from .event_manager import EventManager
from ...core.config import Config
class TestStrategy:
event_manager: EventManager
config: Config
def set_up(self):
self.config = Config()
self.event_manager = EventManager()
async def sleep(self, secs: float):
time = self.config.test_data.cursor.time
mod = time % secs
secs = secs - mod if mod != 0 else mod
time = self.config.test_data.cursor.time + secs
while time > self.config.test_data.cursor.time:
await self.event_manager.wait()
-3
View File
@@ -1,3 +0,0 @@
from .meta_tester import MetaTester
from .test_data import TestData
from .get_data import GetData
-23
View File
@@ -1,23 +0,0 @@
import socket
class socketserver:
def __init__(self, address = '192.168.1.15', port = 9090):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.address = address
self.port = port
self.sock.bind((self.address, self.port))
self.cummdata = ''
def recvmsg(self):
g=self.sock.listen(1)
print(g)
self.conn, self.addr = self.sock.accept()
print('connected to', self.addr)
data = self.conn.recv(10)
self.cummdata += data.decode("utf-8")
so = socketserver()
so.recvmsg()
-1
View File
@@ -1 +0,0 @@
-207
View File
@@ -1,207 +0,0 @@
import pickle
from datetime import datetime
from logging import getLogger
import asyncio
import re
import pytz
from MetaTrader5 import Tick, SymbolInfo
import pandas as pd
from ... import TestData
from ...core.meta_trader import MetaTrader
from ...core.config import Config
from ...core.errors import Error
from ...core.constants import TimeFrame, CopyTicks, OrderType
from ...core.models import (AccountInfo, SymbolInfo, BookInfo, TradeOrder, OrderCheckResult, OrderSendResult,
TradePosition, TradeDeal)
from ...utils import backoff_decorator
from .test_data import TestData
from .get_data import GetData
logger = getLogger(__name__)
class MetaTester(MetaTrader):
"""A class for testing trading strategies in the MetaTrader 5 terminal. A subclass of MetaTrader."""
data: TestData
def __init__(self, data: TestData = None):
super().__init__()
self.data = data
async def initialize(self, path: str = "", login: int = 0, password: str = "", server: str = "",
timeout: int | None = None, portable=False, compressed: bool = False) -> bool:
self.data = await GetData.load_data(name=path, compressed=compressed)
return True
async def account_info(self) -> AccountInfo:
""""""
res = self.data.account
return res
async def symbols_total(self) -> int:
return len(self.data.symbols)
async def symbols_get(self, group: str = "") -> tuple[SymbolInfo]:
""""""
symbols = self.data.symbols.values()
return tuple(symbols)
async def symbol_info(self, symbol: str) -> SymbolInfo | None:
return self.data.symbols.get(symbol)
async def symbol_info_tick(self, symbol: str) -> Tick | None:
res = await asyncio.to_thread(self._symbol_info_tick, symbol)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in obtaining tick for {symbol}.{self.error.description}')
return res
return res
async def symbol_select(self, symbol: str, enable: bool) -> bool:
return await asyncio.to_thread(self._symbol_select, symbol, enable)
async def copy_rates_from(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float, count: int):
res = await asyncio.to_thread(self._copy_rates_from, symbol, timeframe, date_from, count)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in obtaining rates for {symbol}.{self.error.description}')
return res
return res
async def copy_rates_from_pos(self, symbol: str, timeframe: TimeFrame, start_pos: int, count: int):
res = await asyncio.to_thread(self._copy_rates_from_pos, symbol, timeframe, start_pos, count)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in obtaining rates for {symbol}.{self.error.description}')
return res
return res
async def copy_rates_range(self, symbol: str, timeframe: TimeFrame, date_from: datetime | float,
date_to: datetime | float):
res = await asyncio.to_thread(self._copy_rates_range, symbol, timeframe, date_from, date_to)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in obtaining rates for {symbol}.{self.error.description}')
return res
return res
async def copy_ticks_from(self, symbol: str, date_from: datetime | float, count: int, flags: CopyTicks):
res = await asyncio.to_thread(self._copy_ticks_from, symbol, date_from, count, flags)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in obtaining ticks for {symbol}.{self.error.description}')
return res
return res
async def copy_ticks_range(self, symbol: str, date_from: datetime | float, date_to: datetime | float,
flags: CopyTicks):
res = await asyncio.to_thread(self._copy_ticks_range, symbol, date_from, date_to, flags)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in obtaining ticks for {symbol}.{self.error.description}')
return res
return res
async def orders_total(self) -> int:
return await asyncio.to_thread(self._orders_total)
async def orders_get(self, group: str = "", ticket: int = 0, symbol: str = "") -> tuple[TradeOrder] | None:
"""Get active orders with the ability to filter by symbol or ticket. There are three call options.
Call without parameters. Return active orders on all symbols
Keyword Args:
symbol (str): Symbol name. Optional named parameter. If a symbol is specified, the ticket parameter is ignored.
group (str): The filter for arranging a group of necessary symbols. Optional named parameter. If the group is specified, the function
returns only active orders meeting a specified criteria for a symbol name.
ticket (int): Order ticket (ORDER_TICKET). Optional named parameter.
Returns:
tuple[TradeOrder]: A list of active trade orders as TradeOrder objects
"""
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('symbol', symbol)) if value}
res = await asyncio.to_thread(self._orders_get, **kwargs)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in obtaining orders.{self.error.description}')
return res
return res
async def order_calc_margin(self, action: OrderType, symbol: str, volume: float, price: float) -> float | None:
res = await asyncio.to_thread(self._order_calc_margin, action, symbol, volume, price)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in calculating margin.{self.error.description}')
return res
return res
async def order_calc_profit(self, action: OrderType, symbol: str, volume: float, price_open: float,
price_close: float) -> float | None:
res = await asyncio.to_thread(self._order_calc_profit, action, symbol, volume, price_open, price_close)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in calculating profit.{self.error.description}')
return res
return res
async def order_check(self, request: dict) -> OrderCheckResult:
return await asyncio.to_thread(self._order_check, request)
async def order_send(self, request: dict) -> OrderSendResult:
return await asyncio.to_thread(self._order_send, request)
async def positions_total(self) -> int:
return await asyncio.to_thread(self._positions_total)
async def positions_get(self, group: str = "", ticket: int = None, symbol: str = "") -> tuple[TradePosition] | None:
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('symbol', symbol)) if value}
res = await asyncio.to_thread(self._positions_get, **kwargs)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in obtaining open positions.{self.error.description}')
return res
return res
async def history_orders_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
return await asyncio.to_thread(self._history_orders_total, date_from, date_to)
async def history_orders_get(self, date_from: datetime | float = None, date_to: datetime | float = None,
group: str = '', ticket: int = None, position: int = None) -> tuple[TradeOrder] | None:
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('position', position)) if value}
args = tuple(arg for arg in (date_from, date_to) if arg)
res = await asyncio.to_thread(self._history_orders_get, *args, **kwargs)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in getting orders.{self.error.description}')
return res
return res
async def history_deals_total(self, date_from: datetime | float, date_to: datetime | float) -> int:
return await asyncio.to_thread(self._history_deals_total, date_from, date_to)
async def history_deals_get(self, date_from: datetime | float = None, date_to: datetime | float = None,
group: str = '', ticket: int = None, position: int = None) -> tuple[TradeDeal] | None:
kwargs = {key: value for key, value in (('group', group), ('ticket', ticket), ('position', position)) if value}
args = tuple(arg for arg in (date_from, date_to) if arg)
res = await asyncio.to_thread(self._history_deals_get, *args, **kwargs)
if res is None:
err = await self.last_error()
self.error = Error(*err)
logger.warning(f'Error in getting deals.{self.error}')
return res
return res
+2 -2
View File
@@ -4,7 +4,7 @@ from logging import getLogger
from .config import Config
from .meta_trader import MetaTrader
from ..contrib.backtester import MetaTester
logger = getLogger(__name__)
@@ -22,7 +22,7 @@ class Base:
**kwargs: Set instance attributes with keyword arguments. Only if they are annotated on the class body.
"""
self.config = Config()
self.mt5 = MetaTrader()
self.mt5 = MetaTrader() if self.config.mode == 'live' else MetaTester()
self.exclude = {'mt5', "config", 'exclude', 'include', 'annotations', 'class_vars', 'dict'}
self.include = set()
self.set_attributes(**kwargs)
+15 -1
View File
@@ -8,6 +8,7 @@ from .task_queue import TaskQueue
logger = getLogger(__name__)
Bot = TypeVar("Bot")
TestData = TypeVar("TestData")
class Config:
@@ -44,16 +45,20 @@ class Config:
record_trades: bool
records_dir: Path
records_dir_name: str
compress_test_data: bool
test_data_dir: Path
test_data_dir_name: str
task_queue: TaskQueue
_test_data: TestData
bot: Bot
_instance: 'Config'
mode: Literal['backtest', 'live']
use_terminal_for_backtesting: bool
test_data_file: str
_defaults = {"timeout": 60000, "record_trades": True, "trade_record_mode": "csv", "mode": "live",
'filename': "aiomql.json", "records_dir_name": "trade_records", "test_data_dir_name": "test_data",
"use_terminal_for_backtesting": True, 'path': '', 'login': 0, 'password': '', 'server': ''}
"use_terminal_for_backtesting": True, 'path': '', 'login': 0, 'password': '', 'server': '',
"compress_test_data": False, 'test_data_file': ''}
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
@@ -61,12 +66,21 @@ class Config:
cls._instance.state = {}
cls._instance.task_queue = TaskQueue()
cls._instance.set_attributes(**cls._defaults)
cls._instance._test_data = None
cls._instance.load_config(**kwargs)
return cls._instance
def __init__(self, **kwargs):
self.set_attributes(**kwargs)
@property
def test_data(self):
return self._test_data
@test_data.setter
def test_data(self, value: TestData):
self._test_data = value
def set_attributes(self, **kwargs):
"""Set keyword arguments as object attributes
+1 -1
View File
@@ -24,7 +24,7 @@ class Error:
def __init__(self, code: int, description: str = ''):
self.code = code
self.description = description or self.descriptions.get(code, 'Unknown Error')
self.description = description or self.descriptions.get(code, 'unknown error')
def is_connection_error(self):
return self.code in self.conn_errors
+2 -24
View File
@@ -334,9 +334,9 @@ class SymbolInfo(Base):
path: str
def __init__(self, **kwargs):
if (name := kwargs.pop('name', None)) is None:
if (name := kwargs.pop('name', '')) == '':
raise AttributeError('Symbol Object Must be initialized with a name')
self.name = name # type: str
self.name = name
super().__init__(**kwargs)
def __repr__(self):
@@ -351,28 +351,6 @@ class SymbolInfo(Base):
def __hash__(self):
return hash(self.name)
class TickInfo(Base):
"""Price Tick of a Financial Instrument.
Attributes:
time (int): Time of the last prices update for the symbol
bid (float): Current Bid price
ask (float): Current Ask price
last (float): Price of the last deal (Last)
volume (float): Volume for the current Last price
time_msc (int): Time of the last prices update for the symbol in milliseconds
flags (TickFlag): Tick flags
volume_real (float): Volume for the current Last price
Index (int): Custom attribute representing the position of the tick in a sequence.
"""
time: float
bid: float
ask: float
last: float
volume: float
time_msc: float
flags: TickFlag
volume_real: float
class BookInfo(Base):
"""Book Information Class.
+1
View File
@@ -1,3 +1,4 @@
from .strategies import *
from .traders import *
from .symbols import *
from .candle_patterns import *
@@ -0,0 +1 @@
from .fractals import *
@@ -0,0 +1,13 @@
from ...candle import Candle, Candles
def find_bearish_fractal(candles: Candles) -> Candle | None:
for i in range(len(candles) - 3, 1, -1):
if candles[i].high > max(candles[i - 1].high, candles[i + 1].high, candles[i - 2].high, candles[i + 2].high):
return candles[i]
def find_bullish_fractal(candles: Candles) -> Candle | None:
for i in range(len(candles) - 3, 1, -1):
if candles[i].low < min(candles[i - 1].low, candles[i + 1].low, candles[i - 2].low, candles[i + 2].low):
return candles[i]
+1
View File
@@ -1,2 +1,3 @@
from .finger_trap import FingerTrap
from .tracker import Tracker
from .finger_trap_back_test import FingerTrapTest
+3 -1
View File
@@ -9,7 +9,7 @@ from ...candle import Candles
from ...strategy import Strategy
from ...core import TimeFrame, OrderType
from ...sessions import Sessions
from ...utils import find_bearish_fractal, find_bullish_fractal
from ..candle_patterns import find_bearish_fractal, find_bullish_fractal
logger = logging.getLogger(__name__)
@@ -36,6 +36,7 @@ class FingerTrap(Strategy):
async def check_trend(self):
try:
candles: Candles = await self.symbol.copy_rates_from_pos(timeframe=self.ttf, count=self.tcc)
if not ((current := candles[-1].time) >= self.tracker.trend_time):
self.tracker.update(new=False, order_type=None)
@@ -106,6 +107,7 @@ class FingerTrap(Strategy):
await self.trader.place_trade(order_type=self.tracker.order_type, parameters=self.parameters,
sl=self.tracker.sl)
await self.sleep(self.tracker.snooze)
except Exception as err:
logger.error(f"{err} For {self.symbol} in {self.__class__.__name__}.trade")
await self.sleep(self.ttf.time)
@@ -0,0 +1,30 @@
from .finger_trap import FingerTrap
from ...contrib.backtester.test_strategy import TestStrategy
class FingerTrapTest(TestStrategy, FingerTrap):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_up()
async def test(self):
print(f"Backtesting {self.symbol}")
while True:
await self.event_manager.acquire()
try:
await self.event_manager.wait()
await self.watch_market()
if not self.tracker.new:
continue
if self.tracker.order_type is not None:
await self.trader.place_trade(order_type=self.tracker.order_type, parameters=self.parameters,
sl=self.tracker.sl)
await self.sleep(self.tracker.snooze)
except Exception as err:
print(f"{err} For {self.symbol} in {self.__class__.__name__}.trade")
await self.sleep(self.ttf.time)
finally:
self.event_manager.release()
+38 -21
View File
@@ -1,11 +1,11 @@
"""Utility functions for aiomql."""
import decimal
import random
from functools import wraps, partial
import asyncio
from logging import getLogger
from .candle import Candles, Candle
logger = getLogger(__name__)
def dict_to_string(data: dict, multi=False) -> str:
@@ -22,25 +22,6 @@ def dict_to_string(data: dict, multi=False) -> str:
return f"{sep}".join(f"{key}: {value}" for key, value in data.items())
def round_off(value: float, step: float, round_down: bool = False) -> float:
"""Round off a number to the nearest step."""
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 find_bearish_fractal(candles: Candles) -> Candle | None:
for i in range(len(candles) - 3, 1, -1):
if candles[i].high > max(candles[i - 1].high, candles[i + 1].high, candles[i - 2].high, candles[i + 2].high):
return candles[i]
def find_bullish_fractal(candles: Candles) -> Candle | None:
for i in range(len(candles) - 3, 1, -1):
if candles[i].low < min(candles[i - 1].low, candles[i + 1].low, candles[i - 2].low, candles[i + 2].low):
return candles[i]
def backoff_decorator(func=None, *, max_retries: int = 3, retries: int = 0, delay: int = 1, error=None) -> callable:
if func is None:
return partial(backoff_decorator, max_retries=max_retries, retries=retries, delay=delay, error=error)
@@ -64,8 +45,44 @@ def backoff_decorator(func=None, *, max_retries: int = 3, retries: int = 0, dela
return wrapper
def error_handler(func=None, *, msg='', exe = Exception):
if func is None:
return partial(error_handler, msg=msg, exe=exe)
@wraps(func)
async def wrapper(*args, **kwargs):
try:
res = await func(*args, **kwargs)
return res
except exe as err:
logger.error(f'Error in {func.__name__}: {msg or err}')
return wrapper
def error_handler_sync(func=None, *, msg='', exe=Exception):
if func is None:
return partial(error_handler, msg=msg, exe=exe)
@wraps(func)
def wrapper(*args, **kwargs):
try:
res = func(*args, **kwargs)
return res
except exe as err:
logger.error(f'Error in {func.__name__}: {msg or err}')
return wrapper
def round_down(value: int, base: int) -> int:
return value if value % base == 0 else value - (value % base)
def round_up(value: int, base: int) -> int:
return value if value % base == 0 else value + base - (value % base)
def round_off(value: float, step: float, round_down: bool = False) -> float:
"""Round off a number to the nearest step."""
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))))