mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-07-27 20:27:43 +00:00
testdata
This commit is contained in:
@@ -75,3 +75,4 @@ target/
|
||||
config.json
|
||||
aiomql.json
|
||||
config/
|
||||
test_data/
|
||||
|
||||
-730
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
@@ -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 *
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
+84
-38
@@ -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)
|
||||
+281
-177
@@ -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()
|
||||
@@ -1,3 +0,0 @@
|
||||
from .meta_tester import MetaTester
|
||||
from .test_data import TestData
|
||||
from .get_data import GetData
|
||||
@@ -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 +0,0 @@
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,2 +1,3 @@
|
||||
from .finger_trap import FingerTrap
|
||||
from .tracker import Tracker
|
||||
from .finger_trap_back_test import FingerTrapTest
|
||||
|
||||
@@ -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
@@ -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))))
|
||||
|
||||
Reference in New Issue
Block a user