Overall Statistics
Total Orders
5144
Average Win
0.12%
Average Loss
-0.08%
Compounding Annual Return
47.129%
Drawdown
29.900%
Expectancy
0.580
Start Equity
10000000
End Equity
31848814.56
Net Profit
218.488%
Sharpe Ratio
0.907
Sortino Ratio
1.384
Probabilistic Sharpe Ratio
26.931%
Loss Rate
35%
Win Rate
65%
Profit-Loss Ratio
1.43
Alpha
0
Beta
0
Annual Standard Deviation
0.433
Annual Variance
0.188
Information Ratio
0.943
Tracking Error
0.433
Treynor Ratio
0
Total Fees
$532163.90
Estimated Strategy Capacity
$9000.00
Lowest Capacity Asset
KMB R735QTJ8XC9X
Portfolio Turnover
4.29%
Drawdown Recovery
280
# region imports
from AlgorithmImports import *
# endregion

def get_r_o_a_score(fundamental):
    '''Get the Profitability - Return of Asset sub-score of Piotroski F-Score'''
    # Nearest ROA as current year data
    roa = fundamental.operation_ratios.ROA.three_months
    # 1 score if ROA datum exists and positive, else 0
    return int(roa and roa > 0)

def get_operating_cash_flow_score(fundamental):
    '''Get the Profitability - Operating Cash Flow sub-score of Piotroski F-Score'''
    # Nearest Operating Cash Flow as current year data
    operating_cashflow = fundamental.financial_statements.cash_flow_statement.cash_flow_from_continuing_operating_activities.three_months
    # 1 score if operating cash flow datum exists and positive, else 0
    return int(operating_cashflow and operating_cashflow > 0)

def get_r_o_a_change_score(fundamental):
    '''Get the Profitability - Change in Return of Assets sub-score of Piotroski F-Score'''
    # if current or previous year's ROA data does not exist, return 0 score
    roa = fundamental.operation_ratios.ROA
    if not roa.three_months or not roa.one_year:
        return 0
    # 1 score if change in ROA positive, else 0 score
    return int(roa.three_months > roa.one_year)

def get_accruals_score(fundamental):
    '''Get the Profitability - Accruals sub-score of Piotroski F-Score'''
    # Nearest Operating Cash Flow, Total Assets, ROA as current year data
    operating_cashflow = fundamental.financial_statements.cash_flow_statement.cash_flow_from_continuing_operating_activities.three_months
    total_assets = fundamental.financial_statements.balance_sheet.total_assets.three_months
    roa = fundamental.operation_ratios.ROA.three_months
    # 1 score if operating cash flow, total assets and ROA exists, and operating cash flow / total assets > ROA, else 0
    return int(operating_cashflow and total_assets and roa and operating_cashflow / total_assets > roa)

def get_leverage_score(fundamental):
    '''Get the Leverage, Liquidity and Source of Funds - Change in Leverage sub-score of Piotroski F-Score'''
    # if current or previous year's long term debt to equity ratio data does not exist, return 0 score
    long_term_debt_ratio = fundamental.operation_ratios.long_term_debt_equity_ratio
    if not long_term_debt_ratio.three_months or not long_term_debt_ratio.one_year:
        return 0
    # 1 score if long term debt ratio is lower in the current year, else 0 score
    return int(long_term_debt_ratio.three_months < long_term_debt_ratio.one_year)

def get_liquidity_score(fundamental):
    '''Get the Leverage, Liquidity and Source of Funds - Change in Liquidity sub-score of Piotroski F-Score'''
    # if current or previous year's current ratio data does not exist, return 0 score
    current_ratio = fundamental.operation_ratios.current_ratio
    if not current_ratio.three_months or not current_ratio.one_year:
        return 0
    # 1 score if current ratio is higher in the current year, else 0 score
    return int(current_ratio.three_months > current_ratio.one_year)

def get_share_issued_score(fundamental):
    '''Get the Leverage, Liquidity and Source of Funds - Change in Number of Shares sub-score of Piotroski F-Score'''
    # if current or previous year's issued shares data does not exist, return 0 score
    shares_issued = fundamental.financial_statements.balance_sheet.share_issued
    if not shares_issued.three_months or not shares_issued.twelve_months:
        return 0
    # 1 score if shares issued did not increase in the current year, else 0 score
    return int(shares_issued.three_months <= shares_issued.twelve_months)

def get_gross_margin_score(fundamental):
    '''Get the Leverage, Liquidity and Source of Funds - Change in Gross Margin sub-score of Piotroski F-Score'''
    # if current or previous year's gross margin data does not exist, return 0 score
    gross_margin = fundamental.operation_ratios.gross_margin
    if not gross_margin.three_months or not gross_margin.one_year:
        return 0
    # 1 score if gross margin is higher in the current year, else 0 score
    return int(gross_margin.three_months > gross_margin.one_year)

def get_asset_turnover_score(fundamental):
    '''Get the Leverage, Liquidity and Source of Funds - Change in Asset Turnover Ratio sub-score of Piotroski F-Score'''
    # if current or previous year's asset turnover data does not exist, return 0 score
    asset_turnover = fundamental.operation_ratios.assets_turnover
    if not asset_turnover.three_months or not asset_turnover.one_year:
        return 0
    # 1 score if asset turnover is higher in the current year, else 0 score
    return int(asset_turnover.three_months > asset_turnover.one_year)
# region imports
from AlgorithmImports import *
from universe import FScoreUniverseSelectionModel
# endregion


class PensiveFluorescentYellowParrot(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2020, 7, 1)
        self.set_end_date(2023, 7, 1)
        self.set_cash(10_000_000)
        self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.MARGIN)
        self.add_universe_selection(FScoreUniverseSelectionModel(self.get_parameter("fscore_threshold", 7)))
        self.add_alpha(ConstantAlphaModel(InsightType.PRICE, InsightDirection.UP, timedelta(1)))
        self.set_portfolio_construction(SectorWeightingPortfolioConstructionModel())
        self.set_execution(SpreadExecutionModel(0.01))   # maximum 1% spread allowed
# region imports
from AlgorithmImports import *
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel

from f_score import *
# endregion


class FScoreUniverseSelectionModel(FundamentalUniverseSelectionModel):

    def __init__(self, fscore_threshold):
        super().__init__(self._select_assets)
        self._fscore_threshold = fscore_threshold

    def _select_assets(self, fundamentals):
        '''Defines the fundamental selection function.
        Args:
            fundamentals: The fundamental data used to perform filtering
        Returns:
            An enumerable of symbols passing the filter'''
        # We use a dictionary to hold the F-Score of each stock
        f_scores = {
            f.symbol: (
                get_r_o_a_score(f)
                + get_operating_cash_flow_score(f)
                + get_r_o_a_change_score(f)
                + get_accruals_score(f)
                + get_leverage_score(f)
                + get_liquidity_score(f)
                + get_share_issued_score(f)
                + get_gross_margin_score(f)
                + get_asset_turnover_score(f)
            ) for f in fundamentals
            # We only want stocks with fundamental data and price > $1
            if f.has_fundamental_data and f.price > 1
        }
        # Select the stocks with F-Score higher than the threshold
        return [symbol for symbol, fscore in f_scores.items() if fscore >= self._fscore_threshold]