· 9 years ago · Nov 28, 2016, 02:16 AM
1<?php
2
3/**
4 * Overview
5
6 * The Investor Class will analyze an ExchangeMarket by checking price change ranges at various time intervals
7(@see below: Investor Range Config).
8
9 * It will also enforce some trading CONFIG_SETTINGS (@see below: Investor Trading Engine Config) that will affect:
10 1.) minimum volume
11 2.) maximum loss
12 3.) minimum profit
13 *
14The Investor will attempt to make statistically favorable Investments by investing in volatile markets that meet
15the configured % price change range settings. We will be able to extend this base Investor class by adding
16historical trend analysis, dynamic blacklisting, etc.
17
18 * Goal
19 * The goal of the Investor Class is to increase the amount of BTC across our wallets by making use of market
20volatility and minimizing loss. We just need to be correct more than we are incorrect, by volume (monkey).
21
22 * Investor Trip States:
23 1.) initializing
24 * This state represents a Trip with zero heartbeats, AKA was just created
25 * The first order has not yet been placed
26 2.) sparking
27 * This state represents a Trip that has successfully placed its first order and is waiting for the order to be filled
28 * A Trip will remain in the "sparking" state until its initial order is completely filled
29 3.) waiting
30 * This state represents a Trip that has successfully filled its first order and is now waiting for market movement
31 * This is the equivalent of the beginning of an "Investment"
32 * All of the quote_currency is now available for trading
33 4.) success
34 * This state represents a Trip that has surpassed its SUCCESS_THRESHOLD (@see Config > SUCCESS_THRESHOLD)
35 * AKA: This Trip has already met its minimum profit and will continue to wait until SUCCESS_STOP_THRESHOLD is met
36 * A "success" Trip is in a state of maximum potential profit and in most cases will always end in the "profit" state
37 5.) exiting
38 * This state represents a Trip that has completed its "waiting" period and is now exiting the market
39 * This is not an instant process as the "sell" order must be filled to return to the start_currency
40 * Both "profit" and "loss" trips go through the "exiting" state after either SUCCESS_THRESHOLD OR MAX_LOSS is met
41 * If EXITING_TTL is met, the profile will switch to a more aggressive exiting strategy called "gtfo"
42 6.) gtfo (exit market ASAP)
43 * This state represents a Trip that has not been able to easily exit the market it is in
44 * This state will use a more aggressive strategy when attempting to sell all quote_currency bought in "sparking"
45 7.) cancelled (ended)
46 * This state represents a Trip that has been cancelled
47 * This can occur in multiple cases, some of which are the result of an error
48 * For more information about Trips ended in a "cancelled" state, reconcile exchange order data with orders table
49 8.) loss (ended)
50 * This state represents a Trip that has ended in a loss
51 * AKA: We lost money on this investment
52 9.) profit (ended)
53 * This state represents a Trip that has ended in a profit :D
54 * AKA: We made money on this investment
55
56 * Investor Trip Steps
57 1.) Verify ExchangeMarket matches filter CONFIG_SETTINGS
58 2.) If ExchangeMarket meets requirements, place Order in ExchangeMarket
59 3.) One of four things can happen:
60
61 Case One (profit):
62 a.) Order gets bought before SPARKING_TTL
63 b.) SUCCESS_THRESHOLD is achieved before WAITING_TTL (aka: we have made our target PROFIT on this Trip)
64 c.) We will continue to sit in the market until SUCCESS_STOP_THRESHOLD is achieved
65 d.) Exit the market ASAP
66
67 Case Two (profit OR loss):
68 a.) Order gets bought before SPARKING_TTL
69 b.) SUCCESS_THRESHOLD is not achieved before WAITING_TTL (aka: the order was bought but the price became stale)
70 c.) Exit the market as efficiently as possible (time is allowed so sell@sell and buy@buy)
71 d.) If EXITING_TTL is achieved, exit the market ASAP
72
73 Case Three (loss):
74 a.) Order gets bought before SPARKING_TTL
75 b.) MAX_LOSS is achieved before WAITING_TTL (aka: we LOST money on this Trip)
76 c.) Exit the market ASAP
77
78 Case Four (cancelled):
79 a.) Order does not get bought before SPARKING_TTL
80 b.) Trip cancels without any foreseeable loss OR gain
81
82 * Investor Trading Engine Config
83
84 * START_AMOUNT - The amount of BTC the bot will start with
85
86 * MIN_VOLUME_24H - The minimum 24h BTC volume for a market to be analyzed
87
88 * MAX_LOSS - The maximum % overall price can decrease by before executing stop-loss order
89
90 * SPARKING_TTL - The amount of heartbeats a "sparking" Investor Trip can sustain before dying
91
92 * WAITING_TTL- The amount of heartbeats a "waiting" Investor Trip can sustain before dying
93
94 * EXITING_TTL- The amount of heartbeats a "exiting" Investor Trip can sustain before dying
95
96 * GTFO_TTL - The amount of heartbeats a "gtfo" Investor Trip can sustain before dying
97
98 * SUCCESS_TTL - The amount of heartbeats a "success" Investor Trip can sustain before dying
99
100 * SUCCESS_THRESHOLD - The minimum amount of % profit for the Trip to enter the "success" state
101
102 * SUCCESS_STOP_THRESHOLD - The minimum % of price decrease a Successful Trip can decrease by at intervals:
103 1.) 5m
104 2.) 30m
105 3.) 1h
106 4.) $trip->success_price (the price of SUCCESS_THRESHOLD)
107
108@-----------------------------------------------IMPORTANT--------------------------------------------------------------@
109
110 * Investor Range Config
111
112 * The Investor Range Analysis Config is used to configure the the type of trend that the Investor uses to detect a
113potentially profitable Trip. An unlimited amount of RANGE_[time] values may be added to this Profile's config table.
114
115 * So what is a range setting?
116
117 * An Investor Range Configuration is a time interval and a range of price change tolerance
118 * The time interval of any particular Range Setting is set using the actual name of the setting (RANGE_[time])
119 * This [time] portion of the setting name can be any time interval starting with the letter code of the time unit
120 * m = minute
121 * h = hour
122 * d = day
123 * M = month
124 * y = year
125 *
126 * The value of this setting is a range of two numbers with the smaller number always on the left side
127 * These two numbers are separated by a colon
128 *
129 * These numbers represent the minimum(left) and maximum(right) price change an ExchangeMarket must have to invest in
130 * AKA: RANGE_m5 = -5.0:5.0 | In the last 5 minutes, the price change must be between -5% and 5%
131 *
132 * See Below for Range Config Examples
133 * RANGE_m5 - A range of % price increase for price_m5 separated by colon (ex: -5.00:5.50)
134 * RANGE_m30 - A range of % price increase for price_m30 separated by colon (ex: -5.00:5.50)
135 * RANGE_h1 - A range of % price increase for price_h1 separated by colon (ex: -5.00:5.50)
136 * RANGE_h2 - A range of % price increase for price_h2 separated by colon (ex: -5.00:5.50)
137 * RANGE_h4 - A range of % price increase for price_h4 separated by colon (ex: -5.00:5.50)
138 * RANGE_h8 - A range of % price increase for price_h8 separated by colon (ex: -5.00:5.50)
139 * RANGE_h12 - A range of % price increase for price_h12 separated by colon (ex: -5.00:5.50)
140 * RANGE_h16 - A range of % price increase for price_h16 separated by colon (ex: -5.00:5.50)
141 * RANGE_h20 - A range of % price increase for price_h20 separated by colon (ex: -5.00:5.50)
142 * RANGE_h24 - A range of % price increase for price_h24 separated by colon (ex: -5.00:5.50)
143 * RANGE_d7 - A range of % price increase for price_d7 separated by colon (ex: -5.00:5.50)
144 */
145class Investor
146{
147 var $id;
148 var $xmarket;
149 var $exchange;
150 var $market;
151 var $config;
152 var $heartbeats = [];
153 var $heartbeat_count = 0;
154 var $debug = 'NOTICE';
155 var $status;
156
157 var $database;
158 var $profile;
159
160 var $blist_currencies = [];
161 var $blist_exchanges = [];
162 var $blist_xmarkets = [];
163 var $blist_markets = [];
164
165 /**
166 * Construct a new Investor
167 * @param $xmarket - Object(ExchangeMarket)
168 * @param $profile:String - The profile to use
169 * @param string|false $debug - Debug Setting
170 * @param null $id - Investor ID (defaults to randomly generated 12 character code)
171 * @param array $config - JSON Array of key:value Config Options
172 */
173 function __construct($xmarket, $profile, $debug='NOTICE', $id=NULL, $config=[])
174 {
175 if(!is_object($xmarket))
176 {
177 echo 'ExchangeMarket Invalid';nl();
178 echo vpre($xmarket);nl();
179 return false;
180 }
181
182 $this->id = $id;
183 $this->xmarket = $xmarket;
184 $this->exchange = $xmarket->name;
185 $this->market = $xmarket->currency_link;
186 $this->debug = $debug;
187 $this->profile = $profile;
188 $this->getDatabase();
189
190 if(empty($config->dry_run))$this->initialize($config); // Initialize Investor
191 else $this->configure($config); // Configure Only for Dry_Run Investors
192 }
193
194 function __destruct()
195 {
196
197 }
198
199 /** Analyze Market to ensure it matches CONFIG Settings
200 * @return bool
201 */
202 function analyze()
203 {
204 return $this->enforceAllConfig();
205 }
206
207 /** Save $this in the archive (INSERT)
208 *
209 */
210 function archive()
211 {
212 $fields =
213 [
214 'id'=>$this->id,
215 'exchange'=>$this->exchange,
216 'market'=>$this->market,
217 'config'=>json_encode($this->config),
218 'heartbeats'=>$this->heartbeats,
219 'heartbeat_count'=>count($this->heartbeats),
220 'debug'=>$this->debug,
221 ];
222
223 echo 'Investor:archive(): Archiving Investor';nl();
224 echo vpre($fields);
225 $this->database->update_or_insert('archive', $fields);
226 }
227
228 /** Change the status of the Investor and immediately save()
229 * @param $status - New Status being assigned to this Investor
230 */
231 function changeStatus($status)
232 {
233 $this->status = $status;
234 $this->heartbeat_count = 0;
235 $this->save();
236 }
237
238 /** Check if MAX_LOSS has been achieved
239 * @return true (MAX_LOSS Achieved) | false (MAX_LOSS Not Achieved)
240 */
241 function checkMaxLoss()
242 {
243 $setting = $this->config('MAX_LOSS');
244 if($setting === false)return false;
245 if((float)$this->getTripPriceDelta() <= (0 - ((float)$setting / 100)))return true;
246 return false;
247 }
248
249 /** Check if SUCCESS_STOP_THRESHOLD has been achieved
250 * @return true(SUCCESS_STOP_THRESHOLD Achieved) | false(SUCCESS_STOP_THRESHOLD Not Achieved)
251 */
252 function checkSuccessStopThreshold()
253 {
254 $success_stop_threshold = (float)$this->config('SUCCESS_STOP_THRESHOLD') / 100;
255 $success_threshold = (float)$this->config('SUCCESS_THRESHOLD') / 100;
256
257 if($this->getTripPriceDelta() <= $success_stop_threshold)return true;
258 if($this->getTripPriceDelta() < $success_threshold)return true;
259 if($this->getTripPriceDelta() > ($success_stop_threshold * 3))return true;
260 return false;
261 }
262
263 /** Check if SUCCESS_THRESHOLD has been achieved
264 * @return true(SUCCESS_THRESHOLD Achieved) | false(SUCCESS_THRESHOLD Not Achieved)
265 */
266 function checkSuccessThreshold()
267 {
268 $success_threshold = (float)$this->config('SUCCESS_THRESHOLD') / 100;
269 if(empty($success_threshold))return false;
270 if($this->getTripPriceDelta() >= $success_threshold)return true;
271 return false;
272 }
273
274 /** Check if $status_TTL has been achieved
275 * @param string - The status of the TTL setting to be checked
276 * @return true(TTL Achieved) | false(TTL Not Achieved)
277 */
278 function checkTTL($status)
279 {
280 $ttl = $this->config($status);
281 if($ttl === false)return false;
282 if((integer)$this->heartbeat_count > (integer)$ttl)return true;
283 return false;
284 }
285
286 /** Get OR Set $this->config settings
287 * @param $var - The config variable
288 * @param null $val (optional) - The value to set $var
289 * @return bool|$config->$var
290 */
291 function config($var, $val=NULL)
292 {
293 if(isset($val))return $this->setConfig($var, $val);
294 else return $this->getInstance()->config->$var;
295 }
296
297 /** Profile Instance Configuration
298 * @param $config
299 */
300 function configure($config)
301 {
302 if(!$this->exists())
303 {
304 // Configure New Investor
305 if(empty($config))$this->config = $this->getGlobalConfig();
306 else $this->config = $config;
307 }
308 else
309 {
310 // Reinitialize Investor
311 if(empty($config))$this->config = $this->getInstanceConfig();
312 else $this->config = $config;
313 }
314
315 // Apply Blacklist Settings
316 $this->blist_currencies = $this->getBlistCurrencies();
317 $this->blist_exchanges = $this->getBlistExchanges();
318 $this->blist_xmarkets = $this->getBlistXMarkets();
319 $this->blist_markets = $this->getBlistMarkets();
320 }
321
322 /** Enforce all config
323 * @return bool
324 */
325 function enforceAllConfig()
326 {
327 if(!$this->enforceBlacklists())
328 {
329 $this->log('BLACKLIST CONFIG - Enforced', "ExchangeMarket $this->exchange($this->market)", 'WARN');
330 return false;
331 }
332
333 if(!$this->enforceAnalysisConfig())
334 {
335 $this->log('ANALYSIS CONFIG - Enforced', "ExchangeMarket $this->exchange($this->market)", 'WARN');
336 return false;
337 }
338
339 if(!$this->enforceRangeConfig())
340 {
341 $this->log('RANGE CONFIG - Enforced', "ExchangeMarket $this->exchange($this->market)", 'WARN');
342 return false;
343 }
344
345 if(!$this->enforceMeanReversion())
346 {
347 $this->log('Mean Reversion CONFIG - Enforced', "ExchangeMarket $this->exchange($this->market)", 'WARN');
348 return false;
349 }
350
351 return true;
352 }
353
354 /** Enforce Analysis Config
355 * @return bool
356 */
357 function enforceAnalysisConfig()
358 {
359 // Enforce MIN_VOLUME_24H
360 if((float)$this->xmarket->volume_btc < (float)$this->config->MIN_VOLUME_24H)return false;
361
362 // @TODO: Add Additional ExchangeMarket Requirements Here
363 // -----------------------------------------------
364 // ex: Historical Trend Requirements
365 // ex: We need to dynamically Blacklist this ExchangeMarket due to multiple failed attempts
366 return true;
367 }
368
369 /** Enforce all blacklists
370 * @return bool
371 */
372 function enforceBlacklists()
373 {
374 // Enforce Blacklists
375 if($this->isExchangeMarketBlacklisted($this->exchange, $this->market))return false;
376 return true;
377 }
378
379 /** Enforce MEAN_REVERSION_OFFSET
380 * @return bool
381 */
382 function enforceMeanReversion()
383 {
384 if(!empty($this->config->MEAN_REVERSION_OFFSET))
385 {
386 $mean = (float)$this->xmarket->mean;
387 $offset = (float)$this->config->MEAN_REVERSION_OFFSET / 100;
388 $minimum_rate = $mean - ($offset * $mean);
389 $last = (float)$this->xmarket->last;
390
391 if($last > $minimum_rate)
392 {
393 $this->log
394 (
395 "MEAN_REVERSION - Enforced: Last($last) > MIN($minimum_rate)",
396 "Setting: $offset | Mean: $mean",
397 'ERROR'
398 );
399 return false;
400 }
401 }
402
403 return true;
404 }
405
406 /** Enforce all RANGE_<TIME> Settings
407 * @return bool
408 */
409 function enforceRangeConfig()
410 {
411 foreach($this->config as $setting => $range)
412 {
413 if(strpos($setting, 'RANGE_') === false || empty($range))continue;
414 if(strpos($setting, 'mo') !== false)$setting = str_replace('mo', 'M', $setting);
415
416 // Read RANGE_<time> Config Setting
417 $range_time = substr($setting, strpos($setting, 'RANGE_') + (strlen('RANGE_')));
418 $range_time_amount = (float)substr($range_time, 1);
419 $range_time_unit = substr($range_time, 0, 1);
420
421 // Determine Time Input in Minutes
422 if($range_time_unit === 's')$minutes_ago = $range_time_amount / 60;
423 else if($range_time_unit === 'm')$minutes_ago = $range_time_amount * 1;
424 else if($range_time_unit === 'h')$minutes_ago = $range_time_amount * 60;
425 else if($range_time_unit === 'd')$minutes_ago = $range_time_amount * 60 * 24;
426 else if($range_time_unit === 'w')$minutes_ago = $range_time_amount * 60 * 24 * 7;
427 else if($range_time_unit === 'M')$minutes_ago = $range_time_amount * 60 * 24 * 30;
428 else if($range_time_unit === 'y')$minutes_ago = $range_time_amount * 60 * 24 * 365;
429 else $minutes_ago = $range_time_amount * 1;
430
431 $range_low = (float)substr($range, 0, strpos($range, ':'));
432 $range_high = (float)substr($range, strpos($range, ':') + 1);
433 if($range_low >= $range_high || empty($range_low) || empty($range_high))
434 {
435 $this->log('Config Invalid: High > Low', 'Setting: '.$setting.' | Value: '.$range);
436 continue;
437 }
438
439 $delta = get_price_delta_by_minutes($minutes_ago, $this->xmarket);
440
441 if($delta === false || $delta < $range_low || $delta > $range_high)
442 {
443 $this->log
444 (
445 "RANGE_CONFIG - Enforced: Delta($minutes_ago m) = $delta%",
446 "Setting: $setting | Val: $range",
447 'ERROR'
448 );
449 return false;
450 }
451 else if($delta === true)$this->log('RANGE_CONFIG - No minutes', "Setting: $setting | Val: $range", 'ERROR');
452 else $this->log('RANGE_CONFIG - Passed', "Setting: $setting | Val: $range", 'ERROR');
453 }
454
455 return true;
456 }
457
458 /** Check if $this exists in active
459 * @return bool
460 */
461 function exists()
462 {
463 if($this->hasID() && $this->database->exists('active', 'id', ['id'=>$this->id]))return true;
464 return false;
465 }
466
467 /** Find the heartbeat that occurred $minutes ago
468 * @param int $minutes
469 * @return bool
470 */
471 function find_heartbeat_by_minutes_ago($minutes)
472 {
473 if($minutes > count($this->heartbeats))
474 {
475 echo 'Investor:find_heartbeat_by_minutes_ago(): Not enough Heartbeats exist!';nl();
476 return false;
477 }
478
479 $heartbeat = $this->heartbeats[count($this->heartbeats) - ($minutes + 1)];
480 return $heartbeat;
481 }
482
483 /** Get Blacklisted Currencies from Profile.blacklist_currency
484 * @return array
485 */
486 function getBlistCurrencies()
487 {
488 $blacklist = [];
489 $result = $this->database->get_rows_assoc("SELECT * FROM $this->profile.blacklist_currency");
490 foreach($result as $row)$blacklist[] = $row['currency'];
491 return $blacklist;
492 }
493
494 /** Get Blacklisted Exchanges from Profile.blacklist_exchange
495 * @return array
496 */
497 function getBlistExchanges()
498 {
499 $blacklist = [];
500 $result = $this->database->get_rows_assoc("SELECT * FROM $this->profile.blacklist_exchange");
501 foreach($result as $row)$blacklist[] = $row['exchange'];
502 return $blacklist;
503 }
504
505 /** Get Blacklisted ExchangeMarkets from Profile.blacklist_exchange_market
506 * @return array
507 */
508 function getBlistXMarkets()
509 {
510 $blacklist = [];
511 $result = $this->database->get_rows_assoc("SELECT * FROM $this->profile.blacklist_exchange_market");
512 foreach($result as $row)$blacklist[$row['exchange']] = $row['market'];
513 return $blacklist;
514 }
515
516 /** Get Blacklisted Markets from Profile.blacklist_market
517 * @return array
518 */
519 function getBlistMarkets()
520 {
521 $blacklist = [];
522 $result = $this->database->get_rows_assoc("SELECT * FROM $this->profile.blacklist_market");
523 foreach($result as $row)$blacklist[] = $row['market'];
524 return $blacklist;
525 }
526
527 /** Initialize Database Object
528 */
529 function getDatabase()
530 {
531 $this->database = Database::getInvestorInstance($this->profile);
532 }
533
534 /** Convert a $debug Setting |VERBOSE|NOTICE|WARN|ERROR| to int |0|1|2|3|
535 * @param $debug - The (string)$debug level to convert
536 * @return int
537 */
538 function getDebugLevel($debug)
539 {
540 if($debug === 'VERBOSE')$debug_level = 0;
541 else if($debug === 'NOTICE')$debug_level = 1;
542 else if($debug === 'WARN')$debug_level = 2;
543 else if($debug === 'ERROR')$debug_level = 3;
544 else $debug_level = 0;
545 return $debug_level;
546 }
547
548 /** Get Default Global Config Options from Profile.config
549 * @return mixed
550 */
551 function getGlobalConfig()
552 {
553 $config = [];
554 $result = $this->database->get_rows_assoc("SELECT * FROM $this->profile.config");
555 foreach($result as $row)$config[$row['var']] = $row['val'];
556 return json_decode(json_encode($config));
557 }
558
559 /** Get Instance as Object from active
560 * @return Investor Object
561 */
562 function getInstance()
563 {
564 $id = $this->id;
565 $profile = $this->profile;
566 $result = $this->database->get_row("SELECT * FROM $profile.active WHERE id='$id'");
567
568 if(empty($result))
569 {
570 $this->log('Instance Error', 'Unable to getInstance()', 'ERROR');return false;
571 }
572
573 $investorRow = [];
574 foreach($result as $var => $val)$investorRow[$var] = $val;
575
576 $name = $investorRow['exchange'];
577 $market = $investorRow['market'];
578 $exchangeMarket = getExchangeMarkets("name='$name' AND currency_link='$market'");
579
580 $investorRow['config'] = json_decode($investorRow['config']);
581 $investorRow['config']->dry_run = 'true';
582 $investorRow['heartbeats'] = json_decode($investorRow['heartbeats']);
583
584 $investor = new Investor
585 (
586 $exchangeMarket[0],
587 $this->profile,
588 $investorRow['debug'],
589 $investorRow['id'],
590 $investorRow['config']
591 );
592
593 $investor->heartbeats = $investorRow['heartbeats'];
594 $investor->heartbeat_count = $investorRow['heartbeat_count'];
595 return $investor;
596 }
597
598 /** Get Instance Config Options from $this->config from active
599 * @return Trip Config Object
600 */
601 function getInstanceConfig()
602 {
603 $config = [];
604 $result = $this->database->get_row("SELECT config FROM $this->profile.active WHERE id='$this->id'");
605 foreach($result as $row)foreach(json_decode($row['config']) as $var => $val)$config[$var] = $val;
606 return json_decode(json_encode($config));
607 }
608
609 /** Get the Price Delta in % since the Trip began
610 * @return float $price_delta | The change in price since the Trip began
611 */
612 function getTripPriceDelta()
613 {
614 $market_entrance = (float)$this->trip_data->market_price_entrance;
615 if(empty($this->trip_data->market_price_exit))$current_price = (float)$this->xmarket->last;
616 else $current_price = (float)$this->trip_data->market_price_exit;
617 return (float)(($current_price - $market_entrance) / $market_entrance);
618 }
619
620 /**
621 * Indicates if instance has an ID assigned
622 * @return bool
623 */
624 function hasID()
625 {
626 return !empty($this->id);
627 }
628
629 /** Monitor & Handle an active Trip
630 * @return Heartbeat Object
631 */
632 function heartbeat()
633 {
634 $this->heartbeat_count++;
635 $trip = $this->getInstance();
636
637 $heartbeat =
638 [
639 'date'=>get_date(),
640 'exchangeMarket'=> array
641 (
642 'name'=>$this->exchange,
643 'market'=>$this->market,
644 'currency_link'=>$this->market,
645 'buy'=>$this->xmarket->buy,
646 'sell'=>$this->xmarket->sell,
647 'last'=>$this->xmarket->last,
648 'volume_btc'=>$this->xmarket->volume_btc,
649 )
650 ];
651
652 $current_heartbeats = empty($trip->heartbeats)?[]:$trip->heartbeats;
653 $current_heartbeats[] = $heartbeat;
654
655 $this->heartbeats = [];
656 $this->config = $this->getGlobalConfig();
657 $this->save();
658
659 if($this->analyze())
660 {
661 nl();echo '-------------------------------------------------------------------';nl();
662 echo "Investor:heartbeat(): Spawning New InvestorProfile($this->profile)...";nl();
663 echo "Investor:heartbeat(): ExchangeMarket: $this->exchange($this->market)";nl();
664 echo "Investor:heartbeat(): Analysis Result: Passed";nl();
665 echo "Investor:heartbeat(): Starting Simulation...";nl();
666 $profile = getProfile("profile_name='$this->profile'");
667 $investor = new InvestorProfile($this->exchange, $this->market, $this->profile);
668 $investor->heartbeat($profile->engine);
669 }
670
671 return $heartbeat;
672 }
673
674 /** Initialize Profile
675 * @param $config - A JSON Array containing key:value CONFIG settings
676 */
677 function initialize($config)
678 {
679 $this->configure($config);
680 if(!empty($this->id))$this->log('Reinitializing', "Investor($this->id)");
681 $this->log('Initializing', "ExchangeMarket $this->exchange($this->market)", 'VERBOSE');
682
683 if(!$this->analyze())$this->__destruct();
684 $this->log('Analysis: Passed', "ExchangeMarket $this->exchange($this->market)", 'VERBOSE');
685 $this->id = "$this->exchange($this->market)";
686 $this->save('initialize');
687 }
688
689 /** Check if the Investor is "alive"
690 * @return true|false
691 */
692 function isAlive()
693 {
694 if($this->status === 'cancelled')return false;
695 if($this->status === 'loss')return false;
696 if($this->status === 'profit')return false;
697 if($this->status === 'error')return false;
698 return true;
699 }
700
701 /** Check if a Currency is blacklisted
702 * @param $currency_name
703 * @return bool
704 */
705 function isCurrencyBlacklisted($currency_name)
706 {
707 $this->blist_exchanges = $this->getBlistMarkets();
708 foreach($this->blist_currencies as $blist)if(strtolower($currency_name) === strtolower($blist))return true;
709 return false;
710 }
711
712 /** Check if the Investor is "dead"
713 * @return true|false
714 */
715 function isDead()
716 {
717 if($this->status === 'cancelled')return true;
718 if($this->status === 'loss')return true;
719 if($this->status === 'profit')return true;
720 if($this->status === 'error')return true;
721 return false;
722 }
723
724 /** Check if an exchange is blacklisted
725 * @param $exchange_name
726 * @return bool
727 */
728 function isExchangeBlacklisted($exchange_name)
729 {
730 $this->blist_exchanges = $this->getBlistExchanges();
731 foreach($this->blist_exchanges as $blist)if(strtolower($exchange_name) === strtolower($blist))return true;
732 return false;
733 }
734
735 /** Check if an ExchangeMarket is blacklisted
736 * @param $ex | Exchange Name
737 * @param $x | XMarket
738 * @return bool
739 */
740 function isExchangeMarketBlacklisted($ex, $x)
741 {
742 $this->blist_xmarkets = $this->getBlistXMarkets();
743 if($this->isExchangeBlacklisted($ex))return true;
744 if($this->isMarketBlacklisted($x))return true;
745 foreach($this->blist_xmarkets as $name => $m)if(strtolower($ex) === strtolower($name) && $x === $m)return true;
746 return false;
747 }
748
749 /** Check if a Market is blacklisted
750 * @param $market_name
751 * @return bool
752 */
753 function isMarketBlacklisted($market_name)
754 {
755 $this->blist_markets = $this->getBlistMarkets();
756 if($this->isCurrencyBlacklisted(get_base_currency($market_name)))return true;
757 if($this->isCurrencyBlacklisted(get_quote_currency($market_name)))return true;
758 foreach($this->blist_markets as $blist)if(strtolower($market_name) === strtolower($blist))return true;
759 return false;
760 }
761
762 /** Create a new log for this Investor
763 * ONLY IF if $this->getDebugLevel($type) >= $this->getDebugLevel($this->debug)
764 * @param $title
765 * @param $message
766 * @param string $type
767 */
768 function log($title, $message, $type='NOTICE')
769 {
770 if(empty($this->id))$this->id = "$this->exchange($this->market)";
771
772 if($this->getDebugLevel($type) >= $this->getDebugLevel($this->debug))
773 {
774 $fields =
775 [
776 'id'=>$this->id,
777 'title'=>$title,
778 'message'=>$message,
779 'type'=>$type,
780 ];
781
782 $profile = $this->database->link->real_escape_string($this->profile);
783 $id = $this->database->link->real_escape_string($fields['id']);
784 $title = $this->database->link->real_escape_string($fields['title']);
785 $message = $this->database->link->real_escape_string($fields['message']);
786 $type = $this->database->link->real_escape_string($fields['type']);
787
788 $this->database->link->query
789 (
790 "INSERT INTO $profile.log (id, title, message, type) VALUES ('$id', '$title', '$message', '$type')"
791 );
792 }
793 }
794
795 /** Refresh $this->exchange with fresh instance from database */
796 function refreshExchangeMarket()
797 {
798 $this->xmarket = getExchangeMarket($this->xmarket->name, $this->xmarket->currency_link);
799 }
800
801 /** Save $this in active (INSERT or UPDATE) */
802 function save()
803 {
804 $fields =
805 [
806 'id'=>$this->id,
807 'exchange'=>$this->exchange,
808 'market'=>$this->market,
809 'config'=>json_encode($this->config),
810 'heartbeats'=>json_encode($this->heartbeats),
811 'heartbeat_count'=>$this->heartbeat_count,
812 'debug'=>$this->debug
813 ];
814
815 $this->database->update_or_insert('active', $fields);
816 }
817
818 // TODO: Create SetBlacklist Methods
819
820 /** Set $this->config settings
821 * @param $var
822 * @param $val
823 * @return bool
824 */
825 function setConfig($var, $val)
826 {
827 if(!$this->exists())return false;
828 $this->config->$var = $val;
829 $this->save();
830 return true;
831 }
832
833 /** Get OR Set $this->status
834 * @param null $status
835 * @return bool
836 */
837 function status($status=null)
838 {
839 if(!$this->exists())return false;
840
841 if(empty($status))return $this->status;
842 else
843 {
844 $this->status = $status;
845 $this->save();
846 return true;
847 }
848 }
849}