1
0

Compare commits

..

2 Commits

Author SHA1 Message Date
92c7f3e541
Output and secrets are now configurable
All checks were successful
Test Python / Test (push) Successful in 27s
2024-09-04 20:04:37 +02:00
48a821a8d4
Improved error recovery 2024-09-04 19:48:29 +02:00
5 changed files with 102 additions and 67 deletions

View File

@ -216,50 +216,65 @@ def run_auto_sell(
all_executed_orders: list[fin_depo.data.TradeOrderDetails] = [] all_executed_orders: list[fin_depo.data.TradeOrderDetails] = []
total_sleep_duration = datetime.timedelta(seconds=0) total_sleep_duration = datetime.timedelta(seconds=0)
while True: try:
# Check that account has tokens. while True:
input_amount_available = config.seller.get_depo().get_amount_of_asset( # Check that account has tokens.
config.input_asset, input_amount_available = config.seller.get_depo().get_amount_of_asset(
)
logger.info(
'Currently own %s %s',
input_amount_available,
config.input_asset.raw_short_name(),
)
if initial_rounds_to_skip > 0:
initial_rounds_to_skip -= 1
logger.info('skipping this round')
elif input_amount_available > 0:
amount_to_sell = sample_from_range(rng, config.input_amount_range)
amount_to_sell = min(input_amount_available, amount_to_sell)
amount_to_sell = amount_to_sell.quantize(
ROUND_TO_WHOLE,
rounding=ROUND_DOWN,
)
logger.info('Attempting to sell %s %s', amount_to_sell, config.input_asset)
order_details = config.seller.place_market_order(
config.input_asset, config.input_asset,
amount_to_sell, )
config.output_asset, logger.info(
'Currently own %s %s',
input_amount_available,
config.input_asset.raw_short_name(),
) )
config.log_order_to_csv(order_details) if initial_rounds_to_skip > 0:
all_executed_orders.append(order_details) initial_rounds_to_skip -= 1
logger.info('skipping this round')
elif input_amount_available > 0:
amount_to_sell = sample_from_range(rng, config.input_amount_range)
amount_to_sell = min(input_amount_available, amount_to_sell)
amount_to_sell = amount_to_sell.quantize(
ROUND_TO_WHOLE,
rounding=ROUND_DOWN,
)
logger.info(
'Attempting to sell %s %s',
amount_to_sell,
config.input_asset,
)
del amount_to_sell order_details = config.seller.place_market_order(
elif config.exit_when_empty: config.input_asset,
break amount_to_sell,
config.output_asset,
)
# Time out config.log_order_to_csv(order_details)
time_to_sleep = sample_from_range(rng, config.interval_range) all_executed_orders.append(order_details)
time_to_sleep_secs = time_to_sleep.total_seconds()
logger.info('Sleeping %s (%d seconds)', time_to_sleep, time_to_sleep_secs)
config.sleep(time_to_sleep_secs)
total_sleep_duration += time_to_sleep
del input_amount_available del amount_to_sell
elif config.exit_when_empty:
break
# Time out
time_to_sleep = sample_from_range(rng, config.interval_range)
time_to_sleep_secs = time_to_sleep.total_seconds()
logger.info('Sleeping %s (%d seconds)', time_to_sleep, time_to_sleep_secs)
config.sleep(time_to_sleep_secs)
total_sleep_duration += time_to_sleep
del input_amount_available
except KeyboardInterrupt:
logger.warning('Manual interrupt')
except RuntimeError:
logger.exception(
'A unexpected and serious error occured. The program will be winding down',
)
logger.fatal('Please send the above error message to: mailto:jonjmaa@gmail.com')
logger.fatal(
'He will attempt to diagnosticate the problem, and help with recovery',
)
time_end = datetime.datetime.now(tz=datetime.UTC) time_end = datetime.datetime.now(tz=datetime.UTC)
@ -288,41 +303,45 @@ def log_estimates(config: AutoSellConfig):
current_balance / config.input_amount_range[0], current_balance / config.input_amount_range[0],
) )
minisleep = 0.1
logger.info('')
logger.info('Welcome to crypto seller!') logger.info('Welcome to crypto seller!')
config.sleep(1) config.sleep(3)
logger.info('') logger.info('')
config.sleep(1) config.sleep(minisleep)
logger.info('I, the great crypto seller, your humble servant, will') logger.info('I, the great crypto seller, your humble servant, will')
config.sleep(minisleep)
logger.info('now analyse your configuration and divine some estimates!') logger.info('now analyse your configuration and divine some estimates!')
config.sleep(1) config.sleep(3)
logger.info('') logger.info('')
config.sleep(1) config.sleep(minisleep)
logger.info( logger.info(
'- Current balance: %s %s', '- Current balance: %s %s',
current_balance, current_balance,
config.input_asset.raw_short_name(), config.input_asset.raw_short_name(),
) )
config.sleep(1) config.sleep(minisleep)
logger.info('- Average sleep: %s seconds', average_sleep) logger.info('- Average sleep: %s seconds', average_sleep)
config.sleep(1) config.sleep(minisleep)
logger.info( logger.info(
'- Average amount: %s %s', '- Average amount: %s %s',
average_amount, average_amount,
config.input_asset.raw_short_name(), config.input_asset.raw_short_name(),
) )
config.sleep(1) config.sleep(minisleep)
logger.info('- Expected counts: %s', expected_num_sell_offs) logger.info('- Expected counts: %s', expected_num_sell_offs)
config.sleep(1) config.sleep(minisleep)
logger.info('- Expected time: %s', expected_duration) logger.info('- Expected time: %s', expected_duration)
config.sleep(1) config.sleep(minisleep)
logger.info('- Fastest time: %s', fastest_duration) logger.info('- Fastest time: %s', fastest_duration)
config.sleep(1) config.sleep(minisleep)
logger.info('- Slowest time: %s', slowest_duration) logger.info('- Slowest time: %s', slowest_duration)
config.sleep(1) config.sleep(minisleep)
logger.info('') logger.info('')
config.sleep(1) config.sleep(minisleep)
logger.info('Do you still want to proceed?') logger.info('Do you still want to proceed?')
config.sleep(1) config.sleep(minisleep)
logger.info('If not, press CTRL+C within the next 10 seconds...') logger.info('If not, press CTRL+C within the next 10 seconds...')
config.sleep(10) config.sleep(10)
logger.info('') logger.info('')

View File

@ -3,6 +3,7 @@ import datetime
import json import json
import logging import logging
import logging.handlers import logging.handlers
import sys
import time import time
from decimal import Decimal from decimal import Decimal
from pathlib import Path from pathlib import Path
@ -24,18 +25,18 @@ logger = logging.getLogger(__name__)
################################################################################ ################################################################################
# Constants # # Constants #
PATH_OUTPUT = Path('./output').absolute() PATH_FILE_LOG = 'log.txt'
PATH_LOG_FILE = PATH_OUTPUT / 'log.txt' PATH_FILE_TRADES = 'trades.csv'
PATH_TRADES_FILE = PATH_OUTPUT / 'trades.csv'
################################################################################ ################################################################################
# Application Setup # # Application Setup #
def setup_logging(): def setup_logging(output_directory: Path):
"""Enables logging for the terminal and to a log file.""" """Enables logging for the terminal and to a log file."""
PATH_LOG_FILE.parent.mkdir(parents=True, exist_ok=True) path_log_file = output_directory / PATH_FILE_LOG
file_handler = logging.handlers.WatchedFileHandler(filename=PATH_LOG_FILE) path_log_file.parent.mkdir(parents=True, exist_ok=True)
file_handler = logging.handlers.WatchedFileHandler(filename=path_log_file)
file_handler.setFormatter( file_handler.setFormatter(
logging.Formatter( logging.Formatter(
'%(levelname)s:%(asctime)s: %(message)s', '%(levelname)s:%(asctime)s: %(message)s',
@ -69,7 +70,7 @@ License : MIT License (see website for full text)
""".strip() """.strip()
def load_config(config_path: Path) -> AutoSellConfig: def load_config(config_path: Path, output_directory: Path) -> AutoSellConfig:
logger.info('Loading configuration') logger.info('Loading configuration')
from . import secrets_config from . import secrets_config
@ -98,7 +99,7 @@ def load_config(config_path: Path) -> AutoSellConfig:
exit_when_empty=True, exit_when_empty=True,
seller=seller_backend, seller=seller_backend,
sleep=time.sleep, sleep=time.sleep,
log_order_to_csv=order_csv.CsvFileLogger(PATH_TRADES_FILE), log_order_to_csv=order_csv.CsvFileLogger(output_directory / PATH_FILE_TRADES),
) )
@ -116,6 +117,13 @@ def parse_args():
required=True, required=True,
help='Trading configuration file', help='Trading configuration file',
) )
parser.add_argument(
'--output',
type=Path,
dest='output_directory',
required=True,
help='Directory for outputing logs and trades list',
)
parser.add_argument( parser.add_argument(
'--wait-before-first', '--wait-before-first',
action='store_true', action='store_true',
@ -127,16 +135,23 @@ def parse_args():
def main(): def main():
"""Initializes the program.""" """Initializes the program."""
setup_logging()
logger.info('Initializing crypto_seller')
args = parse_args() args = parse_args()
setup_logging(output_directory=args.output_directory)
logger.info('Initializing crypto_seller')
# Load config # Load config
auto_sell_config = load_config(args.config_file) auto_sell_config = load_config(
args.config_file,
output_directory=args.output_directory,
)
# Display estimates # Display estimates
log_estimates(auto_sell_config) try:
log_estimates(auto_sell_config)
except KeyboardInterrupt:
sys.exit(1)
# Run auto sell # Run auto sell
results = run_auto_sell( results = run_auto_sell(

View File

@ -4,7 +4,7 @@ from secret_loader import SecretLoader
__all__ = ['KUCOIN_KEY', 'KUCOIN_SECRET', 'KUCOIN_PASS'] __all__ = ['KUCOIN_KEY', 'KUCOIN_SECRET', 'KUCOIN_PASS']
secret_loader = SecretLoader() secret_loader = SecretLoader(ENV_KEY_PREFIX='CS')
KUCOIN_KEY = secret_loader.load_or_fail('KUCOIN_KEY') KUCOIN_KEY = secret_loader.load_or_fail('KUCOIN_KEY')
KUCOIN_SECRET = secret_loader.load_or_fail('KUCOIN_SECRET') KUCOIN_SECRET = secret_loader.load_or_fail('KUCOIN_SECRET')

1
requirements_test.txt Normal file
View File

@ -0,0 +1 @@
pytest

View File

@ -65,7 +65,7 @@ def test_auto_run():
exit_when_empty=True, exit_when_empty=True,
seller=seller_mock, seller=seller_mock,
log_order_to_csv=crypto_seller.order_csv.CsvFileLogger( log_order_to_csv=crypto_seller.order_csv.CsvFileLogger(
Path('output/test-trades.csv'), Path('test/output/test-trades.csv'),
), ),
sleep=sleep_mock, sleep=sleep_mock,
) )
@ -82,4 +82,4 @@ def test_auto_run():
# Check mocks agree # Check mocks agree
assert seller_mock.amount_left == 0 assert seller_mock.amount_left == 0
assert sleep_mock.time_slept == 1000 + 23 assert sleep_mock.time_slept == 1000 + 17.2