/satan/r_test.php
PHP | 703 lines | 367 code | 124 blank | 212 comment | 17 complexity | c0ba29524862d684b0067b609a2dd90a MD5 | raw file
- <?php
- /*
- * BACKGROUND:
- * There is a black box trading system. In 2008, the system was fully functional. A statistician
- * used SAS software to optimize the variable values that the system uses. Since 2008, the system
- * has been rewritten several times. New features and variables have been added, but the original
- * variables and basic functionality remain the same. The previous statisical analysis determined
- * the best way to optimize the variables. The analysis seems correct. New random tests were run
- * and the results matched what the statistician predicted. Now that the system has new features,
- * a new statistical analysis needs to be performed. That will help determine the best values for
- * the newly added variables. Also, more test data will be used. The goal is to use R scripts to
- * optimize the variable values. Moreover, since there are more variables (and 'too many' values)
- * R will have to guess the optimal values since brute force testing is not feasible. Ultimately,
- * the goal is to design the R scripts so that statistical analysis tests can be run automatically
- * in the future every 6 months.
- *
- * This r_test.php file is designed to mimic the black box system. Optimal variable values can be
- * manually set, and then tests can be run to make sure the R script successfully guesses the best
- * values for each variable. The "goodness value" that shows how the black box system performed is
- * stored in the database in a column named "rank".
- *
- *
- * INSTRUCTIONS:
- * . Review the previous statistical analysis at http://caLLLendar.com/pub/analysis/
- * The previous statistical analysis describes the various methods used to determine the optimal
- * variable values. It also lists the important variables that have the most influence. Please
- * use it as a guide when creating the new R scripts. Also, note the behavior of the variables.
- *
- * . Review the PHP code below.
- * The purpose of this r_test.php file is to mimic the behavior of the black box system. Briefly
- * review the code in the score() method below. The score() method is used to calculate a "rank"
- * value based on the variable values.
- *
- * . Make recommendations for modifying r_test.php
- * r_test.php is designed to mimic the black box trading system. Please determine if the score is
- * calculated in such a way as to effectively mimic the system used in the previous analysis. It
- * may be important to have the individual variables perform in the same way they did before. If
- * so, please recommend changes that should be made to make r_test.php more authentic.
- *
- * Variable relationships
- *
- * 3. Write the R script that will optimize the variables
- * Use the previous statistical analysis (http://caLLLendar.com/pub/analysis/ ) as a guide. The
- * analisys was successful in predicting the performance of the variable settings that it chose.
- * Note: The idea is that the optimal values in r_test.php can be manually set. Then, the R code
- * will be run and it will be clear if it is able to find those manually set optimal values.
- *
- * Once the R script is integrated into the php below, r_test.php will be called.
- *
- * Here is the process.
- * . r_test is called
- * . The database and tables are set up. Default values are added to the `settings` table.
- * . The R script (hereafter called "R") is called.
- * . R will find an empty `backtests` table.
- * . R will guess the variable settings to test.
- * . R will update the `settings` table with the variable values that should be tested.
- * . r_test will create rows in the `backtests` tables.
- * . r_test will mimic the black box system by calculating fake `rank` values.
- * . r_test repeats / loops.
- * . R is called again and finds a `backtest` table that includes results.
- * . R will guess what values to test based on the `rank` column in the `backtests` table.
- * . R will determine if the variables are sufficiently optimized.
- * . If R thinks the testing process is complete, it will update the `settings` table.
- * . r_test will stop looping.
- *
- * Here are the goals:
- * . Find the optimal values.
- * . Sharpe ratio
- * . Minimize / Maximize values
- * . How much money is needed (Kelly Criterion), how many times to profit?
- * . http://calllendar.com/pub/backtest-optimization.txt
- *
- */
- // Run the R optimization test
- $r_test = new r_test();
- class r_test
- {
- private $sett, $parent_insertq, $child_insertq;
- public function __construct()
- {
- do {
- $this->setup_database();
- $this->setup_settings();
- $this->call_r();
- $this->setup_testresults();
- $this->setup_positions();
- $this->rank();
- $brake = $this->stop_testing();
- } while( $brake == 'no' );
- }
- private function stop_testing()
- {
- /*
- * CONCEPT:
- * test_r->stop_testing() stops the optimization process
- * after R records "brake = yes" in the settings table.
- *
- * CAUTION:
- */
- list( $brake ) = mysql_fetch_row( mysql_query("SELECT `value` FROM `settings`
- WHERE `name` = 'backtestbrake' " ));
- if( $brake === 'yes' )
- {
- echo "Optimization is complete. ";
- }
- return $brake;
- }
- private function setup_database()
- {
- /*
- * CONCEPT:
- * r_test->setup_database() sets up the database and tables needed for R.
- *
- * CAUTION:
- */
- // Connect to the mySQL server
- $connect = mysql_connect( 'localhost', 'root', 'OlV734A' )
- or die( "Unable to connect to server!" );
- // Connect to the database
- $select = mysql_select_db( 'r_test', $connect )
- or die( "Could not select database ( `r_test` )
- using [ localhost ] connection code" );
- // Create the database that corresponds with the account set in configure
- mysql_query( "CREATE DATABASE IF NOT EXISTS `r_test` " );
- // Create the `settings` table
- mysql_query( "CREATE TABLE `settings`
- (name char(24) NOT NULL,
- port int(3) NOT NULL,
- value char(64) NOT NULL,
- KEY `name` (`name`, `port`),
- KEY `port` (`port`,`name`) )
- ENGINE=InnoDB " );
- // Create the table that holds the test results
- mysql_query( "CREATE TABLE IF NOT EXISTS `backtests` ( " .
- // id to make it easy to update test results
- "`id` int(10) unsigned NOT NULL auto_increment, " .
- // godprofit - setprofit = rank
- "`rank` int(10) DEFAULT NULL, " .
- // The percentage profit (or loss) between the startcash and godresult
- "`godprofit` int(11) NOT NULL DEFAULT '0', " .
- // The percentage profit (or loss) between the startcash and setresult
- "`setprofit` int(11) NOT NULL DEFAULT '0', " .
- // The place to store the failure message
- "`fail` text NOT NULL, " .
- // The total amount of cash the client puts in the brokerage account before god runs.
- "`startcash` decimal(10,2) NOT NULL DEFAULT '0.00', " .
- // The actual amount of money the position ended with
- "`godresult` decimal(10,2) NOT NULL DEFAULT '0.00', " .
- // startdate is the day the symbols are seleted on. The test process walks forward in time from startdate.
- "`startdate` date NOT NULL DEFAULT '0000-00-00', " .
- // The number of days between the startdate and the last day of trading
- "`duration` int(5) NOT NULL DEFAULT '0', " .
- // enddate is startdate + duration (days) (plus any extra time backtest needs to sell off all positions)
- "`enddate` date NOT NULL DEFAULT '0000-00-00', " .
- // The lowest price a symbol can be on the startdate
- "`startquote` decimal(10,2) NOT NULL DEFAULT '0.00', " .
- // The position number of the test result
- "`position` int(3) NOT NULL DEFAULT '0', " .
- // Internal variable that may affect results
- "`selldegree` int(3) NOT NULL DEFAULT '0', " .
- // The broker commission schedule used for the test
- "`broker` varchar(3) NOT NULL DEFAULT '', " .
- // Use R to select symbols
- "`select_r` varchar(3) NOT NULL DEFAULT '', " .
- // The set of symbols picked by R (based on their price correlation)
- "`symbols` text NOT NULL, " .
- // The diversify=yes forces god to avoid buying a symbol for more than one position
- "`diversify` varchar(3) NOT NULL DEFAULT '', " .
- // The hold=yes temporarily prevents god from selling a symbol with a rising price
- "`hold` varchar(3) NOT NULL DEFAULT '', " .
- // The number of positions processed during the test
- "`positions` int(3) NOT NULL DEFAULT '0', " .
- // The number of symbols selected for the set
- "`quantity` int(3) NOT NULL DEFAULT '0', " .
- // The sharebuffer setting god uses solely
- "`sharebuffer` int(11) NOT NULL DEFAULT '0', " .
- // The number of transactions that god executed
- "`transactions` int(11) NOT NULL DEFAULT '0', " .
- // The max amount of overall profit before triggering a stop
- "`profitlimitpercent` int(3) NOT NULL DEFAULT '0', " .
- // The max amount of overall loss before triggering a stop
- "`stoplosspercentage` int(3) NOT NULL DEFAULT '0', " .
- // The max percentage profit after buying a symbol before triggering a stop
- "`buystop_percentage` int(3) NOT NULL DEFAULT '0', " .
- // The max amount of loss after buying a symbol before triggering a stop
- "`sellstoppercentage` int(3) NOT NULL DEFAULT '0', " .
- // The max amount of loss from a high price before triggering a stop
- "`trailingpercentage` int(3) NOT NULL DEFAULT '0', " .
- // The percentage of slippage before triggering a stop
- "`pricebufferpercent` int(3) NOT NULL DEFAULT '0', " .
- // Setting KEYS increased performance
- "KEY `id` (`id`), " .
- "KEY `rank` (`rank`), " .
- "KEY `godprofit` (`godprofit`), " .
- "KEY `setprofit` (`setprofit`), " .
- "KEY `startcash` (`startcash`), " .
- "KEY `godresult` (`godresult`), " .
- // fail key not needed
- // startdate key not needed
- "KEY `startquote` (`startquote`), " .
- "KEY `position` (`position`), " .
- "KEY `selldegree` (`selldegree`), " .
- "KEY `broker` (`broker`), " .
- "KEY `select_r` (`select_r`), " .
- // symbols key not needed
- "KEY `diversify` (`diversify`), " .
- "KEY `duration` (`duration`), " .
- // enddate key not needed
- "KEY `hold` (`hold`), " .
- "KEY `positions` (`positions`), " .
- "KEY `quantity` (`quantity`), " .
- "KEY `sharebuffer` (`sharebuffer`), " .
- "KEY `transactions` (`transactions`), " .
- "KEY `profitlimitpercent` (`profitlimitpercent`), " .
- "KEY `stoplosspercentage` (`stoplosspercentage`), " .
- "KEY `buystop_percentage` (`buystop_percentage`), " .
- "KEY `sellstoppercentage` (`sellstoppercentage`), " .
- "KEY `trailingpercentage` (`trailingpercentage`), " .
- "KEY `pricebufferpercent` (`pricebufferpercent`) ) ENGINE=InnoDB" );
- echo "Database and tables are setup<br> ";
- }
- private function setup_positions()
- {
- /*
- * CONCEPT:
- *
- * CAUTION:
- */
- // Delete the tests
- mysql_query("DELETE FROM `backtests` " );
- // Insert the basic tests
- for( $i = 1; $i <= 9; $i++ )
- {
- mysql_query("$this->parent_insertq");
- mysql_query("$this->child_insertq");
- mysql_query("UPDATE `backtests` SET `position` = '$i' WHERE `position` = '' ");
- }
- }
- private function setup_testresults()
- {
- /*
- * CONCEPT:
- *
- * CAUTION:
- */
- $settingsq = mysql_query("SELECT name,value FROM `settings` ");
- $testarray = array();
- while( list( $name, $value ) = mysql_fetch_array( $settingsq ) )
- {
- $$name = $value;
- }
- // Make the parent
- $this->parent_insertq = "INSERT INTO `backtests` SET
- `startcash` = '1000',
- `startdate` = '2012-03-17',
- `duration` = '360',
- `startquote` = '1.00',
- `broker` = '0',
- `select_r` = 'yes',
- `symbols` = 'ASTC,RHAT',
- `diversify` = 'no',
- `hold` = 'no',
- `positions` = '9',
- `quantity` = '24',
- `sharebuffer` = '0',
- `profitlimitpercent` = '10000',
- `stoplosspercentage` = '1',
- `buystop_percentage` = '1000',
- `sellstoppercentage` = '1',
- `trailingpercentage` = '1',
- `pricebufferpercent` = '0' ";
- // Make a child
- $this->child_insertq = "INSERT INTO `backtests` SET
- `startcash` = '$startcash',
- `startdate` = '2012-03-17',
- `duration` = '$duration',
- `startquote` = '$startquote',
- `broker` = '$broker',
- `select_r` = '$select_r',
- `symbols` = 'ASTC,RHAT',
- `diversify` = '$diversify',
- `hold` = '$hold',
- `positions` = '9',
- `quantity` = '$quantity',
- `sharebuffer` = '$sharebuffer',
- `profitlimitpercent` = '$profitlimitpercent',
- `stoplosspercentage` = '$stoplosspercentage',
- `buystop_percentage` = '$buystop_percentage',
- `sellstoppercentage` = '$sellstoppercentage',
- `trailingpercentage` = '$trailingpercentage',
- `pricebufferpercent` = '$pricebufferpercent' ";
- }
- private function setup_settings()
- {
- /*
- * CONCEPT:
- *
- * CAUTION:
- * 'position' will NOT be stored or set in the `settings` table.
- */
- $this->sett = array(
- 'broker_min' => '0',
- 'broker_max' => '3',
- 'broker_opt' => '1',
- 'buystop_percentage_min' => '100',
- 'buystop_percentage_max' => '1000',
- 'buystop_percentage_opt' => '800',
- 'diversify_min' => 'no',
- 'diversify_max' => 'yes',
- 'diversify_opt' => 'yes',
- 'duration_min' => '1',
- 'duration_max' => '360',
- 'duration_opt' => '90',
- 'hold_min' => 'no',
- 'hold_max' => 'yes',
- 'hold_opt' => 'no',
- 'position_min' => '1',
- 'position_max' => '9',
- 'position_opt' => '3',
- 'pricebufferpercent_min' => '1',
- 'pricebufferpercent_max' => '25',
- 'pricebufferpercent_opt' => '10',
- 'profitlimitpercent_min' => '1',
- 'profitlimitpercent_max' => '10000',
- 'profitlimitpercent_opt' => '5555',
- 'quantity_min' => '2',
- 'quantity_max' => '24',
- 'quantity_opt' => '12',
- 'select_r_min' => 'no',
- 'select_r_max' => 'yes',
- 'select_r_opt' => 'yes',
- 'selldegree_min' => 'no',
- 'selldegree_max' => 'yes',
- 'selldegree_opt' => 'yes',
- 'sellstoppercentage_min' => '1',
- 'sellstoppercentage_max' => '100',
- 'sellstoppercentage_opt' => '51',
- 'sharebuffer_min' => '0',
- 'sharebuffer_max' => '3000',
- 'sharebuffer_opt' => '444',
- 'startcash_min' => '100',
- 'startcash_max' => '1000',
- 'startcash_opt' => '333',
- 'startquote_min' => '0.10',
- 'startquote_max' => '50',
- 'startquote_opt' => '2.22',
- 'stoplosspercentage_min' => '1',
- 'stoplosspercentage_max' => '100',
- 'stoplosspercentage_opt' => '75',
- 'trailingpercentage_min' => '1',
- 'trailingpercentage_max' => '100',
- 'trailingpercentage_opt' => '11' );
- mysql_query("DELETE FROM `settings` " );
- // maximize / minimize
- $sett[] = array('broker', 'base' => 0, 'min' => 0, 'max' => 3, 'type' => 'int', 'opt' => '1');
- $sett[] = array('buystop_percentage', 'base' => 0, 'min' => 100, 'max' => 1000, 'type' => 'int', 'opt' => '800');
- $sett[] = array('diversify', 'base' => 'no', 'min' => 'no', 'max' => 'yes', 'type' => 'yesno', 'opt' => 'yes');
- $sett[] = array('duration', 'base' => 1, 'min' => 1, 'max' => 360, 'type' => 'int', 'opt' => '90');
- $sett[] = array('hold', 'base' => 'no', 'min' => 'no', 'max' => 'yes', 'type' => 'yesno', 'opt' => 'no');
- $sett[] = array('position', 'base' => 1, 'min' => 1, 'max' => 9, 'type' => 'int', 'opt' => '3');
- $sett[] = array('pricebufferpercent', 'base' => 0, 'min' => 0, 'max' => 25, 'type' => 'int', 'opt' => '10');
- $sett[] = array('profitlimitpercent', 'base' => 100, 'min' => 0, 'max' => 10000, 'type' => 'int', 'opt' => '5555');
- $sett[] = array('quantity', 'base' => 3, 'min' => 2, 'max' => 24, 'type' => 'int', 'opt' => '12');
- $sett[] = array('select_r', 'base' => 'no', 'min' => 'no', 'max' => 'yes', 'type' => 'yesno', 'opt' => 'yes');
- $sett[] = array('selldegree', 'base' => 1, 'min' => 1, 'max' => 9, 'type' => 'int', 'opt' => ''); // ???
- $sett[] = array('sellstoppercentage', 'base' => 36, 'min' => 1, 'max' => 100, 'type' => 'int', 'opt' => '51');
- $sett[] = array('sharebuffer', 'base' => 400, 'min' => 0, 'max' => 3000, 'type' => 'int', 'opt' => '444');
- $sett[] = array('startcash', 'base' => 200, 'min' => 100, 'max' => 1000, 'type' => 'int', 'opt' => '333');
- $sett[] = array('startquote', 'base' => 1, 'min' => 0.10, 'max' => 50, 'type' => 'int', 'opt' => '2.22');
- $sett[] = array('stoplosspercentage', 'base' => 10, 'min' => 1, 'max' => 100, 'type' => 'int', 'opt' => '75');
- $sett[] = array('trailingpercentage', 'base' => 36, 'min' => 1, 'max' => 100, 'type' => 'int', 'opt' => '11');
- // Set a class-wide variable for the settings
- foreach( $sett AS $index => $setting )
- {
- $r_value = $this->set_random( $setting );
- echo "setting = [ " . $setting[0] . " ] default = [ " . $r_value . " ]<br> ";
- mysql_query("INSERT INTO `settings` SET
- `name` = '" . $setting[0] . "',
- `value` = '" . $r_value . "' ");
- }
- mysql_query("INSERT INTO `settings` SET `name` = 'backtestbrake', `value` = 'no' ");
- mysql_query("INSERT INTO `settings` SET `name` = '', `value` = '' ");
- echo "Done inserting default settings into the `settings` table.<br> ";
- }
- private function call_r()
- {
- /*
- * CONCEPT:
- * test_r->call_r() calls R. Then, R will . . .
- * 1. Check the `backtests` table.
- * 2. Determine what variable settings to optimize.
- * 3. Update the values in the `settings` table.
- * 4. Determine if the settings are completely opitimized.
- * 5. IF the settings are optimized insert brake = 'yes' in the settings table.
- *
- * R should try to . . .
- * maximize buystop_percentage
- * minimize duration
- * maximize profitlimitpercent
- * maximize pricebufferpercent
- * minimize quantity
- * minimize sellstoppercentage
- * maximize sharebuffer
- * minimize startcash
- * minimize stoplosspercentage
- * minimize trailingpercentage
- *
- * CAUTION:
- * Make sure that R updates 'backtestbrake' to 'yes' in the `settings` table.
- */
- // Call R
- // Remove this line after R is able to set the backtestbrake
- mysql_query("UPDATE `settings` SET `value` = 'yes' WHERE `name` = 'backtestbrake' ");
- }
- private function score( $test_settings )
- {
- /*
- * CONCEPT:
- *
- * CAUTION:
- */
- // print_r($test_settings);
- // print_r($this->sett);
- foreach( $test_settings AS $setting => $value )
- {
- if( $setting !== 'id' )
- {
- echo "setting = $setting , value = $value <br>";
- $opt = $this->sett[$setting . '_opt'];
- $opt = $value; // ??? Remove this line
- $max = $this->sett[$setting . '_max'];
- // Convert yesno values
- if( $max === 'yes' )
- {
- $max = 2;
- }
- elseif( $max === 'no' )
- {
- $max = 1;
- }
- if( $opt === 'yes' )
- {
- $opt = 2;
- }
- elseif( $opt === 'no' )
- {
- $opt = 1;
- }
- if( $value === 'yes' )
- {
- $value = 2;
- }
- elseif( $value === 'no' )
- {
- $value = 1;
- }
- if( $opt > $value )
- {
- $difference = $opt - $value;
- }
- else
- {
- $difference = $value - $opt;
- }
- // echo "difference = $difference<br>";
- $subset = $max - $difference;
- $decimal = $subset / $max;
- $percent = floor( $decimal * 100 );
- // echo "value = $value<br>";
- // echo "decimal = $decimal<br>";
- echo "score = $percent<br>";
- // Tally the score
- $score = $score + $percent;
- }
- }
- return $score;
- }
- public function set_random( $setting )
- {
- /*
- * CONCEPT:
- * randomizer() picks a random setting between the min and max
- *
- * CAUTION:
- */
- // say($setting);
- // var_dump($setting);
- // Convert the 'no' and 'yes' to '0' and '1' for the mt_rand() function
- if( $setting['type'] == 'yesno' )
- {
- $setting['max'] = 1;
- $setting['min'] = 0;
- }
- elseif( $setting['type'] == 'string' )
- {
- $emergencies .= "randomizer() was asked to randomize a setting that is a 'string' type";
- }
- // Set the '' to 0 on the $settings that have 0 as the minimum value
- $setting['min'] = ( !$setting['min'] ) ? 0 : $setting['min'];
- // If the setting is startquote, use a minimum of 1
- if( $setting === 'startquote' )
- {
- // Multiply the max startquote by 100 to make room for change
- $maxed_quote = $setting['max'] * 100;
- // Make some random change (starting as low as $0.10)
- $change = mt_rand( 10, $maxed_quote );
- // Divide by 100 to get the dollars and cents
- $rand = $change / 100;
- // Convert to a string for easy comparison
- $rand = strval( $rand );
- }
- else
- {
- // Get a random number
- $rand = mt_rand( $setting['min'], $setting['max'] );
- }
- if( $setting['type'] == 'yesno' )
- {
- // If the type is 'yesno' convert the random number back to 'yes' or 'no'
- $rand = ( $setting['type'] == 'yesno' && $rand == 1 ) ? 'yes' : 'no';
- }
- // echo $rand;
- return $rand;
- }
- private function rank()
- {
- /*
- * CONCEPT:
- *
- * CAUTION:
- */
- // Calculate the rank for each position
- $resultq = "SELECT `id`,`startcash`,`duration`,`startquote`,`position`,`broker`,`select_r`,
- `diversify`,`hold`,`quantity`,`sharebuffer`,`transactions`,`profitlimitpercent`,`stoplosspercentage`,
- `buystop_percentage`,`sellstoppercentage`,`trailingpercentage`,`pricebufferpercent` FROM `backtests`
- WHERE `rank` IS NULL ";
- // echo "$resultq<br>";
- $result = mysql_query("$resultq");
- while( list( $id, $startcash, $duration, $startquote, $position, $broker, $select_r,
- $diversify, $hold, $quantity, $sharebuffer, $transactions, $profitlimitpercent, $stoplosspercentage,
- $buystop_percentage, $sellstoppercentage, $trailingpercentage, $pricebufferpercent ) =
- mysql_fetch_row( $result ) )
- {
- $test_settings = array(
- 'id' => "$id",
- 'startcash' => "$startcash",
- 'duration' => "$duration",
- 'startquote' => "$startquote",
- 'position' => "$position",
- 'broker' => "$broker",
- 'select_r' => "$select_r",
- 'diversify' => "$diversify",
- 'hold' => "$hold",
- 'quantity' => "$quantity",
- 'sharebuffer' => "$sharebuffer",
- 'pricebufferpercent' => "$pricebufferpercent",
- 'profitlimitpercent' => "$profitlimitpercent",
- 'stoplosspercentage' => "$stoplosspercentage",
- 'buystop_percentage' => "$buystop_percentage",
- 'sellstoppercentage' => "$sellstoppercentage",
- 'trailingpercentage' => "$trailingpercentage",
- 'pricebufferpercent' => "$pricebufferpercent" );
- // Calculate the score for the test
- $total_score = $this->score( $test_settings );
- echo "total_score = [ $total_score ]<br> ";
- // Alter the score randomly ?
- $t_score_min = $total_score - ( $total_score * 0.25 );
- $t_score_max = $total_score + ( $total_score * 0.20 );
- $rank = mt_rand( $t_score_min, $t_score_max );
- echo "t_score_min = [ $t_score_min ]<br>";
- echo "t_score_max = [ $t_score_max ]<br>";
- echo "rank = [ $rank ] ";
- // Record the rank
- mysql_query("UPDATE `backtests` SET `rank` = '" . $rank . "'
- WHERE `id` = '" . $id . "' ");
- }
- }
- }
- ?>