2
\$\begingroup\$

Description:

This would make a first part of a currency exchange simulator The first phase includes downloading currency historical data from histdata.com api, plotting given period and given time frames using different plot types and adding some forex technical indicators to the data. I will be posting follow ups when I'm done with further phases of the project.

Feedback:

I need feedback regarding the whole program structure, and general suggestions for improvements/optimizations/features to add are welcome. I'm intending to add a simulation functionality with virtual money and automate a bot that runs on the historical data which predicts future values and maximize profit and maybe a GUI. I also need suggestions on predicting future values for a given time period in the future using machine learning I don't know maybe neural networks - regression - nlp ... I need guidance on focus points(what libraries/techniques...) for building a robust machine learning prediction system as I'm not quite experienced in ml.

Links:

Please download the following zip file and extract the contents(2 folders beginning with 'DAT') in the same folder as the code's (to be able to run the simulator on the period(2017-2018) for eurusd pair:

2017-2018 eurusd M1(1 minute data)

Files

  • forex_data_handler.py: This is used to download forex historical data from histdata.com api (Use pip3 install histdata beforehand if you're going to download extra data) and this is optional (Links are provided above for minimal trial-run) and of course feel free to download whatever data you want using this module for trying extra examples.
  • fx_simulator.py: This is the simulation part of the code and since it's the first part of the project, it contains the following features:

    A) The FxSimulator constructor accepts parameters such as currency pairs, years and a few other parameters(you'll find everything in the docstrings)

    B) get_interval() method which returns a specific time frame pandas DF. Time frames ex: M1 = 1 min, D5 = 5 days ... check the documentation.

    C) compare_years() and compare_year_months() methods to enable comparisons of months and years periods using a selection of parameters including interval, comparison_value ...

    D) plot_initial_data() method to plot the historical data (scatter, line, histogram ...

    E) add_indicators() method to add forex technical indicator values to the pandas df. Indicators include(Bollinger Bands, RSI, Moving Averages ...)

  • pairs.txt: Contains a list of 66 different currency pairs to choose from while downloading the data(optional) from histdata.com api.

Example Plots:

  • 2010-2018 period eurusd 'Close' 1 Day(D1) year comparisons(1 figure):

2010-2018 period eurusd 'Close' 1 Day(D1) year comparisons(1 figure)

  • 2010-2018 period eurusd 'Close' 1 Day(D1) year comparisons(Multiple plots):

2010-2018 period for eurusd 'Close' 1 Day(D1) year comparisons(Multiple plots)

  • 2019 Months comparison with Moving average(14 days) for eurusd 'Close' 1 Day(D1)

2019 Months comparison for eurusd 'Close' 1 Day(D1)

  • 2019 Seasonal decompose for eurusd 'Close' 1 Day(D1)

2019 Seasonal decompose for eurusd 'Close' 1 Day(D1)

forex_data_handler.py:

from histdata import download_hist_data
from concurrent.futures import ThreadPoolExecutor, as_completed
from time import perf_counter
import os


def get_folder_name(year, platform, time_frame, pair, month=None, compressed=True):
    """
    Produce folder name in histdata.com download format.
    Args:
        year: ex: 2010
        platform: MT, ASCII, XLSX, NT, MS.
        time_frame: M1 (one minute) or T (tick data).
        pair: ex: 'eurgbp'
        month: int range(1, 12) inclusive
        compressed: If compressed, a folder name ending with '.zip' is returned
    """
    if not month:
        folder_name = '_'.join(['DAT', platform, pair.upper(), time_frame, str(year)])
        if compressed:
            return folder_name + '.zip'
        else:
            return folder_name
    if month and month <= 9:
        month = '0' + str(month)
    folder_name = '_'.join(['DAT', platform, pair.upper(), time_frame, str(year) + str(month)])
    if compressed:
        return folder_name + '.zip'
    if not compressed:
        return folder_name


def download_fx_data(start_year, end_year, pairs, threads=50, time_frame='M1', platform='MT', output_directory='.',
                     current_year=2019, current_month=12):
    """
    Download Forex data over a given time range from histdata api.
    Args:
        start_year: int: The starting year.
        end_year: int: The ending year.
        pairs: A list of pairs ex: ['eurgbp', 'eurusd']
        threads: Number of parallel threads.
        time_frame: M1 (one minute) or T (tick data).
        platform: MT, ASCII, XLSX, NT, MS.
        output_directory: Where to dump the data.
        current_year: Current year.
        current_month: Current month.
    """
    month_pairs = []
    if end_year >= current_year:
        last_year = current_year - 1
    else:
        last_year = end_year
    years_pairs = [(year, pair) for year in range(start_year, last_year + 1) for pair in pairs
                   if get_folder_name(year, platform, time_frame, pair) not in os.listdir(output_directory)]
    if end_year >= current_year:
        month_pairs.extend([(month, pair) for pair in pairs for month in range(1, current_month)
                            if get_folder_name(current_year, platform, time_frame, pair, month)
                            not in os.listdir(output_directory)])
    with ThreadPoolExecutor(max_workers=threads) as executor:
        future_years_data = {executor.submit(download_hist_data, str(year), None, pair, time_frame,
                                             platform, output_directory): (year, pair) for year, pair in years_pairs}
        future_months_data = {executor.submit(download_hist_data, str(current_year), str(month), pair, time_frame,
                                              platform, output_directory): (month, pair) for month, pair in month_pairs}
        for future_file in as_completed(future_years_data):
            try:
                future_file.result()
            except AssertionError:
                print(f'Failure to retrieve {future_years_data[future_file]}')
        for future_file in as_completed(future_months_data):
            try:
                future_file.result()
            except AssertionError:
                print(f'Failure to retrieve {future_months_data[future_file]}')


if __name__ == '__main__':
    start_time = perf_counter()
    # Change the next line for specific choices, this is set to download all the data available.
    # pair_list = [pair.rstrip() for pair in open('pairs.txt').readlines()]
    # download_fx_data(2000, 2020, pair_list)
    end_time = perf_counter()
    print(f'Process finished ... {end_time - start_time} seconds.')

fx_simulator.py:

from statsmodels.tsa.seasonal import seasonal_decompose
from forex_data_handler import get_folder_name
import matplotlib.pyplot as plt
import pandas as pd
import os


class FxSimulator:
    """A tool for conducting forex backtesting and simulation."""
    def __init__(self, pair, years, history_data_path='.', platform='MT', time_frame='M1', current_year=2019,
                 current_month=12):
        """
        Initialize year data.
        Args:
            pair: A string of currency pair ex: 'eurusd'
            years: a list of years.
            history_data_path: Folder path containing the historical data.
            platform: MT, ASCII, XLSX, NT, MS.
            time_frame: M1 or T
            current_year: Current year ex: 2019
            current_month: Current month ex: 8
        """
        not_supported_years = [yr for yr in years if yr not in range(2000, current_year + 1) or yr > current_year]
        if not_supported_years:
            raise ValueError(f'Invalid year {not_supported_years[0]}'
                             f'\nYears supported are in range 2000-current year.')
        self.currency_pair = pair
        self.years = years
        self.path = history_data_path
        self.platform = platform
        self.time_frame = time_frame
        self.current_year = current_year
        self.current_month = current_month
        self.column_names = {'M1': ['Date', 'Time', 'Open', 'High', 'Low', 'Close', 'Volume']}

    def combine_frames(self, folders, sep=','):
        """
        Combine folder data.
        Args:
            folders: A list of folders.
            sep: Separator of the csv file.
        Return:
            A data frame of all folders combined.
        """
        frames = []
        for folder_name in folders:
            try:
                os.chdir(self.path + folder_name)
                current_frame = pd.read_csv(folder_name + '.csv', sep=sep, names=self.column_names[self.time_frame],
                                            parse_dates=True)
                frames.append(current_frame)
            except FileNotFoundError:
                print(f'Folder: {folder_name} not found.')
        if frames:
            return pd.concat(frames)

    def load_data(self, sep=',', year=None):
        """
        Load data of the given year range from csv.
        Args:
            sep: Separator of the csv file.
            year: To load a particular year data.
        Return:
            A data frame with the following columns (check self.column_names)
        """
        current_year_month_folders = [get_folder_name(
            self.current_year, self.platform, self.time_frame, self.currency_pair, month, False)
            for month in range(1, self.current_month)]
        if year:
            if year not in self.years:
                raise ValueError(f'Year {year} not included in self.years')
            if year != self.current_year:
                folder_name = get_folder_name(year, self.platform, self.time_frame, self.currency_pair, None, False)
                return self.combine_frames([folder_name], sep)
            if year == self.current_year:
                return self.combine_frames(current_year_month_folders, sep)
        folder_names = [get_folder_name(yr, self.platform, self.time_frame, self.currency_pair, None, False)
                        for yr in self.years if yr != self.current_year]
        if self.current_year in self.years:
            folder_names.extend(current_year_month_folders)
        return self.combine_frames(folder_names, sep)

    def get_interval(self, interval, year=None, day_timing='12:30'):
        """
        Set desired interval.
        Args:
            interval:
                'M' + Minute interval(int 1 - 60) ex: M15 --> 15 minute interval.
                'H' + Hour interval(int 1  - 24) ex: H4 --> 4 hour interval.
                'D' + Day interval(int 1 - 31) ex: D1 --> 1 day interval.
                'W' + Week interval(int 1 - 4)
            year: If year, interval of the year will be returned.
            day_timing: Timing of the day to get intervals.
        Return:
             Adjusted data frame.
        """
        if year:
            period_data = self.load_data(year=year)
        else:
            period_data = self.load_data()
        if 'M' in interval:
            current_minutes = period_data['Time'].apply(lambda timing: int(timing.split(':')[-1]))
            return period_data[current_minutes % int(interval[1:]) == 0]
        if 'H' in interval:
            hour_data = period_data[period_data['Time'].apply(lambda timing: timing.split(':')[-1] == '00')]
            only_hours = hour_data['Time'].apply(lambda timing: int(timing.split(':')[0]))
            valid_hours = only_hours % int(interval[1:]) == 0
            return hour_data[valid_hours]
        if 'D' in interval:
            day_data = period_data[period_data['Time'] == day_timing]
            day_only = day_data['Date'].apply(lambda timing: int(timing.split('.')[-1]))
            return day_data[day_only % int(interval[1:]) == 0]
        if 'W' in interval:
            week_data = period_data[period_data['Date'].apply(lambda timing: int(timing.split('.')[-1]) % 7) == 0]
            valid_weeks = week_data[week_data['Date'].apply(lambda timing:
                                                            int(timing.split('.')[-1]) % int(interval[1:])) == 0]
            return valid_weeks[valid_weeks['Time'] == day_timing]
        raise ValueError(f'Invalid interval: {interval}')

    def compare_years(self, interval, comparison_value):
        """
        Get A monthly indexed data for years in self.years.
        Args:
            interval: Period string indication ex: 'M1', 'D5' ...
            comparison_value: A string indication of value to compare: ex: 'Open', Close ...
        Return:
            A data frame containing year data per month.
        """
        frames = []
        for year in self.years:
            data = self.get_interval(interval, year)
            data['Month'] = data['Date'].apply(lambda timing: timing.split('.')[1])
            data.reset_index(drop=True, inplace=True)
            frames.append(data)
        all_years = pd.concat(frames, axis=1).dropna()
        all_years.set_index('Month', inplace=True)
        all_years.reset_index(inplace=True)
        all_years['Month'] = all_years['Month'].apply(lambda months: set(months).pop())
        years_data = all_years[['Month', comparison_value]]
        years_data.columns = ['Month'] + [comparison_value + '(' + str(yr) + ')' for yr in self.years]
        return years_data

    def compare_year_months(self, year, interval):
        """
        Get A certain year months.
        Args:
            year: A year specified in self.years
            interval: Period string indication ex: 'M1', 'D5' ...
        Return:
             A data frame containing months of a certain year.
        """
        if year not in self.years:
            raise ValueError(f'Year {year} not included in self.years')
        year_data = self.get_interval(interval, year)
        year_data['Month'] = year_data['Date'].apply(lambda timing: timing.split('.')[1])
        return year_data

    def plot_initial_data(self, plot_type, interval, year=None, years=False, comparison_value=None,
                          moving_average=None, **kwargs):
        """
        Plot initial data in several graph forms.
        Args:
            plot_type: A string indication to graph type.
                - li: Line plot.
                - h: Histogram.
                - bw: Box & whiskers plot.
                - lp: Lag plot.
                - ac: Auto-correlation plot.
                - sda: Additive Seasonal decompose(Observed-Trend-Seasonal-Residual).
                - sdm: Multiplicative Seasonal decompose(Observed-Trend-Seasonal-Residual).
            interval: Period string indication ex: 'M1', 'D5' ...
            year: If True, only months of given year will be plotted.
            years: If True, years will be compared.
            comparison_value: If years, this would be a string indication of value to compare against
            ('Open', Close ...)
            moving_average: int representing the moving average window to be displayed (works when a comparison
            value is specified).
            **kwargs: Additional keyword arguments.
        Return:
            None
        """
        if years and not comparison_value:
            raise ValueError(f'Comparison value not specified for years=True')
        if year and years:
            raise ValueError(f'Cannot compare a single year and all years, please specify year or years')
        if moving_average and not comparison_value:
            raise ValueError(f'Comparison value not specified for moving average {moving_average}')
        if plot_type in ['sda', 'sdm'] and not comparison_value:
            raise ValueError(f'Must specify a comparison value for seasonal decomposition')
        period_data = pd.DataFrame()
        if not year and not years:
            period_data = self.get_interval(interval)
            period_data.set_index('Date', inplace=True)
            if comparison_value:
                period_data = period_data.filter(like=comparison_value)
                if moving_average:
                    period_data['Moving Average'] = period_data[comparison_value].rolling(moving_average).mean()
        if years:
            period_data = self.compare_years(interval, comparison_value)
            period_data.set_index('Month', inplace=True)
            if moving_average:
                for column in period_data.columns:
                    if comparison_value in column:
                        period_data['MA ' + column] = period_data[column].rolling(moving_average).mean()
        if year:
            period_data = self.compare_year_months(year, interval)
            period_data.set_index('Month', inplace=True)
            if comparison_value:
                period_data = period_data.filter(like=comparison_value)
                if moving_average:
                    period_data['Moving Average'] = period_data[comparison_value].rolling(moving_average).mean()
        if plot_type == 'li':
            period_data.plot(**kwargs)
        if plot_type == 'h':
            period_data.hist(**kwargs)
        if plot_type == 'bw':
            period_data.boxplot(**kwargs)
        if plot_type == 'sda':
            result = seasonal_decompose(period_data.filter(like=comparison_value), model='additive', freq=1)
            result.plot()
        if plot_type == 'sdm':
            result = seasonal_decompose(period_data.filter(like=comparison_value), model='multiplicative', freq=1)
            result.plot()
        plt.show()

    def add_indicators(self, interval, indicators, significant_value='Close', year=None, day_timing='12:30',
                       rsi_period=14, si_period=14, bb_period=20, kn_period=9, kj_period=26, fast_ema=12,
                       slow_ema=26, signal_line=9, adx_period=14):
        """
        Add Forex technical indicators for the specified interval.
        Args:
            interval:
                'M' + Minute interval(int 1 - 60) ex: M15 --> 15 minute interval.
                'H' + Hour interval(int 1  - 24) ex: H4 --> 4 hour interval.
                'D' + Day interval(int 1 - 31) ex: D1 --> 1 day interval.
                'W' + Week interval(int 1 - 4).
            indicators: A list of Technical indicators including:
                - 'rsi': Relative strength indicator(RSI).
                - 'si': Stochastic oscillator indicator(SI).
                - 'bb': Bollinger Bands indicator.
                - 'ic': Ichimoku Cloud.
                - 'macd': Moving Average Convergence Divergence(MACD).
                - 'pp': Pivot Point.
                - 'adx': Average Directional movement(ADX)
            significant_value: Close - Open - High - Low.
            year: If year, interval of the year will be returned.
            day_timing: Timing of the day to get intervals.
            rsi_period: Period of averaging for the relative strength(RSI) indicator.
            si_period: Period of averaging for the stochastic indicator(SI).
            bb_period: Period of averaging for the Bollinger Bands indicator.
            kn_period: Period of averaging for the Ichimoku Cloud indicator(Kenkan-Sen).
            kj_period: Period of averaging for the Ichimoku Cloud indicator(Kijun Sen).
            fast_ema: Period of exponential moving average(12 days)
            slow_ema: Period of exponential moving average(26 days)
            signal_line: MACD signal line.
            adx_period: Average True Range(ATR) period.
        Return:
             Data frame with the adjusted technical indicators.
        """
        period_data = self.get_interval(interval, year, day_timing).reset_index(drop=True)
        if 'rsi' in indicators:
            period_data['Change'] = period_data[significant_value] - period_data[significant_value].shift(1)
            period_data['Upward Movement'] = period_data['Change'].apply(lambda change: change if change > 0 else 0)
            period_data['Downward Movement'] = period_data['Change'].apply(lambda change: abs(change)
                                                                           if change < 0 else 0)
            period_data['Average Upward Movement'] = period_data['Upward Movement'].rolling(rsi_period).mean()
            period_data['Average Downward Movement'] = period_data['Downward Movement'].rolling(rsi_period).mean()
            period_data['Relative Strength(RS)'] = (period_data['Average Upward Movement']
                                                    / period_data['Average Downward Movement'])
            period_data['Relative Strength Index(RSI)'] = 100 - (100 / (1 + period_data['Relative Strength(RS)']))
        if 'si' in indicators:
            period_data['Lowest Low'] = period_data['Low'].rolling(si_period).min()
            period_data['Highest High'] = period_data['High'].rolling(si_period).max()
            period_data['Stochastic Oscillator Index'] = 100 * (period_data['Close'] - period_data['Lowest Low']) / (
                    period_data['Highest High'] - period_data['Lowest Low'])
        if 'bb' in indicators:
            period_data['Bollinger Middle Band'] = period_data[significant_value].rolling(bb_period).mean()
            period_data['Bollinger Standard Deviation'] = period_data[significant_value].rolling(bb_period).std()
            period_data['Upper Bollinger Band'] = period_data['Bollinger Middle Band'] + (
                2 * period_data['Bollinger Standard Deviation'])
            period_data['Lower Bollinger Band'] = period_data['Bollinger Middle Band'] - (
                2 * period_data['Bollinger Standard Deviation'])
        if 'ic' in indicators:
            period_data['Lowest Low(Kenkan-Sen)'] = period_data['Low'].rolling(kn_period).min()
            period_data['Highest High(Kenkan-Sen)'] = period_data['High'].rolling(kn_period).max()
            period_data['Kenkan-Sen'] = (
                    period_data['Highest High(Kenkan-Sen)'] + period_data['Lowest Low(Kenkan-Sen)']) / 2
            period_data['Lowest Low(Kijun Sen)'] = period_data['Low'].rolling(kj_period).min()
            period_data['Highest High(Kijun Sen)'] = period_data['High'].rolling(kj_period).max()
            period_data['Kijun-Sen'] = (
                period_data['Highest High(Kijun Sen)'] + period_data['Lowest Low(Kijun Sen)']) / 2
            period_data['Chikou-Span'] = period_data[significant_value].shift(-kj_period)
            period_data['Senkou(Span A)'] = ((period_data['Kenkan-Sen'] + period_data['Kijun-Sen']) / 2).shift(
                kj_period)
            period_data['Lowest Low(52)'] = period_data['Low'].rolling(2 * kj_period).min()
            period_data['Highest High(52)'] = period_data['High'].rolling(2 * kj_period).max()
            period_data['Senkou(Span B)'] = ((period_data['Lowest Low(52)'] + period_data['Highest High(52)']) / 2
                                             ).shift(kj_period)
        if 'macd' in indicators:
            period_data['Fast EMA'] = period_data[significant_value].rolling(fast_ema).mean()
            period_data['Slow EMA'] = period_data[significant_value].rolling(slow_ema).mean()
            period_data['MACD'] = period_data['Fast EMA'] - period_data['Slow EMA']
            period_data['MACD Signal'] = period_data['MACD'].rolling(signal_line).mean()
            period_data['MACD Histogram'] = period_data['MACD'] - period_data['MACD Signal']
        if 'pp' in indicators:
            period_data['Pivot point'] = (period_data['High'] + period_data['Low'] + period_data['Close']) / 3
            period_data['First Resistance(R1)'] = (2 * period_data['Pivot point']) - period_data['Low']
            period_data['First Support(S1)'] = (2 * period_data['Pivot point']) - period_data['High']
            period_data['Second Resistance(R2)'] = period_data['Pivot point'] + period_data[
                'High'] - period_data['Low']
            period_data['Second Support(S2)'] = period_data['Pivot point'] - (period_data['High'] - period_data['Low'])
            period_data['Third Resistance(R3)'] = period_data['High'] + 2 * (
                    period_data['Pivot point'] - period_data['Low'])
            period_data['Third Support(S3)'] = period_data['Low'] - 2 * (
                    period_data['High'] - period_data['Pivot point'])
        if 'adx' in indicators:
            period_data['High-Low Difference'] = period_data['High'] - period_data['Low']
            period_data['High-Previous Close Difference'] = period_data['High'] - period_data['Close'].shift(1)
            period_data['Previous Close and Current Low Difference'] = period_data['Close'].shift(1) - period_data[
                'Low']
            period_data['True Range(TR)'] = period_data[['High-Low Difference', 'High-Previous Close Difference',
                                                        'Previous Close and Current Low Difference']].dropna().max(
                axis=1)
            period_data['Average True Range(ATR)'] = period_data['True Range(TR)'].rolling(adx_period).mean()
            period_data['High-Previous High Difference'] = period_data['High'] - period_data['High'].shift(1)
            period_data['Previous Low-Low Difference'] = period_data['Low'].shift(1) - period_data['Low']
            positive_condition = period_data[
                                     'High-Previous High Difference'] > period_data['Previous Low-Low Difference']
            period_data['Positive DX'] = period_data['High-Previous High Difference'][positive_condition]
            period_data['Positive DX'] = period_data['Positive DX'].fillna(0)
            negative_condition = period_data[
                                     'High-Previous High Difference'] < period_data['Previous Low-Low Difference']
            period_data['Negative DX'] = period_data['Previous Low-Low Difference'][negative_condition]
            period_data['Negative DX'] = period_data['Negative DX'].fillna(0)
            period_data['Smooth Positive DX'] = period_data['Positive DX'].rolling(adx_period).mean()
            period_data['Smooth Negative DX'] = period_data['Negative DX'].rolling(adx_period).mean()
            period_data['Positive Directional Movement Index(+DMI)'] = (
                    period_data['Smooth Positive DX'] / period_data['Average True Range(ATR)']) * 100
            period_data['Negative Directional Movement Index(-DMI)'] = (
                    period_data['Smooth Negative DX'] / period_data['Average True Range(ATR)']) * 100
            period_data['Directional Index(DX)'] = abs(
                period_data['Positive Directional Movement Index(+DMI)'] -
                period_data['Negative Directional Movement Index(-DMI)']) / (
                period_data[['Positive Directional Movement Index(+DMI)', 'Negative Directional Movement Index(-DMI)']]
            ).sum(axis=1) * 100
            period_data['Average Directional Index(ADX)'] = period_data['Directional Index(DX)'].rolling(
                adx_period).mean()
        return period_data


if __name__ == '__main__':
    test_years = [year for year in range(2017, 2019)]
    x = FxSimulator('eurusd', test_years)
    x.plot_initial_data('li', 'D1', comparison_value='Close')
    plt.show()

pairs.txt:

eurusd
eurchf
eurgbp
eurjpy
euraud
usdcad
usdchf
usdjpy
usdmxn
gbpchf
gbpjpy
gbpusd
audjpy
audusd
chfjpy
nzdjpy
nzdusd
xauusd
eurcad
audcad
cadjpy
eurnzd
grxeur
nzdcad
sgdjpy
usdhkd
usdnok
usdtry
xauaud
audchf
auxaud
eurhuf
eurpln
frxeur
hkxhkd
nzdchf
spxusd
usdhuf
usdpln
usdzar
xauchf
zarjpy
bcousd
etxeur
eurczk
eursek
gbpaud
gbpnzd
jpxjpy
udxusd
usdczk
usdsek
wtiusd
xaueur
audnzd
cadchf
eurdkk
eurnok
eurtry
gbpcad
nsxusd
ukxgbp
usddkk
usdsgd
xagusd
xaugbp
\$\endgroup\$
  • \$\begingroup\$ Please don't add things like [feedback needed] to a post's title. Most, if not all, 'unanswered' questions are looking for feedback, if everyone added stuff like that to titles we'd have nonsense like "[python][feedback needed][begginer][help][on-topic][performance]... my program". Makes me think of YouTube tags. There are mechanisms in place to increase the visibility of a post, such as bounties. \$\endgroup\$ – Peilonrayz Feb 5 at 20:22
  • \$\begingroup\$ @Peilonrayz I'll be more appreciative if you look into any of my unanswered questions and maybe try to provide some feedback \$\endgroup\$ – bullseye Feb 5 at 20:42
  • \$\begingroup\$ I'd appreciate it if someone asking me for help, where I get nothing in return, weren't sassy. I may not be able to point to the reason why, but I think I'll take a pass on your posts. \$\endgroup\$ – Peilonrayz Feb 5 at 20:45
  • \$\begingroup\$ To be clear I won't be looking at your posts. \$\endgroup\$ – Peilonrayz Feb 6 at 17:17
1
\$\begingroup\$

What about your

program structure, and general suggestions for improvements/optimizations/features

I found True/False Evaluations and docString weakness and suggest you follow some code style guide like Google. Also, you can call pylint on each .py files, like I was execute on your forex_data_handler.py pylint forex_data_handler.py, and get the next output:

************* Module forex_data_handler
forex_data_handler.py:33:0: C0301: Line too long (115/100) (line-too-long)
forex_data_handler.py:54:0: C0301: Line too long (108/100) (line-too-long)
forex_data_handler.py:61:0: C0301: Line too long (117/100) (line-too-long)
forex_data_handler.py:62:0: C0301: Line too long (114/100) (line-too-long)
forex_data_handler.py:63:0: C0301: Line too long (120/100) (line-too-long)
forex_data_handler.py:82:0: C0304: Final newline missing (missing-final-newline)
forex_data_handler.py:1:0: C0114: Missing module docstring (missing-module-docstring)
forex_data_handler.py:1:0: E0401: Unable to import 'histdata' (import-error)
forex_data_handler.py:7:0: R0913: Too many arguments (6/5) (too-many-arguments)
forex_data_handler.py:20:8: R1705: Unnecessary "else" after "return" (no-else-return)
forex_data_handler.py:7:0: R1710: Either all return statements in a function should return an expression, or none of them should. (inconsistent-return-statements)
forex_data_handler.py:33:0: R0913: Too many arguments (9/5) (too-many-arguments)
forex_data_handler.py:33:0: R0914: Too many local variables (16/15) (too-many-locals)
forex_data_handler.py:77:4: C0103: Constant name "start_time" doesn't conform to UPPER_CASE naming style (invalid-name)
forex_data_handler.py:81:4: C0103: Constant name "end_time" doesn't conform to UPPER_CASE naming style (invalid-name)
forex_data_handler.py:2:0: C0411: standard import "from concurrent.futures import ThreadPoolExecutor, as_completed" should be placed before "from histdata import download_hist_data" (wrong-import-order)
forex_data_handler.py:3:0: C0411: standard import "from time import perf_counter" should be placed before "from histdata import download_hist_data" (wrong-import-order)
forex_data_handler.py:4:0: C0411: standard import "import os" should be placed before "from histdata import download_hist_data" (wrong-import-order)

------------------------------------------------------------------
Your code has been rated at 4.76/10 (previous run: 8.81/10, -4.05)
```
\$\endgroup\$
  • \$\begingroup\$ Answers should provide more in-depth reviews. This answer is however acceptable, because it does provide a review. \$\endgroup\$ – FreezePhoenix Jan 3 at 13:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.