Skip to content

Commit

Permalink
Merge pull request #380 from mraniki/dev
Browse files Browse the repository at this point in the history
💄♻️  Code refactor to support multiple parser
  • Loading branch information
mraniki committed Jul 7, 2024
2 parents 78f3155 + fa6a93a commit 8a4b9b0
Show file tree
Hide file tree
Showing 11 changed files with 668 additions and 236 deletions.
7 changes: 4 additions & 3 deletions findmyorder/default_settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

[default]
# Dynaconf settings verification
VALUE = "Production - Default"
# VALUE = "Production - Default"

# Module Enable/Disable
findmyorder_enabled = true

[default.findmyorder.template]
parser_library = "standard"
enabled = true
# Keyword to be use to identify an order
action_identifier = "BUY SELL LONG SHORT"
# Keyword identifier for stoploss
Expand All @@ -33,14 +36,12 @@ order_type_identifier = "spot future margin"
leverage_type_identifier = "cross isolated"
# Keyword identifier for comments
comment_identifier = "comment="

# Stoploss default value is none is provided
stop_loss = 1000
# Take-Profit default value is none is provided
take_profit = 1000
# Quantity default value is none is provided
quantity = 1

# Settings to enable or disable
# instrument mapping
instrument_mapping = true
Expand Down
9 changes: 9 additions & 0 deletions findmyorder/handler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
Supported Handlers
"""

from .basic import BasicHandler
from .standard import StandardHandler

__all__ = ["StandardHandler", "BasicHandler"]
53 changes: 53 additions & 0 deletions findmyorder/handler/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
Basic Parser
"""

from pyparsing import (
Optional,
Word,
alphas,
nums,
one_of,
pyparsing_common,
)

from .handler import ParserClient


class BasicHandler(ParserClient):

def __init__(self, **kwargs):
"""
Initialize the Handler object
"""

super().__init__(**kwargs)
self.client = "basic"

async def identify_order(
self,
my_string: str,
) -> dict:
"""
Identify an order and return a dictionary
with the order parameters
Args:
my_string (str): Message
Returns:
dict with the order parameters:
action, instrument
"""
action = (
one_of(self.action_identifier, caseless=True)
.set_results_name("action")
.set_parse_action(pyparsing_common.upcase_tokens)
)
instrument = Word(alphas + nums).set_results_name("instrument")
order_grammar = action("action") + Optional(instrument, default=None)
order = order_grammar.parse_string(instring=my_string, parse_all=False)
return order.asDict()
125 changes: 125 additions & 0 deletions findmyorder/handler/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from datetime import datetime, timezone

from loguru import logger


class ParserClient:
"""
Parser Handler Base Class
Args:
**kwargs:
Methods:
search(self)
identify_order(self)
get_order(self)
replace_instrument(self)
"""

def __init__(self, **kwargs):
"""
Initialize the chat client.
"""
self.name = kwargs.get("name", None)
self.client = None
self.enabled = kwargs.get("enabled", None)
self.action_identifier = kwargs.get("action_identifier", "BUY SELL")
self.action_identifier = self.action_identifier.lower()
self.stop_loss_identifier = kwargs.get("stop_loss_identifier", None)
self.take_profit_identifier = kwargs.get("take_profit_identifier", None)
self.quantity_identifier = kwargs.get("quantity_identifier", None)
self.order_type_identifier = kwargs.get("order_type_identifier", None)
self.leverage_type_identifier = kwargs.get("leverage_type_identifier", None)
self.comment_identifier = kwargs.get("comment_identifier", None)
self.stop_loss = kwargs.get("stop_loss", None)
self.take_profit = kwargs.get("take_profit", None)
self.quantity = kwargs.get("quantity", None)
self.instrument_mapping = kwargs.get("instrument_mapping", None)
self.mapping = kwargs.get("mapping", None)
self.ignore_instrument = kwargs.get("ignore_instrument", None)

async def identify_order(
self,
my_string: str,
) -> dict:
"""
Identify an order and return a dictionary
with the order parameters to be implemented in
the child class
"""

async def search(self, message: str) -> bool:
"""
Search an order.
Args:
message (str): Message
Returns:
bool
"""
if message:
order_identifier = message.split()[0].lower()
# logger.debug("Order identifier: {}", order_identifier)
# logger.debug("Action identifiers: {}", self.action_identifiers)
if order_identifier in self.action_identifier:

# logger.debug("Order identifier found in {}", order_identifier)
return True

return False

async def replace_instrument(self, order):
"""
Replace instrument by an alternative instrument, if the
instrument is not in the mapping, it will be ignored.
Args:
order (dict):
Returns:
dict
"""
instrument = order["instrument"]
for item in self.mapping:
if item["id"] == instrument:
order["instrument"] = item["alt"]
break
logger.debug("Instrument symbol changed", order)
return order

async def get_order(
self,
msg: str,
):
"""
Get an order from a message. The message can be
an order or an order identifier
Args:
msg (str): Message
Returns:
dict
"""
if not await self.search(msg):
logger.debug("No order identified")
return None
order = await self.identify_order(msg)
if isinstance(order, dict):
order["timestamp"] = datetime.now(timezone.utc).strftime(
"%Y-%m-%dT%H:%M:%SZ"
)
if self.instrument_mapping:
logger.debug("mapping")
await self.replace_instrument(order)
if order["instrument"] in self.ignore_instrument:
logger.debug("Ignoring instrument {}", order["instrument"])
return
logger.debug("Order identified {}", order)
return order
91 changes: 91 additions & 0 deletions findmyorder/handler/standard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
Standard Parser
"""

from loguru import logger
from pyparsing import (
Combine,
Optional,
Suppress,
Word,
alphas,
nums,
one_of,
pyparsing_common,
)

from .handler import ParserClient


class StandardHandler(ParserClient):

def __init__(self, **kwargs):
"""
Initialize the Handler object
"""

super().__init__(**kwargs)
self.client = "standard"

async def identify_order(
self,
my_string: str,
) -> dict:
"""
Identify an order and return a dictionary
with the order parameters
Args:
my_string (str): Message
Returns:
dict
"""
if not await self.search(my_string):
logger.debug("No order identified")
return None
else:
action = (
one_of(self.action_identifier, caseless=True)
.set_results_name("action")
.set_parse_action(pyparsing_common.upcase_tokens)
)
instrument = Word(alphas + nums).set_results_name("instrument")
stop_loss = Combine(
Suppress(self.stop_loss_identifier) + Word(nums)
).set_results_name("stop_loss")
take_profit = Combine(
Suppress(self.take_profit_identifier) + Word(nums)
).set_results_name("take_profit")
quantity = Combine(
Suppress(self.quantity_identifier)
+ Word(nums)
+ Optional(Suppress("%"))
).set_results_name("quantity")
order_type = one_of(
self.order_type_identifier, caseless=True
).set_results_name("order_type")
leverage_type = one_of(
self.leverage_type_identifier, caseless=True
).set_results_name("leverage_type")
comment = Combine(
Suppress(self.comment_identifier) + Word(alphas)
).set_results_name("comment")

order_grammar = (
action("action")
+ Optional(instrument, default=None)
+ Optional(stop_loss, default=self.stop_loss)
+ Optional(take_profit, default=self.take_profit)
+ Optional(quantity, default=self.quantity)
+ Optional(order_type, default=None)
+ Optional(leverage_type, default=None)
+ Optional(comment, default=None)
)

order = order_grammar.parse_string(instring=my_string, parse_all=False)
logger.debug("Order parsed {}", order)
return order.asDict()
Loading

0 comments on commit 8a4b9b0

Please sign in to comment.