10 changed files with 327 additions and 53 deletions
@ -0,0 +1,10 @@ |
|||
{ |
|||
"python.testing.pytestPath": "./scripts/test.sh", |
|||
"python.testing.unittestEnabled": false, |
|||
"python.testing.pytestEnabled": true, |
|||
"testOnSave.enabled": true, |
|||
"testOnSave.testCommand": "./scripts/test.sh", |
|||
"python.testing.pytestArgs": [ |
|||
"tests" |
|||
] |
|||
} |
@ -1,3 +1,12 @@ |
|||
# AutoTrading |
|||
|
|||
Used [this](https://www.kaggle.com/sudalairajkumar/cryptocurrencypricehistory) dataset but for now just making a mean of the values. |
|||
|
|||
## Use it |
|||
|
|||
# install |
|||
./scripts/install.sh |
|||
# run |
|||
python main.py |
|||
# test |
|||
./scripts/test.sh |
|||
|
@ -0,0 +1,21 @@ |
|||
"""Some errors to catch.""" |
|||
|
|||
class TradingException(Exception): |
|||
""" An error connected to the trading logic. """ |
|||
message: str |
|||
|
|||
def __init__(self, message): |
|||
super().__init__(message) |
|||
self.message = message |
|||
|
|||
class OrderException(TradingException): |
|||
""" An order execution was unsuccessfull. """ |
|||
|
|||
class UnknowOrder(OrderException): |
|||
""" The exectuor can't process this order. """ |
|||
|
|||
class OrderFails(OrderException): |
|||
""" The order placement failed. """ |
|||
|
|||
class PTFException(TradingException): |
|||
""" An error occur inside the PTF. """ |
@ -1,54 +1,173 @@ |
|||
""" |
|||
Define basic interfaces for trading strategies. |
|||
""" |
|||
from abc import ABC, abstractmethod |
|||
import logging |
|||
|
|||
import pandas as pd # type: ignore |
|||
from abc import ABC, abstractmethod, abstractproperty |
|||
from dataclasses import dataclass, field |
|||
from datetime import timedelta, datetime |
|||
from pandas import DataFrame |
|||
from typing import Dict, List, Optional, Type, Callable |
|||
|
|||
from .errors import OrderException, UnknowOrder, PTFException |
|||
|
|||
class Portfolio(ABC): |
|||
"""How may of each money do I have ?""" |
|||
|
|||
@abstractmethod |
|||
def content(self) -> dict: |
|||
"""return the content in each currency""" |
|||
@dataclass |
|||
class Order: |
|||
""" An order to execute on a market. """ |
|||
successfull: Optional[bool] = field(default=None, init=False) |
|||
creation_date: datetime |
|||
|
|||
@abstractmethod |
|||
def withdraw(self, amount: int, currency: str) -> None: |
|||
"""Withdraw money from the portfolio""" |
|||
|
|||
@abstractmethod |
|||
def deposit(self, amount: int, currency: str) -> None: |
|||
"""Deposit money into the portfolio""" |
|||
@dataclass |
|||
class CandlesProperties: |
|||
period: timedelta |
|||
|
|||
@abstractmethod |
|||
def convert(self, amount: int, to_amount: int, currency: str, to_currency: str) -> None: |
|||
"""Convert money from one currency to another""" |
|||
|
|||
class DataBroker(ABC): |
|||
""" Somethink that give you data. """ |
|||
|
|||
def __init__(self): |
|||
""" Init the class. """ |
|||
self.logger = logging.getLogger(self.__class__.__name__) |
|||
|
|||
@abstractproperty |
|||
def properties(self) -> CandlesProperties: |
|||
""" Return the properties of the candles for this broker. """ |
|||
|
|||
@abstractproperty |
|||
def current_change(self) -> DataFrame: |
|||
""" Return the current change for each money. """ |
|||
|
|||
class Strategy(ABC): |
|||
"""When do I buy and how many ?""" |
|||
def __iter__(self) -> "DataBroker": |
|||
""" Initialise the iterator. """ |
|||
return self |
|||
|
|||
@abstractmethod |
|||
def run(self, ptf: Portfolio, result: dict, current_conversion_rate: dict) -> None: |
|||
"""Run the strategy""" |
|||
def __next__(self) -> DataFrame: |
|||
"""Next values. |
|||
|
|||
Return the dataframe of all stock history for the strategy / indicators. |
|||
|
|||
Returns: |
|||
DataFrame: Time-Stock valuated candlestick data. |
|||
For each time and each stock give (high, low, open, close). |
|||
""" |
|||
|
|||
|
|||
class Indicator(ABC): |
|||
""" Somethink that give you an insight of the market. """ |
|||
|
|||
def __init__(self): |
|||
""" Init the class. """ |
|||
self.logger = logging.getLogger(self.__class__.__name__) |
|||
|
|||
@abstractmethod |
|||
def __call__(self, data: DataFrame) -> DataFrame: |
|||
"""Return a dataframe of valuation of each stock from the input data. |
|||
|
|||
Args: |
|||
data (DataFrame): Time-Stock valuated candlestick data. |
|||
For each time and each stock give (high, low, open, close). |
|||
|
|||
class Broker(ABC): |
|||
"""Return the data""" |
|||
Returns: |
|||
DataFrame: Stock valuated float. |
|||
For each stock give -1 if realy bad and +1 if realy good. |
|||
""" |
|||
|
|||
@abstractmethod |
|||
def __bool__(self): |
|||
"""Return True if the broker has data to retrive""" |
|||
|
|||
@abstractmethod |
|||
def next(self) -> pd.DataFrame: |
|||
"""Return the next data to process""" |
|||
class Strategy(ABC): |
|||
""" What order should you take on the market. """ |
|||
indicators: Dict[str, Indicator] |
|||
|
|||
def __init__(self, indicators: Dict[str, Indicator]): |
|||
""" Init the class with some inticators. """ |
|||
self.logger = logging.getLogger(self.__class__.__name__) |
|||
self.indicators = indicators |
|||
|
|||
def run(self, data: DataFrame) -> List[Order]: |
|||
"""Execute the strategy from the data. |
|||
|
|||
Args: |
|||
data (DataFrame): The Data broker output. |
|||
For each time and each stock give (high, low, open, close). |
|||
|
|||
class Predictor(ABC): |
|||
"""What is the future ?""" |
|||
Returns: |
|||
List[Order]: A list of orders to execute. |
|||
""" |
|||
indicators_results: DataFrame = ... |
|||
return self.execute(data, indicators_results) |
|||
|
|||
@abstractmethod |
|||
def predict(self, data: pd.DataFrame) -> dict: |
|||
"""Return the prediction""" |
|||
def execute(self, data: DataFrame, indicators_results: DataFrame) -> List[Order]: |
|||
"""Execute the strategy with the indicators insights. |
|||
|
|||
Args: |
|||
data (DataFrame): The Data broker output. |
|||
For each time and each stock give (high, low, open, close). |
|||
|
|||
indicators_results (DataFrame): Indicator-Stock valuated float. |
|||
For each indicator and each stock give -1 if realy bad and +1 if realy good. |
|||
|
|||
Returns: |
|||
List[Order]: A list of orders to execute. |
|||
""" |
|||
|
|||
|
|||
class PTF(ABC): |
|||
""" Somethink that buy or sell stocks.""" |
|||
|
|||
executors: Dict[object, Callable[["PTF", Order], None]] = {} |
|||
history: List[Order] |
|||
|
|||
def __init__(self, skip_errors: bool = True, save_errors: bool = True): |
|||
""" Init the class. |
|||
|
|||
Args: |
|||
skip_errors (bool, optional): Do we skip orders in failure ? Defaults to True. |
|||
save_errors (bool, optional): Do we save orders in failure in the history ? Defaults to True. |
|||
""" |
|||
self.logger = logging.getLogger(self.__class__.__name__) |
|||
self.history = [] |
|||
self.skip_errors = skip_errors |
|||
self.save_errors = save_errors |
|||
|
|||
@abstractproperty |
|||
def balance(self) -> float: |
|||
""" Return the current total balance. """ |
|||
|
|||
def execute_multiples(self, orders: List[Order]) -> None: |
|||
"""Execute all orders |
|||
|
|||
Args: |
|||
orders (List[Order]): The list of all orders to execute. |
|||
""" |
|||
for order in orders: |
|||
self.logger.debug("Applying order %s...", order) |
|||
try: |
|||
self._execute(order) |
|||
order.successfull = True |
|||
except OrderException as e: |
|||
if not self.skip_errors: |
|||
raise PTFException(f"Got and order exception : {e.message}") from e |
|||
self.logger.warn("Got an order exception : %s", e.message) |
|||
order.successfull = False |
|||
if self.save_errors or order.successfull: |
|||
self.history.append(order) |
|||
|
|||
def _execute(self, order: Order) -> None: |
|||
"""Execute one order. |
|||
|
|||
Try to execute the order on the market. |
|||
|
|||
Raises: |
|||
OrderError: if the execution was unsuccessfull. |
|||
|
|||
Args: |
|||
order (Order): One order to execute. |
|||
""" |
|||
for order_type, executor in self.executors.items(): |
|||
if isinstance(order, order_type): |
|||
executor(self, order) |
|||
return |
|||
raise UnknowOrder(f"Can not process order of type {type(order)}") |
|||
|
@ -0,0 +1,30 @@ |
|||
"""Some orders on the market.""" |
|||
from dataclasses import dataclass |
|||
|
|||
from .interfaces import Order |
|||
|
|||
|
|||
@dataclass |
|||
class Long(Order): |
|||
"""Buy stock.""" |
|||
stock: str |
|||
amount: float |
|||
price: float |
|||
|
|||
@property |
|||
def amount_usd(self) -> float: |
|||
"""The amount in $""" |
|||
return self.amount * self.price |
|||
|
|||
|
|||
@dataclass |
|||
class Short(Order): |
|||
"""Buy stock.""" |
|||
stock: str |
|||
amount: float |
|||
price: float |
|||
|
|||
@property |
|||
def amount_usd(self) -> float: |
|||
"""The amount in $""" |
|||
return self.amount * self.price |
@ -0,0 +1,5 @@ |
|||
#!/bin/bash |
|||
|
|||
python3.10 -m venv venv |
|||
source venv/bin/activate |
|||
python -m pip install -r requirements.txt |
@ -0,0 +1,5 @@ |
|||
#!/bin/bash |
|||
|
|||
source venv/bin/activate |
|||
python -m pytest $@ |
|||
coverage xml |
@ -0,0 +1,68 @@ |
|||
import pytest |
|||
from datetime import datetime |
|||
|
|||
from auto_trading.errors import UnknowOrder |
|||
from auto_trading.interfaces import PTF |
|||
from auto_trading.orders import Short, Long |
|||
|
|||
|
|||
long_order = Long(datetime.now(), "BTC", 1, 1) |
|||
short_order = Short(datetime.now(), "GOLD", 1, 1) |
|||
|
|||
|
|||
class _TestPTFLong(PTF): |
|||
@property |
|||
def balance(self): |
|||
return 0 |
|||
|
|||
def execute_long(self, order: Long): |
|||
assert order == long_order |
|||
|
|||
executors = {Long: execute_long} |
|||
|
|||
|
|||
class _TestPTFLongShort(PTF): |
|||
@property |
|||
def balance(self): |
|||
return 0 |
|||
|
|||
def execute_long(self, order: Long): |
|||
assert order == long_order |
|||
|
|||
def execute_short(self, order: Long): |
|||
assert order == short_order |
|||
|
|||
executors = {Long: execute_long, Short: execute_short} |
|||
|
|||
|
|||
class _TestPTFShort(PTF): |
|||
@property |
|||
def balance(self): |
|||
return 0 |
|||
|
|||
def execute_short(self, order: Long): |
|||
assert order == short_order |
|||
|
|||
executors = {Short: execute_short} |
|||
|
|||
|
|||
class _TestPTFNone(PTF): |
|||
@property |
|||
def balance(self): |
|||
return 0 |
|||
|
|||
|
|||
@pytest.mark.parametrize("test_class, must_match", [ |
|||
(_TestPTFLong, [long_order]), |
|||
(_TestPTFShort, [short_order]), |
|||
(_TestPTFLongShort, [long_order, short_order]), |
|||
(_TestPTFNone, []) |
|||
]) |
|||
def test_ptf_execution(test_class, must_match): |
|||
inst = test_class() |
|||
for o in [short_order, long_order]: |
|||
if o in must_match: |
|||
inst._execute(o) |
|||
else: |
|||
with pytest.raises(UnknowOrder): |
|||
inst._execute(o) |
Loading…
Reference in new issue