mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-07-27 20:27:43 +00:00
v4.1.2
This commit is contained in:
+1578
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "aiomql"
|
||||
version = "4.1.1"
|
||||
version = "4.1.2"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
classifiers = [
|
||||
|
||||
@@ -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()]
|
||||
|
||||
Reference in New Issue
Block a user