mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-08-22 00:08:08 +00:00
testdata
This commit is contained in:
+161
-74
@@ -2,16 +2,11 @@
|
|||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 1,
|
"execution_count": 2,
|
||||||
"id": "f4500c8d-0e58-4d3f-8dd3-06a4896f397f",
|
"id": "f4500c8d-0e58-4d3f-8dd3-06a4896f397f",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"# import shelve\n",
|
|
||||||
"# import pickle\n",
|
|
||||||
"# import zlib\n",
|
|
||||||
"# import lzma\n",
|
|
||||||
"# import pytz\n",
|
|
||||||
"from datetime import datetime, timedelta\n",
|
"from datetime import datetime, timedelta\n",
|
||||||
"from aiomql import MetaTrader, TimeFrame, AccountInfo, TimeFrame, CopyTicks, Account, Symbol\n",
|
"from aiomql import MetaTrader, TimeFrame, AccountInfo, TimeFrame, CopyTicks, Account, Symbol\n",
|
||||||
"from MetaTrader5 import SymbolInfo\n",
|
"from MetaTrader5 import SymbolInfo\n",
|
||||||
@@ -22,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": 3,
|
||||||
"id": "f2ef126c-6edc-4651-8a81-06bb97eed6f9",
|
"id": "f2ef126c-6edc-4651-8a81-06bb97eed6f9",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
@@ -41,21 +36,108 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 3,
|
"execution_count": 21,
|
||||||
"id": "2c598a85-1e90-49b0-abf1-87329597feae",
|
"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": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"True"
|
"(410, 7710.71, 0.01, 553.9999999999964)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 3,
|
"execution_count": 22,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"output_type": "execute_result"
|
"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": [
|
"source": [
|
||||||
"sym = Symbol(name='Volatility 25 Index')\n",
|
"sym = Symbol(name='Volatility 25 Index')\n",
|
||||||
"await sym.init()"
|
"await sym.init()"
|
||||||
@@ -63,42 +145,20 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 13,
|
"execution_count": null,
|
||||||
"id": "0a7d319c-5760-4058-994f-27e8d10df108",
|
"id": "0a7d319c-5760-4058-994f-27e8d10df108",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [],
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"text/plain": [
|
|
||||||
"0.5"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 13,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
"source": [
|
||||||
"await sym.mt5.order_calc_margin(0, 'Volatility 25 Index', 1, sym.tick.ask)"
|
"await sym.mt5.order_calc_margin(0, 'Volatility 25 Index', 1, sym.tick.ask)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 17,
|
"execution_count": null,
|
||||||
"id": "4ec498ea-5e4e-4ff6-85da-051acc2eaf28",
|
"id": "4ec498ea-5e4e-4ff6-85da-051acc2eaf28",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [],
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"text/plain": [
|
|
||||||
"4"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 17,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
"source": [
|
||||||
"sy = await sym.mt5.symbol_info('Volatility 25 Index')\n",
|
"sy = await sym.mt5.symbol_info('Volatility 25 Index')\n",
|
||||||
"await sym.mt5.symbol_select('Volatility 25 Index', enable=True)\n",
|
"await sym.mt5.symbol_select('Volatility 25 Index', enable=True)\n",
|
||||||
@@ -107,21 +167,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 12,
|
"execution_count": null,
|
||||||
"id": "9307e3df-a5f3-42cf-9d3d-e65e5254c0e5",
|
"id": "9307e3df-a5f3-42cf-9d3d-e65e5254c0e5",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [],
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"text/plain": [
|
|
||||||
"0.50097375"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 12,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
"source": [
|
||||||
"# margin\n",
|
"# margin\n",
|
||||||
"# worked for forex.\n",
|
"# worked for forex.\n",
|
||||||
@@ -432,26 +481,75 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 70,
|
||||||
"id": "e47bd638-42fa-40d9-b573-474a39884dbc",
|
"id": "e47bd638-42fa-40d9-b573-474a39884dbc",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"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": [
|
"source": [
|
||||||
"n = datetime.now()\n",
|
"await symb.copy_rates_from_pos(timeframe=TimeFrame.M1)\n"
|
||||||
"start = n.replace(hour=0, day=1, year=2020, month=1)\n",
|
|
||||||
"end = n.replace(hour=15)\n",
|
|
||||||
"res = await mt.copy_rates_range('Volatility 25 Index', TimeFrame.M1, start, end)"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": 72,
|
||||||
"id": "a4bc7b28-6dac-4863-b257-64d2c5c62575",
|
"id": "a4bc7b28-6dac-4863-b257-64d2c5c62575",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"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": [
|
"source": [
|
||||||
"res = pd.DataFrame(res)\n",
|
"tz = pytz.timezone('Etc/UTC')\n",
|
||||||
"len(res.index)"
|
"now = datetime.now(tz=tz)\n",
|
||||||
|
"# now.replace(tz=tz-tz)\n",
|
||||||
|
"await symb.copy_ticks_from(date_from=now)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -591,21 +689,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 2,
|
"execution_count": null,
|
||||||
"id": "fe60fc41-8844-4e00-b254-bb95d28528f2",
|
"id": "fe60fc41-8844-4e00-b254-bb95d28528f2",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [],
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"text/plain": [
|
|
||||||
"1.2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"execution_count": 2,
|
|
||||||
"metadata": {},
|
|
||||||
"output_type": "execute_result"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
"source": [
|
||||||
"6 / (0 or 5)"
|
"6 / (0 or 5)"
|
||||||
]
|
]
|
||||||
@@ -635,7 +722,7 @@
|
|||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.11.4"
|
"version": "3.11.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
|||||||
@@ -29,10 +29,8 @@ class Account(AccountInfo):
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
acc = self.config.account_info()
|
self.exclude = self.exclude | {'_instance', 'symbols'}
|
||||||
acc_details = {k: v for k, v in self.get_dict(include={'login', 'server', 'password'}).items() if v}
|
acc = {k: (self.dict[k] or v) for k, v in self.config.account_info().items()}
|
||||||
acc |= acc_details
|
|
||||||
self.config.set_attributes(**acc)
|
|
||||||
self.set_attributes(**acc)
|
self.set_attributes(**acc)
|
||||||
|
|
||||||
async def refresh(self):
|
async def refresh(self):
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
from collections import namedtuple
|
import socket
|
||||||
class ITR:
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.span = iter(range(0, 10))
|
|
||||||
self.start = 0
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
self.start = next(self.span)
|
|
||||||
return self.start
|
|
||||||
|
|
||||||
|
|
||||||
Gender = namedtuple('Gender', ['man', 'woman'])
|
class socketserver:
|
||||||
gen = Gender(man='Manny', woman='Babe')
|
def __init__(self, address = '192.168.1.15', port = 9090):
|
||||||
gend = gen._asdict()
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
genz = Gender(gend)
|
self.address = address
|
||||||
print(gen, genz)
|
self.port = port
|
||||||
# b = ITR()
|
self.sock.bind((self.address, self.port))
|
||||||
# print(next(b))
|
self.cummdata = ''
|
||||||
# print(next(b))
|
|
||||||
# print(next(b))
|
|
||||||
|
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,4 +1,4 @@
|
|||||||
from typing import TypedDict
|
from dataclasses import dataclass
|
||||||
import pickle
|
import pickle
|
||||||
import lzma
|
import lzma
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -16,24 +16,36 @@ from ...core.constants import TimeFrame, CopyTicks
|
|||||||
from ...utils import backoff_decorator
|
from ...utils import backoff_decorator
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
from MetaTrader5 import TradePosition, TradeOrder, TradeDeal
|
||||||
|
|
||||||
|
tof = list(TradeOrder._fields)
|
||||||
|
tof.append('symbol')
|
||||||
|
tpf = list(TradePosition._fields)
|
||||||
|
tpf.append('symbol')
|
||||||
|
tdf = list(TradeDeal._fields)
|
||||||
|
tdf.append('symbol')
|
||||||
|
|
||||||
|
|
||||||
class Data(TypedDict):
|
@dataclass
|
||||||
|
class Data:
|
||||||
account: dict
|
account: dict
|
||||||
symbols: dict[str, dict]
|
symbols: dict[str, dict]
|
||||||
prices: dict[str, DataFrame]
|
prices: dict[str, DataFrame]
|
||||||
ticks: dict[str, DataFrame]
|
ticks: dict[str, DataFrame]
|
||||||
rates: dict[str, dict[str, DataFrame]]
|
rates: dict[str, dict[str, DataFrame]]
|
||||||
interval: range
|
span: range
|
||||||
|
range: range
|
||||||
|
history_orders: DataFrame = DataFrame([], columns=tof)
|
||||||
|
history_deals: DataFrame = DataFrame([], columns=tdf)
|
||||||
|
positions: DataFrame = DataFrame([], columns=tpf)
|
||||||
|
|
||||||
|
|
||||||
class GetData:
|
class GetData:
|
||||||
config: Config = Config()
|
|
||||||
|
|
||||||
def __init__(self, *, start: datetime, end: datetime, timeframes: set[TimeFrame], symbols: set[str],
|
def __init__(self, *, start: datetime, end: datetime, timeframes: set[TimeFrame], symbols: set[str],
|
||||||
name: str = '', tz: str = 'Etc/UTC'):
|
name: str = '', tz: str = 'Etc/UTC'):
|
||||||
""""""
|
""""""
|
||||||
super().__init__()
|
self.config = Config()
|
||||||
self.tz = pytz.timezone(tz)
|
self.tz = pytz.timezone(tz)
|
||||||
self.start = start.replace(tzinfo=self.tz)
|
self.start = start.replace(tzinfo=self.tz)
|
||||||
self.end = end.replace(tzinfo=self.tz)
|
self.end = end.replace(tzinfo=self.tz)
|
||||||
@@ -41,24 +53,18 @@ class GetData:
|
|||||||
self.timeframes = timeframes
|
self.timeframes = timeframes
|
||||||
self.name = name or f"{start:%d-%m-%y}_{end:%d-%m-%y}"
|
self.name = name or f"{start:%d-%m-%y}_{end:%d-%m-%y}"
|
||||||
diff = int((self.end - self.start).total_seconds())
|
diff = int((self.end - self.start).total_seconds())
|
||||||
self.interval = range(start := int(self.start.timestamp()), diff + start)
|
self.range = range(diff)
|
||||||
|
self.span = range(start := int(self.start.timestamp()), diff + start)
|
||||||
self.mt5 = MetaTrader()
|
self.mt5 = MetaTrader()
|
||||||
|
|
||||||
async def get_data(self) -> Data:
|
async def get_data(self) -> Data:
|
||||||
""""""
|
""""""
|
||||||
data = {}
|
|
||||||
rates, ticks, prices, symbols, account = await asyncio.gather(self.get_symbols_rates(), self.get_symbols_ticks(),
|
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_symbols_prices(), self.get_symbols_info(),
|
||||||
self.get_account_info())
|
self.get_account_info())
|
||||||
|
return Data(account=account, symbols=symbols, prices=prices, ticks=ticks, rates=rates,
|
||||||
data['rates'] = rates
|
span=self.span, range=self.range)
|
||||||
data['ticks'] = ticks
|
|
||||||
data['prices'] = prices
|
|
||||||
data['symbols'] = symbols
|
|
||||||
data['account'] = account
|
|
||||||
data['range'] = self.interval
|
|
||||||
|
|
||||||
return Data(**data)
|
|
||||||
|
|
||||||
async def pickle_data(self) -> None:
|
async def pickle_data(self) -> None:
|
||||||
""""""
|
""""""
|
||||||
@@ -145,7 +151,7 @@ class GetData:
|
|||||||
res = pd.DataFrame(res)
|
res = pd.DataFrame(res)
|
||||||
res.drop_duplicates(subset=['time'], keep='last', inplace=True)
|
res.drop_duplicates(subset=['time'], keep='last', inplace=True)
|
||||||
res.set_index('time', inplace=True, drop=False)
|
res.set_index('time', inplace=True, drop=False)
|
||||||
res = res.reindex(self.interval, method='nearest')
|
res = res.reindex(self.span, method='nearest')
|
||||||
return symbol, res
|
return symbol, res
|
||||||
|
|
||||||
@backoff_decorator(max_retries=5)
|
@backoff_decorator(max_retries=5)
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
from datetime import datetime, tzinfo
|
from collections import namedtuple
|
||||||
|
from datetime import datetime
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
from itertools import zip_longest
|
||||||
|
import random
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from MetaTrader5 import (Tick, SymbolInfo, AccountInfo, TradeOrder, TradePosition, TradeDeal,
|
from MetaTrader5 import (Tick, SymbolInfo, AccountInfo, TradeOrder, TradePosition, TradeDeal,
|
||||||
ORDER_TYPE_BUY, ORDER_TYPE_SELL, TradeRequest)
|
ORDER_TYPE_BUY, ORDER_TYPE_SELL, TradeRequest, OrderCheckResult, OrderSendResult,
|
||||||
import MetaTrader5 as mt5
|
ACCOUNT_STOPOUT_MODE_PERCENT)
|
||||||
from ..meta_trader import MetaTrader
|
from ..meta_trader import MetaTrader
|
||||||
from ..constants import TimeFrame, CopyTicks, OrderType
|
from ..constants import TimeFrame, CopyTicks, OrderType, TradeAction
|
||||||
from .get_data import Data, GetData
|
from .get_data import Data
|
||||||
from ...utils import round_down, round_up
|
from ...utils import round_down, round_up
|
||||||
|
|
||||||
tz = pytz.timezone('Etc/UTC')
|
tz = pytz.timezone('Etc/UTC')
|
||||||
|
Cursor = namedtuple('Cursor', ['index', 'time'])
|
||||||
|
|
||||||
|
|
||||||
class TestData:
|
class TestData:
|
||||||
@@ -22,27 +25,33 @@ class TestData:
|
|||||||
|
|
||||||
def __init__(self, data: Data):
|
def __init__(self, data: Data):
|
||||||
self._data = data
|
self._data = data
|
||||||
self.account = AccountInfo(**data['account'])
|
self.account = AccountInfo(**data.account)
|
||||||
self.symbols = {symbol: SymbolInfo(**info) for symbol, info in data['symbols'].items()}
|
self.symbols = {symbol: SymbolInfo(**info) for symbol, info in data.symbols.items()}
|
||||||
self.prices = data['prices']
|
self.prices = data.prices
|
||||||
self.ticks = data['ticks']
|
self.ticks = data.ticks
|
||||||
self.rates = data['rates']
|
self.rates = data.rates
|
||||||
self.interval = data['interval']
|
self.span = data.span
|
||||||
self.cursor = 0
|
self.range = data.range
|
||||||
self.iter = iter(self.interval)
|
self.cursor = Cursor(index=self.range[0], time=self.span[0])
|
||||||
|
self.iter = zip_longest(self.range, self.span)
|
||||||
self.orders: dict[str, dict[int, TradeOrder]] = {}
|
self.orders: dict[str, dict[int, TradeOrder]] = {}
|
||||||
self.open_orders: dict[int, TradeOrder] = {}
|
self.open_orders: dict[int, TradeOrder] = {}
|
||||||
self.positions: dict[str, dict[int, TradePosition]] = {}
|
self.positions: dict[str, dict[int, TradePosition]] = {}
|
||||||
self.open_positions: dict[int, TradePosition] = {}
|
self.open_positions: dict[int, TradePosition] = {}
|
||||||
self.mt = MetaTrader()
|
self.history_orders = data.history_orders
|
||||||
|
self.history_deals = data.history_deals
|
||||||
|
self.margins: dict[int, float] = {}
|
||||||
|
self.mt5 = MetaTrader()
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
self.cursor = next(self.iter)
|
index, time = next(self.iter)
|
||||||
|
self.cursor = Cursor(index=index, time=time)
|
||||||
return self.cursor
|
return self.cursor
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.iter = iter(self.interval)
|
self.iter = zip_longest(self.range, self.span)
|
||||||
return self.iter
|
self.cursor = Cursor(index=self.range[0], time=self.span[0])
|
||||||
|
return self.cursor
|
||||||
|
|
||||||
def get_symbols_total(self) -> int:
|
def get_symbols_total(self) -> int:
|
||||||
return len(self.symbols)
|
return len(self.symbols)
|
||||||
@@ -54,7 +63,7 @@ class TestData:
|
|||||||
return AccountInfo(**self.account._asdict())
|
return AccountInfo(**self.account._asdict())
|
||||||
|
|
||||||
def get_symbol_info_tick(self, symbol: str) -> Tick:
|
def get_symbol_info_tick(self, symbol: str) -> Tick:
|
||||||
tick = self.prices[symbol].iloc[self.cursor]
|
tick = self.prices[symbol].iloc[self.cursor.index]
|
||||||
return Tick(**tick)
|
return Tick(**tick)
|
||||||
|
|
||||||
def get_symbol_info(self, symbol: str) -> SymbolInfo:
|
def get_symbol_info(self, symbol: str) -> SymbolInfo:
|
||||||
@@ -108,25 +117,136 @@ class TestData:
|
|||||||
|
|
||||||
async def order_calc_margin(self, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float,
|
async def order_calc_margin(self, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float,
|
||||||
price: float, use_terminal=False):
|
price: float, use_terminal=False):
|
||||||
if use_terminal or self.mt.config.use_terminal:
|
if use_terminal and self.mt5.config.use_terminal_for_backtesting:
|
||||||
return await self.mt.order_calc_margin(OrderType(action), symbol, volume, price)
|
return await self.mt5.order_calc_margin(OrderType(action), symbol, volume, price)
|
||||||
sym = self.symbols[symbol]
|
sym = self.symbols[symbol]
|
||||||
margin = (volume * sym.trade_contract_size * price) / (self.account.leverage / (sym.margin_initial or 1))
|
margin = (volume * sym.trade_contract_size * price) / (self.account.leverage / (sym.margin_initial or 1))
|
||||||
return margin
|
return margin
|
||||||
|
|
||||||
async def order_calc_profit(self, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float,
|
async def order_calc_profit(self, action: Literal[OrderType.BUY, OrderType.SELL], symbol: str, volume: float,
|
||||||
price_open: float, price_close: float, use_terminal=False):
|
price_open: float, price_close: float, use_terminal=True):
|
||||||
if use_terminal or self.mt.config.use_terminal:
|
if use_terminal and self.mt5.config.use_terminal_for_backtesting:
|
||||||
return await self.mt.order_calc_profit(action, symbol, volume, price_open, price_close)
|
return await self.mt5.order_calc_profit(action, symbol, volume, price_open, price_close)
|
||||||
sym = self.symbols[symbol]
|
sym = self.symbols[symbol]
|
||||||
profit = volume * sym.trade_contract_size * (price_close - price_open)
|
profit = volume * sym.trade_contract_size * (price_close - price_open)
|
||||||
return profit
|
return profit
|
||||||
|
|
||||||
def order_send(self, request: dict) -> dict:
|
def check_order(self, order: TradeOrder) -> bool:
|
||||||
...
|
...
|
||||||
|
|
||||||
def order_check(self, request: dict) -> dict:
|
def check_position(self, position: TradePosition) -> bool:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def close_position(self, position: TradePosition):
|
||||||
|
profit = position.profit
|
||||||
|
self.open_orders.pop(position.ticket)
|
||||||
|
self.open_positions.pop(position.ticket)
|
||||||
|
margin = self.margins.pop(position.ticket)
|
||||||
|
self.update_account(profit, margin=margin)
|
||||||
|
|
||||||
|
def modify_stops(self, ticket: int, sl: int = None, tp: int = None):
|
||||||
|
...
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
level = margin_level if acc.margin_mode == ACCOUNT_STOPOUT_MODE_PERCENT else free_margin
|
||||||
|
if level < acc.margin_so_call or free_margin <= 0:
|
||||||
|
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)
|
||||||
|
|
||||||
def get_orders_total(self) -> int:
|
def get_orders_total(self) -> int:
|
||||||
return len(self.open_orders)
|
return len(self.open_orders)
|
||||||
|
|||||||
+67
-76
@@ -23,54 +23,49 @@ class Config:
|
|||||||
server (str): Broker server
|
server (str): Broker server
|
||||||
path (str): Path to terminal file
|
path (str): Path to terminal file
|
||||||
timeout (int): Timeout for terminal connection
|
timeout (int): Timeout for terminal connection
|
||||||
_initialize (bool): First time initialization flag
|
|
||||||
state (dict): A global state dictionary for storing data across the framework
|
state (dict): A global state dictionary for storing data across the framework
|
||||||
root_dir (str): The root directory of the project
|
root (str): Root directory of the project
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
By default, the config class looks for a file named aiomql.json.
|
By default, the config class looks for a file named aiomql.json.
|
||||||
You can change this by passing the filename and/or the config_dir keyword argument(s) to the constructor
|
You can change this by passing the filename and/or the config_dir keyword argument(s) to the constructor
|
||||||
or the load_config method.
|
or the load_config method.
|
||||||
By passing reload=True to the load_config method, you can reload and search again for the config file.
|
By passing reload=True to the load_config method, you can reload and search again for the config file.
|
||||||
"""
|
"""
|
||||||
login: int = 0
|
login: int
|
||||||
trade_record_mode: Literal['csv', 'json'] = 'csv'
|
trade_record_mode: Literal['csv', 'json']
|
||||||
password: str = ""
|
password: str
|
||||||
server: str = ""
|
server: str
|
||||||
path: str | Path = ""
|
path: str | Path
|
||||||
timeout: int = 60000
|
timeout: int
|
||||||
record_trades: bool = True
|
filename: str
|
||||||
filename: str = "aiomql.json"
|
state: dict
|
||||||
_initialize = True
|
|
||||||
state: dict = {}
|
|
||||||
root: Path
|
root: Path
|
||||||
root_dir: Path
|
record_trades: bool
|
||||||
records_dir: Path
|
records_dir: Path
|
||||||
config_dir: str = ''
|
records_dir_name: str
|
||||||
task_queue: TaskQueue = TaskQueue()
|
test_data_dir: Path
|
||||||
bot: Bot = None
|
test_data_dir_name: str
|
||||||
|
task_queue: TaskQueue
|
||||||
|
bot: Bot
|
||||||
_instance: 'Config'
|
_instance: 'Config'
|
||||||
mode: Literal['backtest', 'live'] = 'live'
|
mode: Literal['backtest', 'live']
|
||||||
test_data_dir: str = 'test_data'
|
use_terminal_for_backtesting: bool
|
||||||
use_terminal: bool = False
|
_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': ''}
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if not hasattr(cls, "_instance"):
|
if not hasattr(cls, "_instance"):
|
||||||
cls._instance = super().__new__(cls)
|
cls._instance = super().__new__(cls)
|
||||||
|
cls._instance.state = {}
|
||||||
|
cls._instance.task_queue = TaskQueue()
|
||||||
|
cls._instance.set_attributes(**cls._defaults)
|
||||||
|
cls._instance.load_config(**kwargs)
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
reload = kwargs.pop('reload', False)
|
self.set_attributes(**kwargs)
|
||||||
self.load_config(reload=reload, **kwargs)
|
|
||||||
|
|
||||||
def set_root(self, *, root: str | Path):
|
|
||||||
root = Path(root) if str else root
|
|
||||||
self.root = root.absolute().resolve()
|
|
||||||
self.root_dir = self.root
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
|
||||||
if key == 'path':
|
|
||||||
value = str(self.root_dir / Path(value).absolute().resolve())
|
|
||||||
super().__setattr__(key, value)
|
|
||||||
|
|
||||||
def set_attributes(self, **kwargs):
|
def set_attributes(self, **kwargs):
|
||||||
"""Set keyword arguments as object attributes
|
"""Set keyword arguments as object attributes
|
||||||
@@ -95,10 +90,9 @@ class Config:
|
|||||||
parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
|
parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
|
||||||
last_dir, current_dir = current_dir, parent_dir
|
last_dir, current_dir = current_dir, parent_dir
|
||||||
|
|
||||||
def find_config(self):
|
def find_config_file(self):
|
||||||
try:
|
try:
|
||||||
path = self.root_dir / self.config_dir
|
for dirname in self.walk_to_root(self.root):
|
||||||
for dirname in self.walk_to_root(path):
|
|
||||||
check_path = os.path.join(dirname, self.filename)
|
check_path = os.path.join(dirname, self.filename)
|
||||||
if os.path.isfile(check_path):
|
if os.path.isfile(check_path):
|
||||||
return check_path
|
return check_path
|
||||||
@@ -106,54 +100,51 @@ class Config:
|
|||||||
except Exception as _:
|
except Exception as _:
|
||||||
return
|
return
|
||||||
|
|
||||||
def create_records_dir(self, *, records_dir: str | Path = 'records'):
|
def load_config(self, *, file: str | Path = None, filename: str = None, root: str | Path = None, **kwargs):
|
||||||
"""Create records directory if it does not exist. By default, it is relative to the root directory of the
|
|
||||||
project unless an absolute path is provided.
|
|
||||||
|
|
||||||
Keyword Args:
|
|
||||||
records_dir (str|Path): The directory to save trade records. Default is 'trade_records'
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if isinstance(records_dir, str):
|
|
||||||
records_dir = self.root_dir / records_dir
|
|
||||||
elif isinstance(records_dir, Path):
|
|
||||||
records_dir = records_dir.absolute().resolve()
|
|
||||||
records_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
self.records_dir = records_dir
|
|
||||||
except Exception as err:
|
|
||||||
logger.warning(f"{err}: Unable to create records directory")
|
|
||||||
|
|
||||||
def load_config(self, *, file: str = None, reload: bool = True, filename: str = None,
|
|
||||||
config_dir: str = '', **kwargs):
|
|
||||||
"""Load configuration settings from a file.
|
"""Load configuration settings from a file.
|
||||||
Keyword Args:
|
|
||||||
file (str): The path to the file to load. If not provided, the file is searched for
|
|
||||||
reload (bool): Whether to reload the config object. Default is True
|
|
||||||
filename (str): The name of the file to load. If not provided, the default filename is used
|
|
||||||
config_dir (str): The name of the directory to search for the file. Default is the root directory
|
|
||||||
root_dir (str): The root directory of the project
|
|
||||||
kwargs: Additional keyword arguments
|
|
||||||
"""
|
|
||||||
if not (self._initialize or reload):
|
|
||||||
return
|
|
||||||
data = {}
|
|
||||||
self.filename = filename or self.filename
|
|
||||||
self.config_dir = config_dir or self.config_dir
|
|
||||||
root_dir = kwargs.pop('root_dir', None)
|
|
||||||
records_dir = kwargs.pop('records_dir', 'records')
|
|
||||||
if self._initialize or (root_dir is not None):
|
|
||||||
self.set_root(root=(root_dir or '.'))
|
|
||||||
self.create_records_dir(records_dir=records_dir)
|
|
||||||
|
|
||||||
if (file := (file or self.find_config())) is None:
|
Keyword Args:
|
||||||
|
file (str | Path): The absolute path to the config file.
|
||||||
|
filename (str): The name of the file to load if file path is not specified. If not provided aiomql.json is used
|
||||||
|
root (str): The root directory of the project.
|
||||||
|
kwargs: Additional keyword arguments to set as object attributes.
|
||||||
|
"""
|
||||||
|
if root is not None:
|
||||||
|
root = Path(root).resolve()
|
||||||
|
root.mkdir(parents=True, exist_ok=True) if not root.exists() else ...
|
||||||
|
self.root = root
|
||||||
|
else:
|
||||||
|
self.root = self.root if hasattr(self, 'root') else Path.cwd()
|
||||||
|
|
||||||
|
if file is not None:
|
||||||
|
file = Path(file).resolve()
|
||||||
|
if not file.exists():
|
||||||
|
self.filename = filename or self.filename
|
||||||
|
file = self.find_config_file()
|
||||||
|
else:
|
||||||
|
self.filename = file.name
|
||||||
|
else:
|
||||||
|
self.filename = filename or self.filename
|
||||||
|
file = self.find_config_file()
|
||||||
|
|
||||||
|
if file is None:
|
||||||
logger.warning("No Config File Found")
|
logger.warning("No Config File Found")
|
||||||
|
file_config = {}
|
||||||
else:
|
else:
|
||||||
fh = open(file, mode="r")
|
fh = open(file, mode="r")
|
||||||
data = json.load(fh)
|
file_config = json.load(fh)
|
||||||
fh.close()
|
fh.close()
|
||||||
data |= kwargs
|
|
||||||
|
data = file_config | kwargs
|
||||||
self.set_attributes(**data)
|
self.set_attributes(**data)
|
||||||
self._initialize = False
|
|
||||||
|
if self.record_trades and not hasattr(self, "records_dir"):
|
||||||
|
self.records_dir = self.root / self.records_dir_name
|
||||||
|
self.records_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if self.mode == "backtest" and not hasattr(self, "test_data_dir"):
|
||||||
|
self.test_data_dir = self.root / self.test_data_dir_name
|
||||||
|
self.test_data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
def account_info(self) -> dict[str, int | str]:
|
def account_info(self) -> dict[str, int | str]:
|
||||||
"""Returns Account login details as found in the config object if available
|
"""Returns Account login details as found in the config object if available
|
||||||
@@ -161,4 +152,4 @@ class Config:
|
|||||||
Returns:
|
Returns:
|
||||||
dict: A dictionary of login details
|
dict: A dictionary of login details
|
||||||
"""
|
"""
|
||||||
return {"login": self.login, "password": self.password, "server": self.server}
|
return {'login': self.login, 'password': self.password, 'server': self.server}
|
||||||
|
|||||||
Reference in New Issue
Block a user