PageRenderTime 72ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 0ms

/jesus/setup.php

https://bitbucket.org/samuelbond/god
PHP | 1417 lines | 802 code | 292 blank | 323 comment | 78 complexity | d6fbc38e53043cd96aeef6da9e832e5a MD5 | raw file
  1. <?php
  2. class setup
  3. {
  4. private $check, $setup_settings, $setup_propositions;
  5. public function environment( $account_info )
  6. {
  7. /*
  8. * CONCEPT:
  9. * setup() does most of the things needed to setup an environment to run jesus.
  10. *
  11. * CAUTION:
  12. * There are a lot of tables, globals and rules. setup() must be coded to check
  13. * to make sure everything is set up correctly because the user will forget the
  14. * settings after a few months and we want to reduce the impact of human error.
  15. */
  16. // Include the static class that manages the global settings
  17. $S = settings::getInstance();
  18. // Instantiate the class that does all of the checking
  19. $this->check = new check();
  20. $this->set_account_globals( $account_info );
  21. display_tables( 'open_account' );
  22. // Connect to the database
  23. $this->connect();
  24. // Create the MySQL procedures (stored functions)
  25. $this->procedures();
  26. // Delete the tables after a number of test runs
  27. $this->delete_tables();
  28. // Create the static $port tables
  29. $this->tables();
  30. // Check to make sure the tables are all set up
  31. $this->check->tables();
  32. // Insert the settings based on $unique_account and defaults
  33. $this->insert_settings();
  34. // Make sure there is only ONE instance of jesus running at a time
  35. $this->lock( "lock", "jesus" );
  36. // Set the timestamp for this run
  37. $this->datetime();
  38. // Check to make sure the settings for the account are good
  39. $this->check->settings();
  40. // Set the globals (especially related to the brokerage account).
  41. $this->globals();
  42. // Setup the `propositions` table
  43. $this->propositions();
  44. // Update the degrees (and set $S->degrees for god)
  45. $this->degrees();
  46. // Release all of the temporary 'hold' stops for all positions
  47. $this->release_holds();
  48. // Get the account information from the broker
  49. $this->broker();
  50. // Check the cash_broker against the local tally
  51. $this->check->cash();
  52. // Divide the money among the $ports
  53. $this->communion();
  54. }
  55. public function accounts()
  56. {
  57. /*
  58. * CONCEPT:
  59. * setup->accounts() sets the account unique arrays.
  60. *
  61. * CAUTION:
  62. * If satan is running, $S->unique_accounts is replaced with $S->satan_unique_accounts
  63. */
  64. // Include the static class that manages the global settings
  65. $S = settings::getInstance();
  66. // Instantiate the class that does all of the checking
  67. $check = new check();
  68. comment( "Setting up the unique accounts (on " . $S->datetime . ") . . . ", 11 );
  69. // If the backtest system is running
  70. if( $S->backtesting === 'yes' )
  71. {
  72. // If backtest is running use it's accounts and databases
  73. $unique_accounts = $S->backtest_account;
  74. }
  75. elseif( $S->satanrunning === 'yes' )
  76. {
  77. // If satan is running use it's accounts and databases
  78. $unique_accounts = $S->satan_unique_accounts;
  79. }
  80. else
  81. {
  82. // Include the client accounts
  83. require_once('accounts.php');
  84. }
  85. // Check to make sure the accounts are unique and have valid settings
  86. $check->account_uniqueness( $unique_accounts );
  87. comment( "Done.", 7 );
  88. return $unique_accounts;
  89. }
  90. private function benchmark( $clear = false )
  91. {
  92. /*
  93. * CONCEPT:
  94. * benchmark() uses the php function microtime() which is the "wall clock" time
  95. * that passes while the entire process is running (including the time it takes
  96. * to log in and get quotes from the broker). When jesus is called by backtest,
  97. * the benchmark() function is started at the top of backtest.php (rather than
  98. * inside jesus (so that the entire backtest run time is recorded).
  99. *
  100. * CAUTION:
  101. * Only one benchmark time is recorded at a time. Estimating the time it takes
  102. * to do multiple runs (in backtest) may be hard unless a table is created and
  103. * more benchmark times are stored so that an average can be calculated.
  104. */
  105. // Make $stime static so that it cannot be overwritten
  106. static $starttime;
  107. // Call the php function microtime()
  108. $currentTime = microtime( TRUE );
  109. $benchmark = '';
  110. // If $starttime has been set previously
  111. if( $starttime && !$clear )
  112. {
  113. // Calculate the time lapse between the current time and the start time
  114. $benchmark = $currentTime - $starttime;
  115. // Round the time to 2 decimal places
  116. $benchmark = round( $benchmark, 2 );
  117. comment( "Elapsed time: $benchmark seconds\n", 9 );
  118. }
  119. // Set the starting time
  120. $starttime = $currentTime;
  121. return $benchmark;
  122. }
  123. private function broker()
  124. {
  125. /*
  126. * CONCEPT:
  127. * broker() gives an account of the current information in the brokerage account.
  128. * Except for login, the other variables are returned in multi-demensional arrays.
  129. *
  130. * CAUTION:
  131. * Note: just2trade will give back information about all accounts for the client.
  132. * Also, the information for all $ports is included as well.
  133. */
  134. // Include the static class that manages the global settings
  135. $S = settings::getInstance();
  136. // Instantiate the class that does all of the checking
  137. $check = new check();
  138. // Instantiate the class that sets up the environment
  139. $setup = new setup();
  140. comment( "Running with spiritmode = [ " . $S->spiritmode . " ] ", 6 );
  141. if( $S->satanrunning !== 'yes' )
  142. {
  143. // Set the file jesus will use for spirit . . .
  144. if( $S->spiritmode === 'succubus' || $S->spiritmode === 'demon' )
  145. {
  146. require_once('../spirit/demon.php');
  147. }
  148. elseif( $S->spiritmode === 'broker' )
  149. {
  150. require_once('../spirit/broker.php');
  151. }
  152. }
  153. // Instantiate the correct version of spirit
  154. $spirit = $setup->spirit();
  155. comment( "Aquiring all account attributes . . . ", 8 );
  156. // Unset the superglobals that are about to be retrieved
  157. $unsets = array('logged_in', 'open_market', 'cash_broker', 'broker_positions', 'done_orders', 'open_orders');
  158. // Loop through the unsets array and unset
  159. foreach( $unsets AS $key => $superglobal )
  160. {
  161. unset( $S->$superglobal );
  162. }
  163. // If the variation1 and variation2 values are set
  164. if( $S->variation1 && $S->variation2 )
  165. {
  166. // Get all of the current information from the brokerage account
  167. list( $logged_in, $open_market, $cash_broker, $broker_positions, $done_orders, $open_orders ) =
  168. $spirit->acquire_all_account_attributes();
  169. }
  170. // Check the variables returned from the broker()
  171. $S->logged_in = $logged_in;
  172. $S->open_market = $open_market;
  173. $S->cash_broker = $cash_broker;
  174. // Check the $broker_positions values
  175. $check->symbol_array( $broker_positions, 'broker_positions', 9 );
  176. // Check the $done_orders values
  177. $check->symbol_array( $done_orders, 'done_orders', 9 );
  178. // Check the $open_orders values
  179. $check->symbol_array( $open_orders, 'open_orders', 9 );
  180. comment("logged_in = [ $S->logged_in ] open_market = [ $S->open_market ] cash_broker = [ $S->cash_broker ] ", 9 );
  181. // Update the cash_broker to match the cash_local
  182. sql( "UPDATE `settings` SET value = '" . $S->cash_broker . "'
  183. WHERE name = 'cash_broker' ", "setup->broker" );
  184. comment("Recorded cash_broker = [ \$" . $S->cash_broker . " ] ", 6 );
  185. comment( "Done.", 7 );
  186. }
  187. private function cash_reserve()
  188. {
  189. /*
  190. * CONCEPT:
  191. * setup->cash_reserve() calculates the amount of cash that should set aside for slippage initially.
  192. * It is easy to remember to set aside money for the cash_reserve; It will be factored in during the
  193. * backtesting (which will determine the optimal minimal amount of cash required to make a profit).
  194. *
  195. * CAUTION:
  196. * This should only be run ONCE when first setting up the tables because it stores cash_slippage in
  197. * the `settings` table. The cash_slippage value is updated to reflect the amount of cash_slippage.
  198. */
  199. // Include the static class that manages the global settings
  200. $S = settings::getInstance();
  201. comment("Calculating the \$S->cash_reserve that will be set aside for slippage . . .", 6);
  202. // Set the cash_reserve percent as a decimal
  203. $cash_reserve_percent = $S->cash_reserve_percent / 100;
  204. // Deduct the cash that cannot be invested by god
  205. $spendable_cash = $S->cash_broker - $S->cash_safe;
  206. comment("[ \$$S->cash_broker ] cash_broker - [ \$$S->cash_safe ] cash_safe = [ $spendable_cash ] spendable cash. ", 5);
  207. // Multiply the spendable_cash by the result ( $1000 X 0.10 = $100 )
  208. // Floor the $cash_reserve (at 2 decimal places) to get the initially reserved cash
  209. $cash_reserve = floor_decimal( $spendable_cash * $cash_reserve_percent , 2);
  210. // Set the initial cash_slippage
  211. sql("UPDATE `settings` SET value = '" . $cash_reserve . "'
  212. WHERE name = 'cash_slippage' ", "setup->cash_reserve");
  213. comment("[ \$$cash_reserve ] has been set aside for cash_slippage. ", 7);
  214. comment( "Done. ", 7 );
  215. return $cash_reserve;
  216. }
  217. private function communion()
  218. {
  219. /*
  220. * CONCEPT:
  221. * communion() runs ONE time before we start investing. It divides $S->cash_broker
  222. * (which comes directly from the brokerage account) or $S->test_cash_broker which
  223. * comes from configure() among the $S->ports (after subtracting the $S->cash_safe
  224. * and $S->cash_reserve).
  225. *
  226. * CAUTION:
  227. * It is important to get the total free cash amount from the brokerage account.
  228. */
  229. // Include the static class that manages the global settings
  230. $S = settings::getInstance();
  231. // Tally the starting_cash for all of the ports
  232. list( $cash_port ) = mysql_fetch_row( sql( "SELECT `value` FROM `settings`
  233. WHERE name = 'cash_port' LIMIT 1 ", "setup->communion" ) );
  234. if( $cash_port === 'run_communion' )
  235. {
  236. // Reserve some cash for (the initial) slippage
  237. $cash_reserve = $this->cash_reserve();
  238. comment( "Dividing the money among the \$ports . . . ", 7 );
  239. // Calculate the starting cash for each $port.
  240. // Floor the $total_starting_cash to the penny
  241. $S->port_cash = floor_decimal( ( ( $S->cash_broker
  242. // Deduct the money god is not allowed to spend ever.
  243. - $S->cash_safe
  244. // Deduct the money god will have as an initial slippage buffer.
  245. - $cash_reserve ) / $S->ports ), 2 );
  246. comment( "Distributing ([ \$$S->cash_broker ] cash_broker - [ \$$S->cash_safe ] cash_safe
  247. - [ \$$cash_reserve ] cash_reserve ) / [ $S->ports ] ports = [ \$$S->port_cash ] to each port. ", 5);
  248. // Distribute the money to the ports
  249. sql( "UPDATE `settings` SET value = '" . $S->port_cash . "'
  250. WHERE name = 'port_cash' ", "setup->communion" );
  251. // Permanently record the initial port_cash value in `settings` 'cash_port'. cash_port is never overwritten.
  252. sql( "UPDATE `settings` SET value = '" . $S->port_cash . "'
  253. WHERE name = 'cash_port' ", "setup->communion" );
  254. }
  255. else
  256. {
  257. comment("The investment capital has already been divided among the ports ", 5 );
  258. }
  259. comment( "Done. ", 7 );
  260. }
  261. public function connect()
  262. {
  263. /*
  264. * CONCEPT:
  265. * connect() connects to the database of the server running jesus.
  266. * It has a variable ($server) that can be changed to switch the server connection code.
  267. *
  268. * CAUTION:
  269. * Remember to change the passwords after production for higher security.
  270. */
  271. // Include the static class that manages the global settings
  272. $S = settings::getInstance();
  273. // Initialize var
  274. $server = '';
  275. comment( "" . $S->fullname . " ", 11 );
  276. comment( "[ database: " . $S->database . " ]. . . ", 8 );
  277. // Set this variable to specify which server is being used (programmer | shared | shared)
  278. // [EDIT]: 1. Comment the following line (and uncomment the subsequent line).
  279. // $server = 'shared';
  280. // $server = 'programmer';
  281. // Make sure you set up MySQL to use the InnoDB database engine.
  282. if( $server == 'programmer' )
  283. {
  284. // [EDIT] Set the Programmer's server settings. Simply fill in your mySQL database user and password.
  285. $db_server = 'localhost';
  286. $db_user = 'root';
  287. $db_pass = '';//1111';
  288. }
  289. elseif( $server == 'shared' )
  290. {
  291. // Shared Production Server
  292. $db_server = 'changinglinks1.globatmysql.com';
  293. $db_user = 'shortay';
  294. $db_pass = '4sho';
  295. }
  296. else
  297. {
  298. // My Production MySQL Server
  299. $db_server = 'localhost';
  300. $db_user = 'root';
  301. $db_pass = 'OlV734A';
  302. }
  303. comment( "database server = [ $db_server ] database = [ " . $S->database . " ]
  304. db_user = [ $db_user ] db_pass = [ $db_pass ] ", 2 );
  305. // Connect to the mySQL server
  306. mysql_connect( "$db_server", "$db_user", "$db_pass" ) or die( "Unable to connect to server!" );
  307. // Create the database that corresponds with the account set in configure
  308. sql( "CREATE DATABASE IF NOT EXISTS " . $S->database . " ", "createdatabase" );
  309. mysql_select_db( $S->database ) or die( "Could not select database (" . $S->database . ") using [ $server ] connection code" );
  310. }
  311. private function cssheader()
  312. {
  313. /*
  314. * CONCEPT:
  315. *
  316. * CAUTION:
  317. */
  318. // Include the static class that manages the global settings
  319. $S = settings::getInstance();
  320. if( $S->debug_detail <= 10 && $S->debug == 'yes' )
  321. {
  322. echo "<html><head><style> ";
  323. echo "body { font-family: Arial, sans-serif; font-size: 11pt; color: black; } ";
  324. echo ".header { font-weight: bold; font-size:11pt; } ";
  325. echo ".normal { color: #000000; font-size:11pt; } ";
  326. echo ".satan { color: #973bed; background-color:#b2fdf9; } ";
  327. echo ".N1 { color: #E3E3E3; font-size:8pt; } ";
  328. echo ".N2 { color: #D4D4D4; font-size:9pt; } ";
  329. echo ".N3 { color: #C0C0C0; font-size:10pt; } ";
  330. echo ".N4 { color: #B0B0B0; font-size:10pt; } ";
  331. echo ".N5 { color: #A1A1A1; font-size:11pt; } ";
  332. echo ".N6 { color: #808080; font-size:12pt; } ";
  333. echo ".N7 { color: #555555; font-size:12pt; } ";
  334. echo ".N8 { color: #000000; font-size:13pt; } ";
  335. echo ".N9 { color: #000000; font-size:14pt; } ";
  336. echo ".N10 { color:#FFFFFF; font-weight:bold; font-size:14pt; background-color:#69B000; }";
  337. echo ".N11 { color:#FFFFFF; font-weight:bold; font-size:14pt; background-color:#000000; }";
  338. echo ".N12 { color: #000000; font-size:13pt; background-color:#FFFFCC; } ";
  339. echo ".delimiter { color: #004080; font-weight: bold; } ";
  340. echo ".satandelimiter { color: #004080; font-weight: bold; font-size: large; } ";
  341. echo ".set { color:#000000 font-weight:bold; font-size:10pt; background-color:#FFFFCC; } ";
  342. echo ".data { color: #606060; font-size:11pt; } ";
  343. echo ".error { color: #C00000; } ";
  344. echo "</style></head><body>";
  345. }
  346. }
  347. public function datetime()
  348. {
  349. /*
  350. * CONCEPT:
  351. * setup->datetime() sets the timestamp to the real time (unless backtests are being run).
  352. * Otherwise, the datetime is set with dating->get_datetime(). That allows the backtest
  353. * system to set the date (rather than using the time stamp that is set with php or mySQL.
  354. * Relative time is used in sacrifice->daytrades_counter() & transactions->get_positions()
  355. * Note: Using setup->datetime() also makes sure the quote times are all syncronized.
  356. *
  357. * CAUTION:
  358. * There is a change the quotes table will not be updated with the current date.
  359. * Make sure the error checking stops jesus from processing without a good date.
  360. */
  361. // Include the static class that manages the global settings
  362. $S = settings::getInstance();
  363. if( $S->backtesting !== 'yes' )
  364. {
  365. // Set the datetime
  366. $S->datetime = date("Y-m-d H:i:s");
  367. // Set the current date
  368. $S->date = date("Y-m-d");
  369. // Set the current time
  370. $S->time = date("H:i:s");
  371. }
  372. comment("The datetime is [ " . $S->datetime . " ] ", 7);
  373. // If there is no datetime by this time in the run, emergency()
  374. if( !$S->datetime )
  375. {
  376. emergency("\$S->datetime is set to [ " . $S->datetime . " ] ");
  377. }
  378. comment( "Done. ", 7 );
  379. }
  380. public function delete_tables()
  381. {
  382. /*
  383. * CONCEPT:
  384. * jesus creates the tables and adds data to them the first time it is run.
  385. *
  386. * CAUTION:
  387. * Make sure setup->delete_tables() is not used if $S->execute_transactions = 'yes'.
  388. */
  389. // Include the static class that manages the global settings
  390. $S = settings::getInstance();
  391. // Instantiate the class that does all of the checking
  392. $check = new check();
  393. // If the `settings` table exists . . .
  394. if( $check->table( 'settings' ) )
  395. {
  396. // Select the number of runs since the last table deletion
  397. list( $run_count ) = mysql_fetch_row( sql("SELECT `value`+1 FROM `settings`
  398. WHERE `name` = 'run_count' ", "setup->delete_tables" ) );
  399. // Set the run_count to 1 if it is not set
  400. $run_count = ( is_null($run_count) ) ? 1 : $run_count + 1;
  401. // Calculate the number of runs before the database will be deleted.
  402. $to_delete = $S->runs_to_delete_tables - $run_count;
  403. // Store the updated run_count
  404. sql( "UPDATE `settings` SET value=value+1
  405. WHERE name = 'run_count' ", "setup->delete_tables" );
  406. // Select execute_transactions
  407. list( $execute_transactions ) = mysql_fetch_row( sql( "SELECT value FROM `settings`
  408. WHERE name = 'execute_transactions' ", "setup->delete_tables" ) );
  409. if( $S->runs_to_delete_tables === $S->runs_to_delete_tables_max || $S->spiritmode === 'spirit' || $S->execute_transactions === 'yes' )
  410. {
  411. comment( "jesus has run [ $run_count ] times. setup->delete_tables() is disabled. ", 9 );
  412. }
  413. else
  414. {
  415. // If it is not time to delete yet . . .
  416. if( $run_count < $S->runs_to_delete_tables )
  417. {
  418. comment( "jesus has run [ $run_count ] times. ", 9 );
  419. comment( "Note: setup->delete_tables() is enabled.
  420. All database tables will be deleted after [ $to_delete ] more runs! ", 9 );
  421. }
  422. else
  423. {
  424. comment( "Starting over with a new `" . $S->database . "` database <hr color=#FF9900>", 9 );
  425. // List the global tables
  426. $tables = array('messages', 'port_counts', 'propositions', 'quotes', 'quoteshistory', 'replacements',
  427. 'selecta', 'settings', 'skippedtrans', 'subtransactions', 'transactions');
  428. comment( "Dropping the tables ", 7 );
  429. foreach( $tables AS $table )
  430. {
  431. comment( "Dropping table $table ", 5 );
  432. sql( "DROP TABLE IF EXISTS $table ", "setup->delete_tables" );
  433. if( $check->table( $table ) )
  434. {
  435. emergency( "`$table` ain't been deleted :( ");
  436. }
  437. }
  438. }
  439. }
  440. }
  441. comment( "Done.", 7 );
  442. }
  443. private function degrees()
  444. {
  445. /*
  446. * CONCEPT:
  447. * setup->degrees() pulls the positions and related degrees from the `positions` table,
  448. * validates the values, and adds them to the $S->degrees array.
  449. *
  450. * CAUTION:
  451. * setup->propositions() must be run before setup->degrees()
  452. */
  453. // Include the static class that manages the global settings
  454. $S = settings::getInstance();
  455. // Instantiate the class that does all of the checking
  456. $check = new check();
  457. // Set an array to hold the degrees
  458. $S->degrees = array();
  459. // Count the number of positions with the degrees setup
  460. list( $degrees_setup ) = mysql_fetch_row(sql("SELECT IFNULL(COUNT('position'),0) FROM `propositions`
  461. WHERE `buydegree` = '0'
  462. OR `selldegree` = '0' ", "setup->degrees"));
  463. // If there are no degrees setup
  464. if( $degrees_setup !== '0' )
  465. {
  466. for( $port = 1; $port <= $S->ports; $port++ )
  467. {
  468. for( $position = 1; $position <= $S->positions; $position++ )
  469. {
  470. comment("Recording buydegree = [ " . $S->buydegree.$position . " ]
  471. selldegree = [ " . $S->selldegree.$position . " ]
  472. for port = [ $port ] position = [ $position ] ", 5);
  473. sql( "UPDATE `propositions` SET
  474. `buydegree` = '" . $S->buydegree.$position . "',
  475. `selldegree` = '" . $S->selldegree.$position . "'
  476. WHERE `port` = '" . $port . "'
  477. AND `position` = '" . $position . "' ", "setup->degrees" );
  478. }
  479. }
  480. }
  481. // Check the validity of each degree value
  482. $check->degrees();
  483. comment( "Done. ", 7 );
  484. }
  485. private function insert_settings()
  486. {
  487. /*
  488. * CONCEPT:
  489. * setup->insert_settings() records the settings array into the `settings` table.
  490. *
  491. * CAUTION:
  492. * Be careful not to let insert_settings() run twice on the same account.
  493. */
  494. // Include the static class that manages the global settings
  495. $S = settings::getInstance();
  496. // Count the number of settings in the `settings` table
  497. list( $settings_rows ) = mysql_fetch_row(sql("SELECT IFNULL(COUNT('name'),0) FROM `settings`
  498. WHERE `name` NOT LIKE '%startdate%' ", "setup->insert_settings"));
  499. // If there are no settings (besides startdates)
  500. if( $settings_rows === '0' )
  501. {
  502. // Set an array of non-port settings
  503. foreach( $S->settings_defaults_values AS $i => $setting )
  504. {
  505. comment("Recording \$S->$setting as [ " . $S->$setting . " ]", 4 );
  506. sql( "INSERT INTO `settings` ( name, port, value ) VALUES ('" . $setting . "', '0', '" . $S->$setting . "' ) ", "setup->tables" );
  507. }
  508. foreach( $S->settings_all_ports_values AS $i => $setting )
  509. {
  510. comment("Recording \$S->$setting as [ " . $S->$setting . " ]", 4 );
  511. sql( "INSERT INTO `settings` ( name, port, value )
  512. VALUES ('" . $setting . "', '0', '" . $S->$setting . "' ) ", "setup->tables" );
  513. }
  514. if( $S->backtesting === 'yes' )
  515. {
  516. foreach( $S->settings_backtest_values AS $i => $setting )
  517. {
  518. comment("Recording \$S->$setting as [ " . $S->$setting . " ]", 4 );
  519. sql( "INSERT INTO `settings` ( name, port, value )
  520. VALUES ('" . $setting . "', '0', '" . $S->$setting . "' ) ", "setup->tables" );
  521. }
  522. }
  523. for( $port = 1; $port <= $S->ports; $port++ )
  524. {
  525. foreach( $S->settings_port_values AS $i => $setting )
  526. {
  527. comment("Recording \$S->$setting as [ " . $S->$setting . " ] for port [ $port ] ", 4 );
  528. sql( "INSERT INTO `settings` ( name, port, value )
  529. VALUES ('" . $setting . "', '" . $port . "', '" . $S->$setting . "' ) ", "setup->tables" );
  530. }
  531. }
  532. // sql( "ALTER TABLE `settings` ORDER BY `port`,`name` ", "setup->tables" );
  533. }
  534. comment( "Done. ", 7 );
  535. }
  536. public function globals()
  537. {
  538. /*
  539. * CONCEPT:
  540. * setup->globals() simply gets the global variable values from the `settings` table.
  541. *
  542. * CAUTION:
  543. */
  544. // Include the static class that manages the global settings
  545. $S = settings::getInstance();
  546. // Instantiate the class that does all of the checking
  547. $check = new check();
  548. comment( "Setting the `settings` globals . . . ", 7 );
  549. // Select all of the jesus options globals
  550. $result = sql( "SELECT * FROM `settings`
  551. WHERE port IN ('0','" . $S->current_port . "') ", "setup->globals" );
  552. // Loop through the `settings` table and set each row as a global variable
  553. while( list( $variable, $port, $value ) = mysql_fetch_row( $result ) )
  554. {
  555. // Trim the value (in port there was a space in the database) and lowerport
  556. $variable = strtolower( $variable );
  557. // Add the variable and it's values to an array
  558. $S->$variable = $value;
  559. comment( "\$S->$variable = [ $value ] ", 6 );
  560. }
  561. comment( "Done. ", 7 );
  562. }
  563. public function lock( $switch, $who )
  564. {
  565. /*
  566. * CONCEPT:
  567. * lock() makes sure that only one instance is running by updating
  568. * the `settings` table.
  569. *
  570. * CAUTION:
  571. * It's very important that the tables are locked AND unlocked on time.
  572. * It is possible for god() to run as two instances. If god is started
  573. * two times it malfunctions by multiplying the numbers in the database.
  574. */
  575. // Include the static class that manages the global settings
  576. $S = settings::getInstance();
  577. // Initialize var
  578. $mysqlerror = '';
  579. // Set the switch value (that will be recorded)
  580. if( $switch == "lock" )
  581. {
  582. $value = 'yes';
  583. }
  584. elseif( $switch == "unlock" )
  585. {
  586. $value = 'no';
  587. }
  588. else
  589. {
  590. emergency( "The lock value was [ $lock ]");
  591. }
  592. // If we are locking jesus (to make sure there is only one copy of god running)
  593. if( $who == "jesus" )
  594. {
  595. // Check if an instance of jesus is already running
  596. comment("$switch jesus.", 8);
  597. // Select the lock() value from the database
  598. sql( "SELECT GET_LOCK('jesus', 10)", "setup->lock" );
  599. $res = sql( "SELECT `value` FROM `settings`
  600. WHERE name = 'jesusrunning' ", "setup->lock" );
  601. if( $res === FALSE )
  602. {
  603. sql( "SELECT RELEASE_LOCK('jesus')", "setup->lock" );
  604. }
  605. // Make sure the lock value has been recorded
  606. $current_jesus_status = mysql_fetch_row( $res );
  607. if( !$current_jesus_status )
  608. {
  609. sql( "SELECT RELEASE_LOCK('jesus')", "setup->lock" );
  610. emergency( "The 'jesusrunning' option does not exist in the `settings` table ");
  611. }
  612. // If jesusrunning = yes
  613. if( $current_jesus_status[0] == 'yes' && $value == 'yes' )
  614. {
  615. // The jesus is already running. Quit.
  616. sql( "SELECT RELEASE_LOCK('jesus')", "setup->lock" );
  617. comment( "jesus is already running!", 9 );
  618. exit();
  619. }
  620. // If there is a problem with the locking sequence, emergency()
  621. elseif( $current_jesus_status[0] == 'no' && $value == 'no' )
  622. {
  623. sql( "SELECT RELEASE_LOCK('jesus')", "setup->lock" );
  624. emergency( "Trying to unlock jesus while it was not locked");
  625. }
  626. // Set the lock() value
  627. sql( "UPDATE `settings` SET `value` = '$value'
  628. WHERE name = 'jesusrunning' ", "setup->lock" );
  629. // Update the $S->jesusrunning (or backtest will halt)
  630. $S->jesusrunning = $value;
  631. sql( "SELECT RELEASE_LOCK('jesus')", "setup->lock" );
  632. // If jesus just did the final process ( unlock )
  633. if( $value == "no" )
  634. {
  635. // Now that all other processes are done, calculate the elapsed time.
  636. $benchmark = $this->benchmark();
  637. sql( "UPDATE `settings` SET `value` = '$benchmark' WHERE name = 'benchmark' ", "setup->lock" );
  638. }
  639. }
  640. // Check for any additional mysql errors
  641. if( $mysqlerror )
  642. {
  643. emergency( "MySQL error: [$mysqlerror]");
  644. }
  645. }
  646. public function preliminary()
  647. {
  648. /*
  649. * CONCEPT:
  650. * setup->preliminary() simply runs all of the misc stuff that should run
  651. * at the very start of the jesus process every time.
  652. *
  653. * CAUTION:
  654. */
  655. // Start timing jesus (if it has not been started by backtest or satan)
  656. $benchmark = $this->benchmark();
  657. // Make php errors show on the page if you are running from the browser
  658. // error_reporting( E_WARNING );
  659. // error_reporting( E_ALL | E_NOTICE );
  660. // Errors should be printed to the screen as part of the output (rather than hidden from the user)
  661. ini_set( 'display_errors', 1 );
  662. // Allow script to keep running if the browser disconnects
  663. ignore_user_abort( true );
  664. // Format the displayed output in the browser
  665. $this->cssheader();
  666. // Turn off the maximum execution time error
  667. set_time_limit(0);
  668. }
  669. private function procedures()
  670. {
  671. // Initialize var
  672. $function = '';
  673. // Check to see if the ROUND_COMMERCIAL procedure exists
  674. $result = mysql_query( "SHOW CREATE FUNCTION ROUND_COMMERCIAL " );
  675. // If the ROUND_COMMERCIAL MySQL function has been created
  676. if( $result )
  677. {
  678. list( $function, $sql_mode, $create_function ) = mysql_fetch_row( $result );
  679. }
  680. // If the function does NOT exist
  681. if( $function != 'ROUND_COMMERCIAL' )
  682. {
  683. // Create a function that uses more precise rounding
  684. sql( "CREATE FUNCTION
  685. ROUND_COMMERCIAL(value DOUBLE, preci INT(11))
  686. RETURNS DOUBLE NO SQL
  687. RETURN TRUNCATE((value * POW(10, preci)) + (IF(value = 0, 1,(value / ABS(value)))*(0.5 * POW(1, preci*-1))), 0) / POW(10, preci) ", "setup->procedures" );
  688. }
  689. }
  690. private function propositions()
  691. {
  692. /*
  693. * CONCEPT:
  694. * setup->propositions() makes sure there is a row for each position in each port
  695. * to store values related to the individual positions.
  696. *
  697. * CAUTION:
  698. */
  699. // Include the static class that manages the global settings
  700. $S = settings::getInstance();
  701. // Instantiate the class that does all of the checking
  702. $check = new check();
  703. // If the tables were created on this run
  704. if( $this->setup_propositions === 'yes' )
  705. {
  706. // Insert all of the ports and positions in the propositions table
  707. if( $check->table( 'propositions' ) )
  708. {
  709. comment("Setting up placeholders for the propositions . . . ", 5);
  710. list( $prop_count ) = mysql_fetch_row( sql("SELECT COUNT(`port`) FROM `propositions`
  711. WHERE `port` = '1'
  712. AND `position` = '1' ", "setup->propositions"));
  713. // If there is more than one row for port=1 position=1
  714. if( $prop_count > 1 )
  715. {
  716. emergency("There are too many rows in the `propositions` table ");
  717. }
  718. elseif( $prop_count === '0' )
  719. {
  720. comment("Inserting the placeholders for the propositions ", 4);
  721. for( $port = 1; $port <= $S->ports; $port++ )
  722. {
  723. for( $position = 1; $position <= $S->positions; $position++ )
  724. {
  725. // Insert placeholder rows for all of the positions in all of the ports
  726. sql( "INSERT INTO `propositions` ( port, position )
  727. VALUES ('" . $port . "', '" . $position . "' ) ", "setup->tables" );
  728. }
  729. }
  730. }
  731. }
  732. }
  733. comment( "Done. ", 7 );
  734. }
  735. private function release_holds()
  736. {
  737. /*
  738. * CONCEPT:
  739. *
  740. * CAUTION:
  741. */
  742. // Clear the temporary holds (set when a new high value is reached)
  743. comment("Releasing all 'hold' stops. ", 6);
  744. sql("UPDATE `transactions` SET `stop` = '' WHERE `stop` = 'hold' ", "setup->release_holds");
  745. list( $count ) = mysql_fetch_row( sql("SELECT COUNT(`id`) FROM `transactions`
  746. WHERE `stop` = 'hold' ", "setup->release_holds"));
  747. // If there are still holds, emergency()
  748. if( $count )
  749. {
  750. emergency("[ $count ] rows have 'hold' stops ");
  751. }
  752. comment( "Done. ", 7 );
  753. }
  754. private function set_account_globals( $account_info )
  755. {
  756. /*
  757. * CONCEPT:
  758. *
  759. * CAUTION:
  760. */
  761. // Include the static class that manages the global settings
  762. $S = settings::getInstance();
  763. // Set the account globals to the account array defaults
  764. foreach( $account_info AS $setting => $value )
  765. {
  766. $S->$setting = $value;
  767. // comment("Setting \$S->$setting to [ $value ] from \$account_info", 4);
  768. }
  769. comment( "Done. ", 4 );
  770. }
  771. public function spirit()
  772. {
  773. /*
  774. * CONCEPT:
  775. * $setup->spirit setups up the correct class to be used as $spirit.
  776. *
  777. * CAUTION:
  778. */
  779. // Include the static class that manages the global settings
  780. $S = settings::getInstance();
  781. // If the global conditions are OK and the orders are supposed to be sent to the broker . . .
  782. if( ( $S->spiritmode === 'demon' || $S->spiritmode === 'succubus' ) && !$S->satanrunning )
  783. {
  784. // Instantiate the broker interface class in demon.php
  785. $spirit = new demon();
  786. }
  787. elseif( $S->spiritmode === 'spirit' && $S->execute_transactions === 'yes' && $check->conditions() && !$S->satanrunning )
  788. {
  789. // Instantiate the broker interface class in spirit.php
  790. $spirit = new broker();
  791. }
  792. elseif( $S->satanrunning === 'yes' && $S->spiritmode === 'satan' )
  793. {
  794. // Instantiate the broker interface class in satan.php
  795. $spirit = new satan();
  796. }
  797. return $spirit;
  798. }
  799. private function tables()
  800. {
  801. /*
  802. * CONCEPT:
  803. * setup->tables() creates all of the tables needed to run jesus and god.
  804. *
  805. * CAUTION:
  806. * Make sure the tables are created correctly even when table correction is interupted due to a power
  807. * outage. Also, make sure that the tables are optimized to maximize the process speed.
  808. */
  809. // Include the static class that manages the global settings
  810. $S = settings::getInstance();
  811. // Instantiate the class that does all of the checking
  812. $check = new check();
  813. comment( "Creating the tables . . . ", 7 );
  814. // If the settings table is missing (check to create all of the tables)
  815. if( !$check->table( 'quotes' ) )
  816. {
  817. // Check to see if the transactions$port table has been created
  818. if( !$check->table( 'backtests' ) && $S->backtesting == 'yes' )
  819. {
  820. comment( "Creating the `backtests` table.", 5 );
  821. // Create the table that holds the test results
  822. sql( "CREATE TABLE IF NOT EXISTS `backtests` ( " .
  823. // id to make it easy to update test results
  824. "`id` int(10) unsigned NOT NULL auto_increment, " .
  825. // godprofit - setprofit = rank
  826. "`rank` int(10) DEFAULT NULL, " .
  827. // The percentage profit (or loss) between the startcash and godresult
  828. "`godprofit` int(11) NOT NULL DEFAULT '0', " .
  829. // The percentage profit (or loss) between the startcash and setresult
  830. "`setprofit` int(11) NOT NULL DEFAULT '0', " .
  831. // The place to store the failure message
  832. "`fail` text NOT NULL, " .
  833. // The total amount of cash the client puts in the brokerage account before god runs.
  834. "`startcash` decimal(10,2) NOT NULL DEFAULT '0.00', " .
  835. // The actual amount of money the position ended with
  836. "`godresult` decimal(10,2) NOT NULL DEFAULT '0.00', " .
  837. // startdate is the day the symbols are seleted on. The test process walks forward in time from startdate.
  838. "`startdate` date NOT NULL DEFAULT '0000-00-00', " .
  839. // The number of days between the startdate and the last day of trading
  840. "`duration` int(5) NOT NULL DEFAULT '0', " .
  841. // enddate is startdate + duration (days) (plus any extra time backtest needs to sell off all positions)
  842. "`enddate` date NOT NULL DEFAULT '0000-00-00', " .
  843. // The lowest price a symbol can be on the startdate
  844. "`startquote` decimal(10,2) NOT NULL DEFAULT '0.00', " .
  845. // The position number of the test result
  846. "`position` int(3) NOT NULL DEFAULT '0', " .
  847. // Internal variable that may affect results
  848. "`selldegree` int(3) NOT NULL DEFAULT '0', " .
  849. // The broker commission schedule used for the test
  850. "`broker` varchar(3) NOT NULL DEFAULT '', " .
  851. // Use R to select symbols
  852. "`select_r` varchar(3) NOT NULL DEFAULT '', " .
  853. // The set of symbols picked by R (based on their price correlation)
  854. "`symbols` text NOT NULL, " .
  855. // The diversify=yes forces god to avoid buying a symbol for more than one position
  856. "`diversify` varchar(3) NOT NULL DEFAULT '', " .
  857. // The hold=yes temporarily prevents god from selling a symbol with a rising price
  858. "`hold` varchar(3) NOT NULL DEFAULT '', " .
  859. // The number of positions processed during the test
  860. "`positions` int(3) NOT NULL DEFAULT '0', " .
  861. // The number of symbols selected for the set
  862. "`quantity` int(3) NOT NULL DEFAULT '0', " .
  863. // The sharebuffer setting god uses solely
  864. "`sharebuffer` int(11) NOT NULL DEFAULT '0', " .
  865. // The number of transactions that god executed
  866. "`transactions` int(11) NOT NULL DEFAULT '0', " .
  867. // The max amount of overall profit before triggering a stop
  868. "`profitlimitpercent` int(3) NOT NULL DEFAULT '0', " .
  869. // The max amount of overall loss before triggering a stop
  870. "`stoplosspercentage` int(3) NOT NULL DEFAULT '0', " .
  871. // The max percentage profit after buying a symbol before triggering a stop
  872. "`buystop_percentage` int(3) NOT NULL DEFAULT '0', " .
  873. // The max amount of loss after buying a symbol before triggering a stop
  874. "`sellstoppercentage` int(3) NOT NULL DEFAULT '0', " .
  875. // The max amount of loss from a high price before triggering a stop
  876. "`trailingpercentage` int(3) NOT NULL DEFAULT '0', " .
  877. // The percentage of slippage before triggering a stop
  878. "`pricebufferpercent` int(3) NOT NULL DEFAULT '0', " .
  879. // Setting KEYS increased performance
  880. "KEY `id` (`id`), " .
  881. "KEY `rank` (`rank`), " .
  882. "KEY `godprofit` (`godprofit`), " .
  883. "KEY `setprofit` (`setprofit`), " .
  884. "KEY `startcash` (`startcash`), " .
  885. "KEY `godresult` (`godresult`), " .
  886. // fail key not needed
  887. // startdate key not needed
  888. "KEY `startquote` (`startquote`), " .
  889. "KEY `position` (`position`), " .
  890. "KEY `selldegree` (`selldegree`), " .
  891. "KEY `broker` (`broker`), " .
  892. "KEY `select_r` (`select_r`), " .
  893. // symbols key not needed
  894. "KEY `diversify` (`diversify`), " .
  895. "KEY `duration` (`duration`), " .
  896. // enddate key not needed
  897. "KEY `hold` (`hold`), " .
  898. "KEY `positions` (`positions`), " .
  899. "KEY `quantity` (`quantity`), " .
  900. "KEY `sharebuffer` (`sharebuffer`), " .
  901. "KEY `transactions` (`transactions`), " .
  902. "KEY `profitlimitpercent` (`profitlimitpercent`), " .
  903. "KEY `stoplosspercentage` (`stoplosspercentage`), " .
  904. "KEY `buystop_percentage` (`buystop_percentage`), " .
  905. "KEY `sellstoppercentage` (`sellstoppercentage`), " .
  906. "KEY `trailingpercentage` (`trailingpercentage`), " .
  907. "KEY `pricebufferpercent` (`pricebufferpercent`) ) ENGINE=InnoDB ", "setup->tables" );
  908. }
  909. // If the jesusoptions table is missing, create it
  910. if( !$check->table( 'messages' ) )
  911. {
  912. comment( "Creating the `messages` table.", 5 );
  913. // Create a jesusoptions master table
  914. sql( "CREATE TABLE IF NOT EXISTS `messages` (
  915. `fullname` varchar(100) NOT NULL,
  916. `database` varchar(100) NOT NULL,
  917. `port` int(10) NOT NULL,
  918. `source` varchar(100) NOT NULL,
  919. `error_type` varchar(100) NOT NULL,
  920. `message` varchar(200) NOT NULL,
  921. `timestamp` datetime NOT NULL )
  922. ENGINE=InnoDB ", 'setup->tables' );
  923. }
  924. // Create the propsositions table if it does not exist
  925. if( !$check->table( 'propositions' ) )
  926. {
  927. comment( "Creating the `propositions` table.", 5 );
  928. sql( "CREATE TABLE `propositions`
  929. (`port` int(11) NOT NULL,
  930. `position` int(11) NOT NULL,
  931. `buydegree` int(11) NOT NULL DEFAULT 0,
  932. `selldegree` int(11) NOT NULL DEFAULT 0,
  933. `transid` int(11) NOT NULL DEFAULT 0,
  934. `cash_value` double (8,2) NOT NULL DEFAULT 0,
  935. `shares_value` double (8,2) NOT NULL DEFAULT 0,
  936. `original_value` double (8,2) NOT NULL DEFAULT 0,
  937. `high_position` double (8,2) NOT NULL DEFAULT 0,
  938. `starting_value` double (8,2) NOT NULL DEFAULT 0,
  939. `high` double (8,2) NOT NULL DEFAULT 0,
  940. KEY `original_value` (`port`, `position`,`transid`) )
  941. ENGINE=InnoDB ", "setup->tables" );
  942. // Set a flag for setup->propositions()
  943. $this->setup_propositions = 'yes';
  944. }
  945. // If the quotes table is missing, create it
  946. if( !$check->table( 'quotes' ) )
  947. {
  948. comment( "Creating the `quotes` table.", 5 );
  949. sql( "CREATE TABLE `quotes`
  950. (`symbol` char(8) NOT NULL,
  951. `price` double (8,2) DEFAULT 0 NOT NULL,
  952. `port` tinyint(3) NOT NULL,
  953. `buy` char(2) DEFAULT 'N' NOT NULL,
  954. date datetime DEFAULT '1.1.0 0:0:0' NOT NULL,
  955. PRIMARY KEY (`symbol`,`port`,`date`),
  956. UNIQUE KEY `symbol` (`symbol`) )
  957. ENGINE=InnoDB ", "setup->tables" );
  958. $S->setup_quotes = 'yes';
  959. }
  960. // Check to make surethere is a quoteshistory table
  961. if( !$check->table( 'quoteshistory' ) )
  962. {
  963. comment( "Creating the quoteshistory table.", 5 );
  964. sql( "CREATE TABLE `quoteshistory`
  965. (`symbol` char(8) NOT NULL,
  966. `price` double (8,2) NOT NULL,
  967. `port` tinyint(3) NOT NULL,
  968. `date` datetime NOT NULL)
  969. ENGINE=InnoDB ", "setup->tables" );
  970. }
  971. // Check to make surethere is a quoteshistory table
  972. if( !$check->table( 'replacements' ) )
  973. {
  974. comment( "Creating the replacements table.", 5 );
  975. sql( "CREATE TABLE `replacements`
  976. (`from_symbol` char(8) NOT NULL,
  977. `to_symbol` char(8) NOT NULL,
  978. `change_date` datetime NOT NULL,
  979. `completed` char(1) DEFAULT 'N' NOT NULL )
  980. ENGINE=InnoDB ", "setup->tables" );
  981. }
  982. // Check to make sure there is a selecta table
  983. if( !$check->table( 'selecta' ) )
  984. {
  985. comment( "Creating the selecta table.", 5 );
  986. sql("CREATE TABLE `selecta` (
  987. `date` date NOT NULL,
  988. `name1` text NOT NULL,
  989. `name2` text NOT NULL,
  990. `crosses` text NOT NULL,
  991. `crossprob` text NOT NULL )
  992. ENGINE=InnoDB ", "setup->tables" );
  993. }
  994. // Create the `settings` table if it does not exist
  995. if( !$check->table( 'settings' ) )
  996. {
  997. comment( "Creating the `settings` table.", 5 );
  998. // Create the `settings` table
  999. sql( "CREATE TABLE `settings`
  1000. (name char(24) NOT NULL,
  1001. port int(3) NOT NULL,
  1002. value char(64) NOT NULL,
  1003. KEY `name` (`name`, `port`),
  1004. KEY `port` (`port`,`name`) )
  1005. ENGINE=InnoDB ", 'setup->tables' );
  1006. // Set a flag for setup->insert_settings()
  1007. $this->setup_settings = 'yes';
  1008. }
  1009. // Check to see if the transactions$port table has been created
  1010. if( !$check->table( 'skippedtrans' ) )
  1011. {
  1012. comment( "Creating the `skippedtrans` table.", 5 );
  1013. sql( "CREATE TABLE skippedtrans(
  1014. transid int unsigned NOT NULL, PRIMARY KEY (transid),
  1015. symbol char(8) NOT NULL,
  1016. t_type enum('buy','sell') NOT NULL,
  1017. port tinyint(3) NOT NULL,
  1018. position tinyint(3) unsigned NOT NULL,
  1019. shares int unsigned NOT NULL,
  1020. price double (8,2) NOT NULL,
  1021. high double (8,2) NOT NULL,
  1022. cost double (8,2) NOT NULL,
  1023. credit double (8,2) NOT NULL,
  1024. stop char(15) NOT NULL,
  1025. t_date datetime NOT NULL, KEY (t_date),
  1026. completed char(1) NOT NULL,
  1027. pricebuffer double (8,2) NOT NULL )
  1028. ENGINE=InnoDB ", 'setup->tables' );
  1029. }
  1030. // Check to see if the transactions$port table has been created
  1031. if( !$check->table( 'startdates' ) && $S->backtesting == 'yes' )
  1032. {
  1033. comment( "Creating the `startdates` table.", 5 );
  1034. sql( "CREATE TABLE `startdates` (
  1035. `id` int(10) unsigned NOT NULL auto_increment,
  1036. `startdate` datetime NOT NULL,
  1037. PRIMARY KEY (`id`),
  1038. KEY `position` (`startdate`,`id`) )
  1039. ENGINE=InnoDB ", "setup->tables" );
  1040. }
  1041. // Check to see if the subtransactions table exists
  1042. if( !$check->table( 'subtransactions' ) )
  1043. {
  1044. comment( "Creating the `subtransactions` table.", 5 );
  1045. sql( "CREATE TABLE `subtransactions` (
  1046. `id` int(10) unsigned NOT NULL auto_increment,
  1047. `transid` int(11) NOT NULL default '0',
  1048. `orderid` varchar(100) NOT NULL default '',
  1049. `symbol` char(8) NOT NULL default '',
  1050. `t_type` enum('buy','sell') NOT NULL default 'buy',
  1051. `position` int(10) unsigned NOT NULL default '0',
  1052. `shares` int(10) unsigned NOT NULL default '0',
  1053. `price` double (8,2) NOT NULL default '0',
  1054. `feesaver` char(1) NOT NULL default '',
  1055. t_date datetime NOT NULL, KEY (t_date),
  1056. `pricebuffer` double (8,2) NOT NULL default '0',
  1057. PRIMARY KEY (`id`),
  1058. KEY `position` (`position`,`id`) )
  1059. ENGINE=InnoDB ", 'setup->tables' );
  1060. }
  1061. // Check to see if the transactions$port table has been created
  1062. if( !$check->table( 'transactions' ) )
  1063. {
  1064. comment( "Creating the `transactions` table.", 5 );
  1065. sql( "CREATE TABLE transactions(
  1066. `id` int(10) unsigned NOT NULL auto_increment,
  1067. `symbol` char(8) NOT NULL,
  1068. `t_type` enum('buy','sell') NOT NULL,
  1069. `port` int(3) unsigned NOT NULL,
  1070. `position` int unsigned NOT NULL,
  1071. `shares` int(10) unsigned NOT NULL default '0',
  1072. `price` double (8,2) NOT NULL default '0',
  1073. `cost` double (8,2) NOT NULL,
  1074. `credit` double (8,2) NOT NULL,
  1075. `stop` char(15) NOT NULL,
  1076. `t_date` datetime NOT NULL, KEY (t_date),
  1077. `completed` char(1) NOT NULL,
  1078. `pricebuffer` double (8,2) NOT NULL default '0',
  1079. `value` double (8,2) NOT NULL default '0',
  1080. PRIMARY KEY (`id`),
  1081. KEY `port` (`port`, `id`, `position`),
  1082. KEY `position` (`position`,`id`,`port`) )
  1083. ENGINE=InnoDB ", 'setup->tables' );
  1084. }
  1085. }
  1086. comment( "Done.", 7 );
  1087. }
  1088. }
  1089. ?>