From a0b1ee9ae1f00c8b3144e2e9d28d1d8aeb6a3081 Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Mon, 22 Jul 2024 15:48:55 +0200 Subject: [PATCH] Log trades to csv file --- crypto_seller/__init__.py | 20 ++++++++------------ crypto_seller/__main__.py | 26 +++++++++++++++++++++++--- crypto_seller/order_csv.py | 25 +++++++++++++++++++++++++ test/test_auto_sell.py | 5 +++++ 4 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 crypto_seller/order_csv.py diff --git a/crypto_seller/__init__.py b/crypto_seller/__init__.py index 6e82c98..fca054d 100644 --- a/crypto_seller/__init__.py +++ b/crypto_seller/__init__.py @@ -62,11 +62,15 @@ most mature on the danish market, and does support KuCoin. ## TODO -- [ ] Log all trades to CSV file. - [ ] Parse configuration from json. -- [X] Collect information during the run and output after run +- [ ] Ensure that a failure during selling results in a safe winding down of the system. + * Catch runtime errors when selling + * Show errors to log. + * Stop loop and exit with results, and error indicator. - [ ] Document configuration - [ ] Document auditing +- [X] Log all trades to CSV file. +- [X] Collect information during the run and output after run """ @@ -88,17 +92,9 @@ from ._version import __version__ logger = logging.getLogger(__name__) -################################################################################ -# Constants # - -PATH_OUTPUT = Path('./output').absolute() -PATH_LOG_FILE = PATH_OUTPUT / 'log.txt' -PATH_TRADES_FILE = PATH_OUTPUT / 'trades.csv' - ################################################################################ # Main code # - @dataclasses.dataclass(frozen=True) class AutoSellConfig: """Configuration for `run_auto_sell`. @@ -123,6 +119,7 @@ class AutoSellConfig: output_asset: fin_defs.Asset seller: fin_depo.defi_kucoin.KucoinDepoFetcher sleep: Callable[[float], None] + log_order_to_csv: Callable[[fin_depo.data.TradeOrderDetails], None] exit_when_empty: bool = True @@ -187,9 +184,8 @@ def run_auto_sell(config: AutoSellConfig) -> AutoSellRunResults: config.output_asset, ) - print(order_details) + config.log_order_to_csv(order_details) all_executed_orders.append(order_details) - # TODO: Write order details to file. del amount_to_sell elif config.exit_when_empty: diff --git a/crypto_seller/__main__.py b/crypto_seller/__main__.py index e01b527..c83ac3e 100644 --- a/crypto_seller/__main__.py +++ b/crypto_seller/__main__.py @@ -1,18 +1,34 @@ +import argparse import datetime import logging import logging.handlers -import argparse import time from decimal import Decimal +from pathlib import Path import fin_defs import fin_depo -from . import PATH_LOG_FILE, AutoSellConfig, config, run_auto_sell, log_results +from . import ( + AutoSellConfig, + config, + log_results, + order_csv, + run_auto_sell, +) from . import logger as module_logger logger = logging.getLogger(__name__) +################################################################################ +# Constants # + +PATH_OUTPUT = Path('./output').absolute() +PATH_LOG_FILE = PATH_OUTPUT / 'log.txt' +PATH_TRADES_FILE = PATH_OUTPUT / 'trades.csv' + +################################################################################ +# Application Setup # def setup_logging(): """Enables logging for the terminal and to a log file.""" @@ -30,14 +46,17 @@ def setup_logging(): logger.setLevel('INFO') module_logger.setLevel('INFO') + CLI_DESCRIPTION = """ Sells financial assets from an online account. """.strip() + def parse_args(): - parser = argparse.ArgumentParser('crypto_seller', description = CLI_DESCRIPTION) + parser = argparse.ArgumentParser('crypto_seller', description=CLI_DESCRIPTION) return parser.parse_args() + def main(): """Initializes the program.""" setup_logging() @@ -60,6 +79,7 @@ def main(): exit_when_empty=True, seller=seller_backend, sleep=time.sleep, + log_order_to_csv=order_csv.CsvFileLogger(PATH_TRADES_FILE), ) results = run_auto_sell(auto_sell_config) logging.info('Sell-offs complete') diff --git a/crypto_seller/order_csv.py b/crypto_seller/order_csv.py new file mode 100644 index 0000000..79434a8 --- /dev/null +++ b/crypto_seller/order_csv.py @@ -0,0 +1,25 @@ +import csv +from pathlib import Path + +import fin_depo + + +class CsvFileLogger: + def __init__(self, path: Path): + self.path = path + + def __call__(self, trade_order: fin_depo.data.TradeOrderDetails): + fieldnames: list[str] = [ + 'input_asset', + 'input_amount', + 'output_asset', + 'output_amount', + 'fee_asset', + 'fee_amount', + 'order_id', + 'raw_order_details', + ] + + with open(self.path, 'a') as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writerow(trade_order.__dict__) diff --git a/test/test_auto_sell.py b/test/test_auto_sell.py index e28302e..25f1613 100644 --- a/test/test_auto_sell.py +++ b/test/test_auto_sell.py @@ -1,10 +1,12 @@ import datetime from decimal import Decimal +from pathlib import Path import fin_defs import fin_depo import crypto_seller +import crypto_seller.order_csv class SellerMock(fin_depo.data.DepoFetcher): @@ -59,6 +61,9 @@ def test_auto_run(): output_asset=fin_defs.USD, exit_when_empty=True, seller=seller_mock, + log_order_to_csv=crypto_seller.order_csv.CsvFileLogger( + Path('output/test-trades.csv'), + ), sleep=sleep_mock, )