PART SIX: Configuration Files — Building a Production-Ready Algorithmic Trading Framework in Python

Joseph Edginton-Foy
5 min readJun 19, 2023

--

Photo by Paul Hanaoka on Unsplash

Hello there,

We continue the series on Building a Production-Ready Algorithmic Trading Framework in Python. This article will focus on why and how to use configuration files in Python. The prerequisites for this article can be found below. They contain the classes you will need to get up and running and, if you missed them, the inside knowledge to understand what is happening. Let's get started!

· Logging

· Error Handling

Picture this: You have written your indicator in Python that gives you 300% gains (LOL, I know); for this indicator to work, it needs some arguments. You can hardcode these values yourself, and that will work great. Congratulations, thanks for reading. Of course, this could be better. Say you want to change this value; it will be fine if you only have this one variable to change. But imagine you have several of them scattered around in different classes, files and functions… it can become inconvenient to change these variables manually.

Introducing Python and configuration files. Let’s explore how these two elements can harmonise, adding elegance and flexibility to your software.

In Python, configuration files come in various formats, such as JSON, YAML, or INI. These files store settings, preferences, and adjustable values separately from your code, making it easier to tweak and fine-tune your trading strategy without delving into the depths of your codebase.

INI Files

To create these control panels in Python, I like to use .INI files. They allow for comments and sectioning, which are great if you need to return to them later; the comments should help anyone trying to configure the software or a future you.

; Example Configuration File

[General]
; The name of the strategy
StrategyName = My Awesome Strategy

[IndicatorParameters]
; The period for calculating the Exponential Moving Average
EMAPeriod = 20

; The stop loss percentage for exiting a trade
StopLoss = 0.05

Next, you will need to import that information into your software. In Python, there is an inbuilt library called configparser. By importing our target .INI file, we can now access those values, which can be injected into our software. Our software now has a flexible control panel.

import configparser

# Create a ConfigParser object
config = configparser.ConfigParser()

# Read the configuration file;
# if the file is in another directory, provide the path
config.read('config.ini')

# Access values from the configuration file
strategy_name = config.get('General', 'StrategyName')
ema_period = config.getint('IndicatorParameters', 'EMAPeriod')
stop_loss = config.getfloat('IndicatorParameters', 'StopLoss')

# Print the retrieved values
print("Strategy Name:", strategy_name)
print("EMA Period:", ema_period)
print("Stop Loss:", stop_loss)
Photo by Mike Kononov on Unsplash

Best of all, we can use these INI files to communicate with other programs about the state or if we need values updated based on their calculation.

import configparser

# Create a ConfigParser object
config = configparser.ConfigParser()

# Read the configuration file
config.read('config.ini')

# Update a value in the configuration file
# This could be the output of another function
config.set('IndicatorParameters', 'EMAPeriod', '30')

# Save the changes to the configuration file
with open('config.ini', 'w') as config_file:
config.write(config_file)

print("Configuration file updated successfully.")

Here are some things to note about using these files.

Section and Option Names:

  • Section names are enclosed in square brackets, e.g., [SectionName].
  • Option names and values are specified using the format option_name = option_value.
  • Section and option names are case-insensitive, so [SectionName] and [sectionname] are considered the same.

Comments:

  • INI files support comments, which are lines starting with semicolons (;) or hash symbols (#).
  • Comments help provide explanations and document settings for the configuration file.

Multiple Values:

  • INI files allow multiple values for a single option.
  • Each value should be on a separate line under the option name.
  • The values can be accessed as a list using the get() method with the fallback parameter set to an empty string, like
[General]
StrategyName = My Awesome Strategy

[IndicatorParameters]
EMAPeriod = 20

# Multiple values for a single option
TradeSymbols =
BTC-USD
ETH-USD
LTC-USD
import configparser

# Create a ConfigParser object
config = configparser.ConfigParser()

# Read the configuration file
config.read('config.ini')

config.get('IndicatorParameters', 'TradeSymbols', fallback='').split('\n')

Code

As always, I like to make a class with this basic functionality that can be inherited into other files within the framework.

import configparser
from logging import Logger
from CORE.Error_Handling import ErrorHandling


class ConfigManager(ErrorHandling):
def __init__(self, logger: Logger, config_path: str):
super().__init__(logger)
self.logger = logger
self.config_path: str = config_path
self.config: configparser.ConfigParser

def create_config(self) -> configparser.ConfigParser:
"""
creates config object
:return: ConfigParser object
"""
self.logger.debug(f"Fetching Config Data For '{self.config_path}'...")

config: configparser.ConfigParser = configparser.ConfigParser()
try:
config.read(self.config_path)

if config is not None:
self.logger.debug('Fetching Config Data Successful')

else:
self.ErrorsDetected = True
self.ErrorList.append(
self.error_details(f"{__class__}: create_config -> Config File '{self.config_path}' Returned None."))

except FileNotFoundError:
self.ErrorsDetected = True
self.ErrorList.append(
self.error_details(f"{__class__}: create_config -> Config file '{self.config_path}' not found"))

except configparser.Error as e:
self.ErrorsDetected = True
self.ErrorList.append(self.error_details(
f"{__class__}: create_config -> Failed to read the config file '{self.config_path}' {str(e)}"))

except Exception as error_:
self.ErrorsDetected = True
self.ErrorList.append(self.error_details(f"{__class__}: create_config -> An Error Has Occurred {error_}"))

return config

def update_config(self, section, option, value):
"""
Update config files
:param section:
:param option:
:param value:
:return:
"""
self.logger.debug(f"Updating Config Data For '{self.config_path}' {section} | {option} | {value}...")
try:
if not self.ErrorsDetected:
if self.config is not None:
self.config.set(section, option, value)
with open(self.config_path, 'w') as config_file:
self.config.write(config_file)
self.logger.debug("Configuration file updated successfully.")

else:
self.ErrorsDetected = True
self.ErrorList.append(self.error_details(
f"{__class__}: update_config -> Config Object For '{self.config_path}' Returned None."))

except (configparser.Error, FileNotFoundError) as e:
self.ErrorsDetected = True
print(f"Error: Failed to update the config file. Reason: {str(e)}")

except Exception as error_:
self.ErrorsDetected = True
self.ErrorList.append(self.error_details(f"{__class__}: _create_config -> An Error Has Occurred {error_}"))

Congratulations, dear reader! You’ve now mastered the art of dancing with Python and configuration files. Remember, configuration files add a layer of flexibility and reusability to your code, allowing you to fine-tune your strategies without breaking a sweat.

Photo by JOSHUA COLEMAN on Unsplash

That’s all she wrote, folks. I hope you learnt some things and will use my tools; hopefully, it will help you along the way. Peace.

If you want to help buy me a coffee that fuels these articles’ late nights of research and development, please consider donating to my PayPal link below. Thanks so much, and have a great day.

paypal.me/JEFSBLOG

--

--