1
\$\begingroup\$

Function

This class extends the key class, called EQ, and does basic calculation based on minutely data. It then calculates weight coefficients (e.g., [0, 1]) for sectors of equity exchange markets using data from market movers (e.g., AAPL, GOOG, AMZN, NVDA, FB).

It works, I'm not so sure about its OOP.

Would you be so kind and review it?

SectorCalculations

class SectorCalculations extends EQ implements ConfigConstants
{

    /**
     *
     * @var an array of sector movers
     */
    static $sector_movers;

    /**
     *
     * @var a string of current time
     */
    static $current_time;

    /**
     *
     * @var a string of current time
     */
    static $database_file_permission = 0755;

    public static function calculateSectorCoeffs()
    {
        $base_url_api = self::BASE_URL_API . self::TARGET_QUERY_API;

        $index_data = array("Overall" => array("sector_weight" => 1, "sector_coefficient" => 1, "sector_value" => 0));
        foreach (self::getSectorMovers() as $sector_mover) {
            $sector_url_api = $base_url_api . implode("%2C", array_keys($sector_mover["selected_tickers"])) . "&types=quote&range=1m";
            $raw_sector_json = file_get_contents($sector_url_api);

            if (ConfigConstants::WRITING_INDICES_SECTOR_DATA_ON_DATABASE == true) {
                self::writeIndicesOnDatabase($sector_mover["directory"], $raw_sector_json);
            }

            $raw_sector_array = json_decode($raw_sector_json, true);

            // Calculate the real-time index
            $index_value = 0;
            foreach ($raw_sector_array as $ticker => $ticker_stats) {
                if (isset($sector_mover["selected_tickers"][$ticker], $ticker_stats["quote"], $ticker_stats["quote"]["extendedChangePercent"], $ticker_stats["quote"]["changePercent"], $ticker_stats["quote"]["ytdChange"])) {

                    $change_amount = ($ticker_stats["quote"]["extendedChangePercent"] + $ticker_stats["quote"]["changePercent"] + $ticker_stats["quote"]["ytdChange"]) / 200;
                    $index_value += $sector_mover["sector_weight"] * $sector_mover["selected_tickers"][$ticker] * $change_amount;
                }
            }

            $index_data[$sector_mover["sector"]] = array("sector_weight" => $sector_mover["sector_weight"], "sector_coefficient" => $sector_mover["sector_coefficient"], "sector_value" => $index_value);
            $index_data["Overall"]["sector_value"] += $index_data[$sector_mover["sector"]]["sector_value"];
        }

        // Calculate the index factor for better visibility between -1 and +1
        $front_index_data = array();
        foreach ($index_data as $sector_name => $sector_index_data) {

            $index_sign = abs($sector_index_data["sector_value"]);
            $index_factor = 1;
            for ($i = 0; $i <= 10; $i++) {
                $index_factor = pow(10, $i);
                if (($index_factor * $index_sign) > 1) {
                    $index_factor = pow(10, $i - 1);
                    break;
                }
            }

            $front_index_data[$sector_name] = $sector_index_data["sector_weight"] * $sector_index_data["sector_coefficient"] * $sector_index_data["sector_value"] * $index_factor;
        }

        $index_sector_file = self::writeFinalJSONforSectorCoeffsOnDatabase();

        self::copyFinalSectorCoeffsToFrontDirectory();

        if (EQ::isLocalServer()) {echo "YAAAY! " . __METHOD__ . " updated sector coefficients successfully 💚!\n";}

        return $front_index_data;
    }

    public static function copyFinalSectorCoeffsToFrontDirectory()
    {
        $sector_dir = __DIR__ . self::LIVE_DATA_DIR_APP;
        if (!is_dir($sector_dir)) {mkdir($sector_dir, self::getDatabaseFilePermission(), true);} // if data directory did not exist

        // if s-1 file did not exist
        if (!file_exists($sector_dir . self::DIR_FRONT_SECTOR_COEF_FILENAME)) {
            $handle = fopen($sector_dir . self::DIR_FRONT_SECTOR_COEF_FILENAME, "wb");
            fwrite($handle, "d");
            fclose($handle);
        }

        $sector_coef_file = $sector_dir . self::DIR_FRONT_SECTOR_COEF_FILENAME;
        copy($index_sector_file, $sector_coef_file);
    }

    public static function writeFinalJSONforSectorCoeffsOnDatabase()
    {
        // Write the index file
        $index_sector_dir = __DIR__ . self::INDEX_SECTOR_DIR_PREFIX_APP;

        if (!is_dir($index_sector_dir)) {mkdir($index_sector_dir, self::getDatabaseFilePermission(), true);}

        $index_sector_file = $index_sector_dir . self::getCurrentTime() . ConfigConstants::EXTENSION_JSON;

        $index_sector_json = json_encode($front_index_data, JSON_FORCE_OBJECT);
        $file_handle = fopen($index_sector_file, "a+");
        fwrite($file_handle, $index_sector_json);
        fclose($file_handle);

        return $index_sector_file;
    }

    public static function writeIndicesOnDatabase($sector_mover_database, $raw_sector_json)
    {

        // Write the raw file in the back directories

        $rawSectorDir = __DIR__ . self::EACH_SECTOR_DIR_PREFIX_APP . $sector_mover_database;

        // if back directory not exist
        if (!is_dir($rawSectorDir)) {mkdir($rawSectorDir, self::getDatabaseFilePermission(), true);}

        // create and open/write/close sector data to back directories
        $rawSectorFile = $rawSectorDir . ConfigConstants::SLASH . self::getCurrentTime() . ConfigConstants::EXTENSION_JSON;
        $file_handle = fopen($rawSectorFile, "a+");
        fwrite($file_handle, $raw_sector_json);
        fclose($file_handle);

    }

    /**
     * @return a part of class object
     */
    public static function getSectorMovers()
    {
        return ConfigConstants::SECTOR_MOVERS_COEFFS;
    }

    /**
     * @return a part of class object
     */
    public static function getCurrentTime()
    {
        return date("Y-m-d-H-i-s");
    }

    /**
     * @return a part of class object
     */
    public static function getDatabaseFilePermission()
    {
        return ConfigConstants::DATABASE_FILE_PERMISSION;
    }

}

json_encode($index_data)

{"Overall":{"sector_weight":1,"sector_coefficient":1,"sector_value":0.0009989048910975919},"IT":{"sector_weight":0.18,"sector_coefficient":4,"sector_value":0.0002908602069845678},"Telecommunication":{"sector_weight":0.12,"sector_coefficient":4,"sector_value":8.05578227057372e-5},"Finance":{"sector_weight":0.1,"sector_coefficient":6,"sector_value":9.958073135395748e-5},"Energy":{"sector_weight":0.1,"sector_coefficient":6,"sector_value":8.529905787700634e-5},"Industrials":{"sector_weight":0.08,"sector_coefficient":8,"sector_value":8.477913513820296e-5},"Materials and Chemicals":{"sector_weight":0.08,"sector_coefficient":8,"sector_value":3.149808669009844e-5},"Utilities":{"sector_weight":0.08,"sector_coefficient":8,"sector_value":5.1996127293426994e-5},"Consumer Discretionary":{"sector_weight":0.08,"sector_coefficient":8,"sector_value":9.118133207158268e-5},"Consumer Staples":{"sector_weight":0.06,"sector_coefficient":8,"sector_value":7.264485191327639e-5},"Defense":{"sector_weight":0.04,"sector_coefficient":10,"sector_value":4.648512175199498e-5},"Health":{"sector_weight":0.04,"sector_coefficient":10,"sector_value":1.474285957230441e-5},"Real Estate":{"sector_weight":0.04,"sector_coefficient":10,"sector_value":4.927955774543618e-5}}

Final Sector Coefficients

Sector Coefficients could be between (0, 1):

{"Overall":0.9989048910975918,"IT":0.2094193490288888,"Telecommunication":0.38667754898753853,"Finance":0.5974843881237449,"Energy":0.5117943472620381,"Industrials":0.542586464884499,"Materials and Chemicals":0.20158775481663005,"Utilities":0.33277521467793275,"Consumer Discretionary":0.5835605252581292,"Consumer Staples":0.3486952891837267,"Defense":0.18594048700797994,"Health":0.058971438289217644,"Real Estate":0.19711823098174472}

$ticker_stats sample

Quote array can be viewed in this link:

{
  "symbol": "AAPL",
  "companyName": "Apple Inc.",
  "primaryExchange": "Nasdaq Global Select",
  "sector": "Technology",
  "calculationPrice": "tops",
  "open": 154,
  "openTime": 1506605400394,
  "close": 153.28,
  "closeTime": 1506605400394,
  "high": 154.80,
  "low": 153.25,
  "latestPrice": 158.73,
  "latestSource": "Previous close",
  "latestTime": "September 19, 2017",
  "latestUpdate": 1505779200000,
  "latestVolume": 20567140,
  "iexRealtimePrice": 158.71,
  "iexRealtimeSize": 100,
  "iexLastUpdated": 1505851198059,
  "delayedPrice": 158.71,
  "delayedPriceTime": 1505854782437,
  "extendedPrice": 159.21,
  "extendedChange": -1.68,
  "extendedChangePercent": -0.0125,
  "extendedPriceTime": 1527082200361,
  "previousClose": 158.73,
  "change": -1.67,
  "changePercent": -0.01158,
  "iexMarketPercent": 0.00948,
  "iexVolume": 82451,
  "avgTotalVolume": 29623234,
  "iexBidPrice": 153.01,
  "iexBidSize": 100,
  "iexAskPrice": 158.66,
  "iexAskSize": 100,
  "marketCap": 751627174400,
  "peRatio": 16.86,
  "week52High": 159.65,
  "week52Low": 93.63,
  "ytdChange": 0.3665,
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Please keep in mind that ordinarily the code within questions should not be changed after reviews have arrived. Since in this instance it's on the only answerer's request we'll allow it, but please take a look at what you may and may not do after receiving answers. \$\endgroup\$ – Mast May 9 at 18:15
2
\$\begingroup\$

I don't see a great deal to refine with this snippet -- good work.

  • $ticker_stats["quote"], can be safely omitted from the isset() call because the subsequent checks on its subarrays will do the necessary work. Good work supplying multiple parameters to a single isset() call.

  • I may be able to refine the following section, but would need realistic sample input to be sure.

    foreach ($raw_sector_array as $ticker => $ticker_stats) {
        if (isset($sector_mover["selected_tickers"][$ticker], $ticker_stats["quote"], $ticker_stats["quote"]["extendedChangePercent"], $ticker_stats["quote"]["changePercent"], $ticker_stats["quote"]["ytdChange"])) {
    
            $change_amount = ($ticker_stats["quote"]["extendedChangePercent"] + $ticker_stats["quote"]["changePercent"] + $ticker_stats["quote"]["ytdChange"]) / 200;
            $index_value += $sector_mover["sector_weight"] * $sector_mover["selected_tickers"][$ticker] * $change_amount;
        }
    }
    
    $index_data[$sector_mover["sector"]] = array("sector_weight" => $sector_mover["sector_weight"], "sector_coefficient" => $sector_mover["sector_coefficient"], "sector_value" => $index_value);
    $index_data["Overall"]["sector_value"] += $index_data[$sector_mover["sector"]]["sector_value"];
    
  • Rather than performing iterated "guess & check" arithmetic operations to determine the $index_factor, I think a non-iterative string check should be more direct/efficient. You might write an implementation of Barmar's solution: PHP - Find the number of zeros in a decimal number or a regex based approach: (though I'll admit it is a little challenging to interpret at a glance)

    $float = abs($float);
    $factor = pow(10, preg_match_all('~(?:^0?\.|\G(?!^))0~', $float)))
    

    https://regex101.com/r/SmlRp5/3

    If the above doesn't work for all of your cases (like $float = 0) you can write a earlier condition to shortcut a precise 0 to not receive a factor - but I reckon that this is not an expected case.

    Hmm, on second thought, there's greater stability to be had from using pure arithmetic. String-based processes are vulnerable to challenges dealing with scientific notation.

    I have added a new answer to the earlier mentioned StackOverflow page that Barmar answered.

  • For consistency, write all if blocks over multiple lines. The reduction in lines is not worth the reduction in readability.


Some late advice...

$index_data["Overall"]["sector_value"] += $index_data[$sector_mover["sector"]]["sector_value"];

Is easier to read as:

$index_data["Overall"]["sector_value"] += $index_value;
\$\endgroup\$
  • 1
    \$\begingroup\$ @Emma On second thought, the more I research my advice on finding the $factor using string techniques, the more I am worried about running into conflicts with scientific notation. Do you have some sample floats that I can run some tests on? \$\endgroup\$ – mickmackusa May 3 at 10:01
  • 2
    \$\begingroup\$ May I see what associative elements are in a single $ticker_stats? More specifically, which keys are in the quote subarray. \$\endgroup\$ – mickmackusa May 9 at 7:53

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.