This commit is contained in:
Ichinga Samuel
2026-02-28 18:16:28 +01:00
parent 98e1ec34ac
commit d47a853f94
9 changed files with 1684 additions and 15 deletions
+1578
View File
File diff suppressed because it is too large Load Diff
View File
+16
View File
@@ -0,0 +1,16 @@
import asyncio
from aiomql import Bot, ForexSymbol
from .strategies.ribbon_scalper import RibbonScalper
async def rs_bot():
bot = Bot()
syms = ["ETHUSD", "BTCUSD", "ADAUSD", "SOLUSD", "LTCUSD", "XRPUSD"]
strategies = [RibbonScalper(symbol=ForexSymbol(name=sym)) for sym in syms]
bot.add_strategies(strategies=strategies)
await bot.start()
if __name__ == "__main__":
asyncio.run(rs_bot())
@@ -0,0 +1,59 @@
from aiomql import Strategy, ForexSymbol, TimeFrame, OrderType, Tracker, Trader
from ..traders.rs_trader import RSTrader
class RibbonScalper(Strategy):
"""
Entry Strategy (Long): When the 5 and 8 EMA cross above the 13 EMA and the EMAs start spreading out (indicating a trend).
Entry Strategy (Short): When the 5 and 8 EMA cross below the 13 EMA and spread out.
Exit Strategy (Take Profit): Set a small, fixed profit target (e.g., 5-10 pips) or exit when the EMA ribbon starts to flatten or twist.
Exit Strategy (Stop Loss): Tight stop-loss the previous candle high/low to limit risk.
"""
fast_ema: int # fast moving average
medium_ema: int # medium moving average
slow_ema: int # slow moving average
time_frame: TimeFrame # time frame
candles_count: int # lookback period
tracker: Tracker # a tracker class
pips_target: int # number of pips to target
interval: int # interval between successive runs in seconds
parameters = {"fast_ema": 5, "medium_ema": 8, "slow_ema": 13, "time_frame": TimeFrame.M1,
"candles_count": 120, "pips_target": 10, "interval": 30}
def __init__(self, *, symbol: ForexSymbol, params: dict = None, trader: Trader = None, name="RibbonScalper"):
super().__init__(symbol=symbol, params=params, name=name)
self.tracker = Tracker()
self.trader = trader or RSTrader(symbol=symbol)
self.interval = self.time_frame.seconds
async def find_entry(self):
rates = await self.symbol.copy_rates_from_pos(count=self.candles_count, timeframe=self.time_frame)
rates.ta.ema(length=self.fast_ema, append=True)
rates.ta.ema(length=self.medium_ema, append=True)
rates.ta.ema(length=self.slow_ema, append=True)
rates.rename(**{f"EMA_{self.fast_ema}": "fast", f"EMA_{self.medium_ema}": "medium", f"EMA_{self.slow_ema}": "slow"})
find_long_fast = rates.ta_lib.cross(rates.fast, rates.slow)
find_long_medium = rates.ta_lib.cross(rates.medium, rates.slow)
if find_long_fast.iloc[-1] and find_long_medium.iloc[-1]:
self.tracker.update(order_type=OrderType.BUY, trend="bullish", sl=rates[-2].low, snooze=300)
return
find_short_fast = rates.ta_lib.cross(rates.fast, rates.slow, above=False)
find_short_medium = rates.ta_lib.cross(rates.medium, rates.slow, above=False)
if find_short_fast.iloc[-1] and find_short_medium.iloc[-1]:
self.tracker.update(order_type=OrderType.SELL, trend="bearish", sl=rates[-2].high, snooze=300)
return
self.tracker.update(order_type=None, trend="ranging", snooze=self.interval)
async def trade(self):
await self.find_entry()
if self.tracker.order_type is not None:
await self.trader.place_trade(parameters=self.parameters, order_type=self.tracker.order_type,
sl=self.tracker.sl, pips_target=self.pips_target)
await self.sleep(secs=self.tracker.snooze)
return
else:
await self.sleep(secs=self.tracker.snooze)
@@ -0,0 +1,30 @@
from logging import getLogger
from aiomql import Trader, OrderType
logger = getLogger(__name__)
class RSTrader(Trader):
async def create_order(self, sl: float, pips_target: int = 5):
try:
amount = await self.ram.get_amount()
tick = await self.symbol.info_tick()
price = tick.ask if self.order.type.is_long else tick.bid
volume = await self.symbol.compute_volume_sl(sl=sl, amount=amount, price=price)
tp = price + (self.symbol.point * 10 * pips_target)
self.order.set_attributes(sl=sl, tp=tp, volume=volume, price=price)
except Exception as exe:
logger.error("%s: unable to create order", exe)
async def place_trade(self, sl: float, order_type: OrderType, pips_target: int = 10, parameters: dict = None):
try:
self.order.type = order_type
await self.create_order(sl=sl, pips_target=pips_target)
parameters = parameters or {}
self.order.set_attributes(comment=comment) if (comment := parameters.get("name")) else ...
res = await self.send_order()
if res and res.retcode == 10009:
await self.record_trade(result=res, parameters=parameters)
self.reset_order()
except Exception as exe:
logger.error("%s: unable to place trade", exe)
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "aiomql"
version = "4.1.1"
version = "4.1.2"
readme = "README.md"
requires-python = ">=3.13"
classifiers = [
-14
View File
@@ -17,7 +17,6 @@ import inspect
import os
import time
from concurrent.futures import ThreadPoolExecutor
from signal import signal, SIGINT
from typing import Coroutine, Callable
from logging import getLogger
@@ -61,7 +60,6 @@ class Executor:
self.functions: dict[Callable:dict] = {}
self.config = Config()
self.timeout = None # Timeout for the executor. For testing purposes only
signal(SIGINT, self.sigint_handle)
def add_function(self, *, function: Callable, kwargs: dict = None):
"""Registers a synchronous function to run in the executor.
@@ -144,16 +142,6 @@ class Executor:
"""
function(**kwargs)
def sigint_handle(self, signum, frame):
"""Handles SIGINT (Ctrl+C) by signaling a shutdown.
Args:
signum: The signal number received.
frame: The current stack frame.
"""
print("shutting down")
self.config.shutdown = True
def exit(self):
"""Monitors for shutdown signals and gracefully shuts down the executor.
@@ -193,11 +181,9 @@ class Executor:
Notes:
No matter the number specified, the executor will always use a minimum of 5 workers.
"""
signal(SIGINT, self.sigint_handle)
workers_ = len(self.strategy_runners) + len(self.functions) + len(self.coroutine_threads) + 3
workers = max(workers, workers_)
with ThreadPoolExecutor(max_workers=workers) as executor:
signal(SIGINT, self.sigint_handle)
self.executor = executor
[self.executor.submit(self.run_strategy, strategy) for strategy in self.strategy_runners]
[self.executor.submit(function, **kwargs) for function, kwargs in self.functions.items()]