· 9 years ago · Nov 28, 2016, 02:42 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 $blist_currencies = [];
149 var $blist_exchanges = [];
150 var $blist_markets = [];
151 var $blist_xmarkets = [];
152 var $config;
153 var $database;
154 var $debug = 'NOTICE';
155 var $exchange;
156 var $heartbeats = [];
157 var $heartbeat_count = 0;
158 var $market;
159 var $profile;
160 var $status;
161 var $xmarket;
162
163 /**
164 * Construct a new Investor
165 * @param $xmarket - Object(ExchangeMarket)
166 * @param $profile:String - The profile to use
167 * @param string|false $debug - Debug Setting
168 * @param null $id - Investor ID (defaults to randomly generated 12 character code)
169 * @param array $config - JSON Array of key:value Config Options
170 */
171 function __construct($xmarket, $profile, $debug='NOTICE', $id=null, $config=[])
172 {
173 if(!is_object($xmarket))
174 {
175 echo 'ExchangeMarket Invalid';nl();
176 echo vpre($xmarket);nl();
177 return false;
178 }
179
180 $this->id = $id;
181 $this->xmarket = $xmarket;
182 $this->exchange = $xmarket->name;
183 $this->market = $xmarket->currency_link;
184 $this->debug = $debug;
185 $this->profile = $profile;
186 $this->getDatabase();
187
188 if(empty($config->dry_run))$this->initialize($config); // Initialize Investor
189 else $this->configure($config); // Configure Only for Dry_Run Investors
190 }
191
192 function __destruct()
193 {
194
195 }
196
197 /** Analyze Market to ensure it matches CONFIG Settings
198 * @return bool
199 */
200 function analyze()
201 {
202 return $this->enforceAllConfig();
203 }
204
205 /** Save $this in the archive (INSERT)
206 *
207 */
208 function archive()
209 {
210 $fields =
211 [
212 'id'=>$this->id,
213 'exchange'=>$this->exchange,
214 'market'=>$this->market,
215 'config'=>json_encode($this->config),
216 'heartbeats'=>$this->heartbeats,
217 'heartbeat_count'=>count($this->heartbeats),
218 'debug'=>$this->debug,
219 ];
220
221 echo 'Investor:archive(): Archiving Investor';nl();
222 echo vpre($fields);
223 $this->database->update_or_insert('archive', $fields);
224 }
225
226 /** Change the status of the Investor and immediately save()
227 * @param $status - New Status being assigned to this Investor
228 */
229 function changeStatus($status)
230 {
231 $this->status = $status;
232 $this->heartbeat_count = 0;
233 $this->save();
234 }
235
236 /** Check if MAX_LOSS has been achieved
237 * @return true (MAX_LOSS Achieved) | false (MAX_LOSS Not Achieved)
238 */
239 function checkMaxLoss()
240 {
241 $setting = $this->config('MAX_LOSS');
242 if($setting === false)return false;
243 if((float)$this->getTripPriceDelta() <= (0 - ((float)$setting / 100)))return true;
244 return false;
245 }
246
247 /** Check if SUCCESS_STOP_THRESHOLD has been achieved
248 * @return true(SUCCESS_STOP_THRESHOLD Achieved) | false(SUCCESS_STOP_THRESHOLD Not Achieved)
249 */
250 function checkSuccessStopThreshold()
251 {
252 $success_stop_threshold = (float)$this->config('SUCCESS_STOP_THRESHOLD') / 100;
253 $success_threshold = (float)$this->config('SUCCESS_THRESHOLD') / 100;
254
255 if($this->getTripPriceDelta() <= $success_stop_threshold)return true;
256 if($this->getTripPriceDelta() < $success_threshold)return true;
257 if($this->getTripPriceDelta() > ($success_stop_threshold * 3))return true;
258 return false;
259 }
260
261 /** Check if SUCCESS_THRESHOLD has been achieved
262 * @return true(SUCCESS_THRESHOLD Achieved) | false(SUCCESS_THRESHOLD Not Achieved)
263 */
264 function checkSuccessThreshold()
265 {
266 $success_threshold = (float)$this->config('SUCCESS_THRESHOLD') / 100;
267 if(empty($success_threshold))return false;
268 if($this->getTripPriceDelta() >= $success_threshold)return true;
269 return false;
270 }
271
272 /** Check if $status_TTL has been achieved
273 * @param string - The status of the TTL setting to be checked
274 * @return true(TTL Achieved) | false(TTL Not Achieved)
275 */
276 function checkTTL($status)
277 {
278 $ttl = $this->config($status);
279 if($ttl === false)return false;
280 if((integer)$this->heartbeat_count > (integer)$ttl)return true;
281 return false;
282 }
283
284 /** Get OR Set $this->config settings
285 * @param $var - The config variable
286 * @param null $val (optional) - The value to set $var
287 * @return bool|$config->$var
288 */
289 function config($var, $val=null)
290 {
291 if(isset($val))return $this->setConfig($var, $val);
292 else return $this->getInstance()->config->$var;
293 }
294
295 /** Profile Instance Configuration
296 * @param $config
297 */
298 function configure($config)
299 {
300 if(!$this->exists())
301 {
302 // Configure New Investor
303 if(empty($config))$this->config = $this->getGlobalConfig();
304 else $this->config = $config;
305 }
306 else
307 {
308 // Reinitialize Investor
309 if(empty($config))$this->config = $this->getInstanceConfig();
310 else $this->config = $config;
311 }
312
313 // Apply Blacklist Settings
314 $this->blist_currencies = $this->getBlistCurrencies();
315 $this->blist_exchanges = $this->getBlistExchanges();
316 $this->blist_xmarkets = $this->getBlistXMarkets();
317 $this->blist_markets = $this->getBlistMarkets();
318 }
319
320 /** Enforce all config
321 * @return bool
322 */
323 function enforceAllConfig()
324 {
325 if(!$this->enforceBlacklists())
326 {
327 $this->log('BLACKLIST CONFIG - Enforced', "ExchangeMarket $this->exchange($this->market)", 'WARN');
328 return false;
329 }
330
331 if(!$this->enforceAnalysisConfig())
332 {
333 $this->log('ANALYSIS CONFIG - Enforced', "ExchangeMarket $this->exchange($this->market)", 'WARN');
334 return false;
335 }
336
337 if(!$this->enforceRangeConfig())
338 {
339 $this->log('RANGE CONFIG - Enforced', "ExchangeMarket $this->exchange($this->market)", 'WARN');
340 return false;
341 }
342
343 if(!$this->enforceMeanReversion())
344 {
345 $this->log('Mean Reversion CONFIG - Enforced', "ExchangeMarket $this->exchange($this->market)", 'WARN');
346 return false;
347 }
348
349 return true;
350 }
351
352 /** Enforce Analysis Config
353 * @return bool
354 */
355 function enforceAnalysisConfig()
356 {
357 // Enforce MIN_VOLUME_24H
358 if((float)$this->xmarket->volume_btc < (float)$this->config->MIN_VOLUME_24H)return false;
359
360 // @TODO: Add Additional ExchangeMarket Requirements Here
361 // -----------------------------------------------
362 // ex: Historical Trend Requirements
363 // ex: We need to dynamically Blacklist this ExchangeMarket due to multiple failed attempts
364 return true;
365 }
366
367 /** Enforce all blacklists
368 * @return bool
369 */
370 function enforceBlacklists()
371 {
372 // Enforce Blacklists
373 if($this->isExchangeMarketBlacklisted($this->exchange, $this->market))return false;
374 return true;
375 }
376
377 /** Enforce MEAN_REVERSION_OFFSET
378 * @return bool
379 */
380 function enforceMeanReversion()
381 {
382 if(!empty($this->config->MEAN_REVERSION_OFFSET))
383 {
384 $mean = (float)$this->xmarket->mean;
385 $offset = (float)$this->config->MEAN_REVERSION_OFFSET / 100;
386 $minimum_rate = $mean - ($offset * $mean);
387 $last = (float)$this->xmarket->last;
388
389 if($last > $minimum_rate)
390 {
391 $this->log
392 (
393 "MEAN_REVERSION - Enforced: Last($last) > MIN($minimum_rate)",
394 "Setting: $offset | Mean: $mean",
395 'ERROR'
396 );
397 return false;
398 }
399 }
400
401 return true;
402 }
403
404 /** Enforce all RANGE_<TIME> Settings
405 * @return bool
406 */
407 function enforceRangeConfig()
408 {
409 foreach($this->config as $setting => $range)
410 {
411 if(strpos($setting, 'RANGE_') === false || empty($range))continue;
412 if(strpos($setting, 'mo') !== false)$setting = str_replace('mo', 'M', $setting);
413
414 // Read RANGE_<time> Config Setting
415 $range_time = substr($setting, strpos($setting, 'RANGE_') + (strlen('RANGE_')));
416 $range_time_amount = (float)substr($range_time, 1);
417 $range_time_unit = substr($range_time, 0, 1);
418
419 // Determine Time Input in Minutes
420 if($range_time_unit === 's')$minutes_ago = $range_time_amount / 60;
421 else if($range_time_unit === 'm')$minutes_ago = $range_time_amount * 1;
422 else if($range_time_unit === 'h')$minutes_ago = $range_time_amount * 60;
423 else if($range_time_unit === 'd')$minutes_ago = $range_time_amount * 60 * 24;
424 else if($range_time_unit === 'w')$minutes_ago = $range_time_amount * 60 * 24 * 7;
425 else if($range_time_unit === 'M')$minutes_ago = $range_time_amount * 60 * 24 * 30;
426 else if($range_time_unit === 'y')$minutes_ago = $range_time_amount * 60 * 24 * 365;
427 else $minutes_ago = $range_time_amount * 1;
428
429 $range_low = (float)substr($range, 0, strpos($range, ':'));
430 $range_high = (float)substr($range, strpos($range, ':') + 1);
431 if($range_low >= $range_high || empty($range_low) || empty($range_high))
432 {
433 $this->log('Config Invalid: High > Low', 'Setting: '.$setting.' | Value: '.$range);
434 continue;
435 }
436
437 $delta = HistoryClientNew::get_delta
438 (
439 ["last","volume"],
440 [
441 'id'=>$this->xmarket->id,
442 'table_now'=>'exchange_markets',
443 'type'=>'exchange_market',
444 'time'=>0-$minutes_ago,
445 ]
446 );
447
448 if($delta === false || $delta < $range_low || $delta > $range_high)
449 {
450 $this->log
451 (
452 "RANGE_CONFIG - Enforced: Delta($minutes_ago m) = $delta%",
453 "Setting: $setting | Val: $range",
454 'ERROR'
455 );
456 return false;
457 }
458 else if($delta === true)$this->log('RANGE_CONFIG - No minutes', "Setting: $setting | Val: $range", 'ERROR');
459 else $this->log('RANGE_CONFIG - Passed', "Setting: $setting | Val: $range", 'ERROR');
460 }
461
462 return true;
463 }
464
465 /** Check if $this exists in active
466 * @return bool
467 */
468 function exists()
469 {
470 if($this->hasID() && $this->database->exists('active', 'id', ['id'=>$this->id]))return true;
471 return false;
472 }
473
474 /** Find the heartbeat that occurred $minutes ago
475 * @param int $minutes
476 * @return bool
477 */
478 function find_heartbeat_by_minutes_ago($minutes)
479 {
480 if($minutes > count($this->heartbeats))
481 {
482 echo 'Investor:find_heartbeat_by_minutes_ago(): Not enough Heartbeats exist!';nl();
483 return false;
484 }
485
486 $heartbeat = $this->heartbeats[count($this->heartbeats) - ($minutes + 1)];
487 return $heartbeat;
488 }
489
490 /** Get Blacklisted Currencies from Profile.blacklist_currency
491 * @return array
492 */
493 function getBlistCurrencies()
494 {
495 $blacklist = [];
496 $result = $this->database->get_rows("SELECT * FROM $this->profile.blacklist_currency");
497 foreach($result as $row)$blacklist[] = $row['currency'];
498 return $blacklist;
499 }
500
501 /** Get Blacklisted Exchanges from Profile.blacklist_exchange
502 * @return array
503 */
504 function getBlistExchanges()
505 {
506 $blacklist = [];
507 $result = $this->database->get_rows("SELECT * FROM $this->profile.blacklist_exchange");
508 foreach($result as $row)$blacklist[] = $row['exchange'];
509 return $blacklist;
510 }
511
512 /** Get Blacklisted ExchangeMarkets from Profile.blacklist_exchange_market
513 * @return array
514 */
515 function getBlistXMarkets()
516 {
517 $blacklist = [];
518 $result = $this->database->get_rows("SELECT * FROM $this->profile.blacklist_exchange_market");
519 foreach($result as $row)$blacklist[$row['exchange']] = $row['market'];
520 return $blacklist;
521 }
522
523 /** Get Blacklisted Markets from Profile.blacklist_market
524 * @return array
525 */
526 function getBlistMarkets()
527 {
528 $blacklist = [];
529 $result = $this->database->get_rows("SELECT * FROM $this->profile.blacklist_market");
530 foreach($result as $row)$blacklist[] = $row['market'];
531 return $blacklist;
532 }
533
534 /** Initialize Database Object
535 */
536 function getDatabase()
537 {
538 $this->database = Database::getInvestorInstance($this->profile);
539 }
540
541 /** Convert a $debug Setting |VERBOSE|NOTICE|WARN|ERROR| to int |0|1|2|3|
542 * @param $debug - The (string)$debug level to convert
543 * @return int
544 */
545 function getDebugLevel($debug)
546 {
547 if($debug === 'VERBOSE')return 0;
548 else if($debug === 'NOTICE')return 1;
549 else if($debug === 'WARN')return 2;
550 else if($debug === 'ERROR')return 3;
551 else return 0;
552 }
553
554 /** Get Default Global Config Options from Profile.config
555 * @return mixed
556 */
557 function getGlobalConfig()
558 {
559 $config = [];
560 $result = $this->database->get_rows("SELECT * FROM $this->profile.config");
561 foreach($result as $row)$config[$row['var']] = $row['val'];
562 return json_decode(json_encode($config));
563 }
564
565 /** Get Instance as Object from active
566 * @return Investor | bool
567 */
568 function getInstance()
569 {
570 $result = $this->database->get_row("SELECT * FROM $this->profile.active WHERE id='$this->id'");
571
572 if(empty($result))
573 {
574 $this->log('Instance Error', 'Unable to getInstance()', 'ERROR');return false;
575 }
576
577 $investorRow = [];
578 foreach($result as $var => $val)$investorRow[$var] = $val;
579
580 $name = $investorRow['exchange'];
581 $market = $investorRow['market'];
582 $exchangeMarket = getExchangeMarkets("name='$name' AND currency_link='$market'");
583
584 $investorRow['config'] = json_decode($investorRow['config']);
585 $investorRow['config']->dry_run = 'true';
586 $investorRow['heartbeats'] = json_decode($investorRow['heartbeats']);
587
588 $investor = new Investor
589 (
590 $exchangeMarket[0],
591 $this->profile,
592 $investorRow['debug'],
593 $investorRow['id'],
594 $investorRow['config']
595 );
596
597 $investor->heartbeats = $investorRow['heartbeats'];
598 $investor->heartbeat_count = $investorRow['heartbeat_count'];
599 return $investor;
600 }
601
602 /** Get Instance Config Options from $this->config from active
603 * @return Trip Config Object
604 */
605 function getInstanceConfig()
606 {
607 $config = [];
608 $result = $this->database->get_row("SELECT config FROM $this->profile.active WHERE id='$this->id'");
609 foreach($result as $row)foreach(json_decode($row['config']) as $var => $val)$config[$var] = $val;
610 return json_decode(json_encode($config));
611 }
612
613 /** Get the Price Delta in % since the Trip began
614 * @return float $price_delta | The change in price since the Trip began
615 */
616 function getTripPriceDelta()
617 {
618 $market_entrance = (float)$this->trip_data->market_price_entrance;
619 if(empty($this->trip_data->market_price_exit))$current_price = (float)$this->xmarket->last;
620 else $current_price = (float)$this->trip_data->market_price_exit;
621 return (float)(($current_price - $market_entrance) / $market_entrance);
622 }
623
624 /**
625 * Indicates if instance has an ID assigned
626 * @return bool
627 */
628 function hasID()
629 {
630 return !empty($this->id);
631 }
632
633 /** Monitor & Handle an active Trip
634 * @return Heartbeat Object
635 */
636 function heartbeat()
637 {
638 $this->heartbeat_count++;
639 $trip = $this->getInstance();
640
641 $heartbeat =
642 [
643 'date'=>get_date(),
644 'exchangeMarket'=> array
645 (
646 'name'=>$this->exchange,
647 'market'=>$this->market,
648 'currency_link'=>$this->market,
649 'buy'=>$this->xmarket->buy,
650 'sell'=>$this->xmarket->sell,
651 'last'=>$this->xmarket->last,
652 'volume_btc'=>$this->xmarket->volume_btc,
653 )
654 ];
655
656 $current_heartbeats = empty($trip->heartbeats)?[]:$trip->heartbeats;
657 $current_heartbeats[] = $heartbeat;
658
659 $this->heartbeats = [];
660 $this->config = $this->getGlobalConfig();
661 $this->save();
662
663 if($this->analyze())
664 {
665 nl();echo '-------------------------------------------------------------------';nl();
666 echo "Investor:heartbeat(): Spawning New InvestorProfile($this->profile)...";nl();
667 echo "Investor:heartbeat(): ExchangeMarket: $this->exchange($this->market)";nl();
668 echo "Investor:heartbeat(): Analysis Result: Passed";nl();
669 echo "Investor:heartbeat(): Starting Simulation...";nl();
670 $profile = getProfile("profile_name='$this->profile'");
671 $investor = new InvestorProfile($this->exchange, $this->market, $this->profile);
672 $investor->heartbeat($profile->engine);
673 }
674
675 return $heartbeat;
676 }
677
678 /** Initialize Profile
679 * @param $config - A JSON Array containing key:value CONFIG settings
680 */
681 function initialize($config)
682 {
683 $this->configure($config);
684 if(!empty($this->id))$this->log('Reinitializing', "Investor($this->id)");
685 $this->log('Initializing', "ExchangeMarket $this->exchange($this->market)", 'VERBOSE');
686
687 if(!$this->analyze())$this->__destruct();
688 $this->log('Analysis: Passed', "ExchangeMarket $this->exchange($this->market)", 'VERBOSE');
689 $this->id = "$this->exchange($this->market)";
690 $this->save('initialize');
691 }
692
693 /** Check if the Investor is "alive"
694 * @return true|false
695 */
696 function isAlive()
697 {
698 if($this->status === 'cancelled')return false;
699 if($this->status === 'loss')return false;
700 if($this->status === 'profit')return false;
701 if($this->status === 'error')return false;
702 return true;
703 }
704
705 /** Check if a Currency is blacklisted
706 * @param $currency_name
707 * @return bool
708 */
709 function isCurrencyBlacklisted($currency_name)
710 {
711 $this->blist_exchanges = $this->getBlistMarkets();
712 foreach($this->blist_currencies as $blist)if(strtolower($currency_name) === strtolower($blist))return true;
713 return false;
714 }
715
716 /** Check if the Investor is "dead"
717 * @return true|false
718 */
719 function isDead()
720 {
721 if($this->status === 'cancelled')return true;
722 if($this->status === 'loss')return true;
723 if($this->status === 'profit')return true;
724 if($this->status === 'error')return true;
725 return false;
726 }
727
728 /** Check if an exchange is blacklisted
729 * @param $exchange_name
730 * @return bool
731 */
732 function isExchangeBlacklisted($exchange_name)
733 {
734 $this->blist_exchanges = $this->getBlistExchanges();
735 foreach($this->blist_exchanges as $blist)if(strtolower($exchange_name) === strtolower($blist))return true;
736 return false;
737 }
738
739 /** Check if an ExchangeMarket is blacklisted
740 * @param $ex | Exchange Name
741 * @param $x | XMarket
742 * @return bool
743 */
744 function isExchangeMarketBlacklisted($ex, $x)
745 {
746 $this->blist_xmarkets = $this->getBlistXMarkets();
747 if($this->isExchangeBlacklisted($ex))return true;
748 if($this->isMarketBlacklisted($x))return true;
749 foreach($this->blist_xmarkets as $name => $m)if(strtolower($ex) === strtolower($name) && $x === $m)return true;
750 return false;
751 }
752
753 /** Check if a Market is blacklisted
754 * @param $market_name
755 * @return bool
756 */
757 function isMarketBlacklisted($market_name)
758 {
759 $this->blist_markets = $this->getBlistMarkets();
760 if($this->isCurrencyBlacklisted(get_base_currency($market_name)))return true;
761 if($this->isCurrencyBlacklisted(get_quote_currency($market_name)))return true;
762 foreach($this->blist_markets as $blist)if(strtolower($market_name) === strtolower($blist))return true;
763 return false;
764 }
765
766 /** Create a new log for this Investor
767 * ONLY IF if $this->getDebugLevel($type) >= $this->getDebugLevel($this->debug)
768 * @param $title
769 * @param $message
770 * @param string $type
771 */
772 function log($title, $message, $type='NOTICE')
773 {
774 if(empty($this->id))$this->id = "$this->exchange($this->market)";
775
776 if($this->getDebugLevel($type) >= $this->getDebugLevel($this->debug))
777 {
778 $fields =
779 [
780 'id'=>$this->id,
781 'title'=>$title,
782 'message'=>$message,
783 'type'=>$type,
784 ];
785
786 $profile = $this->database->link->real_escape_string($this->profile);
787 $id = $this->database->link->real_escape_string($fields['id']);
788 $title = $this->database->link->real_escape_string($fields['title']);
789 $message = $this->database->link->real_escape_string($fields['message']);
790 $type = $this->database->link->real_escape_string($fields['type']);
791
792 $this->database->link->query
793 (
794 "INSERT INTO $profile.log (id, title, message, type) VALUES ('$id', '$title', '$message', '$type')"
795 );
796 }
797 }
798
799 /** Refresh $this->exchange with fresh instance from database */
800 function refreshExchangeMarket()
801 {
802 $this->xmarket = getExchangeMarket($this->xmarket->name, $this->xmarket->currency_link);
803 }
804
805 /** Save $this in active (INSERT or UPDATE) */
806 function save()
807 {
808 $fields =
809 [
810 'id'=>$this->id,
811 'exchange'=>$this->exchange,
812 'market'=>$this->market,
813 'config'=>json_encode($this->config),
814 'heartbeats'=>json_encode($this->heartbeats),
815 'heartbeat_count'=>$this->heartbeat_count,
816 'debug'=>$this->debug
817 ];
818
819 $this->database->update_or_insert('active', $fields);
820 }
821
822 // TODO: Create SetBlacklist Methods
823
824 /** Set $this->config settings
825 * @param $var
826 * @param $val
827 * @return bool
828 */
829 function setConfig($var, $val)
830 {
831 if(!$this->exists())return false;
832 $this->config->$var = $val;
833 $this->save();
834 return true;
835 }
836
837 /** Get OR Set $this->status
838 * @param null $status
839 * @return bool
840 */
841 function status($status=null)
842 {
843 if(!$this->exists())return false;
844
845 if(empty($status))return $this->status;
846 else
847 {
848 $this->status = $status;
849 $this->save();
850 return true;
851 }
852 }
853}