Универсальная аналитическая машина

Несколь­ко лет назад была насто­я­щая крип­то­ли­хо­рад­ка: люди поку­па­ли и про­да­ва­ли бит­ко­и­ны, май­ни­ли эфир, ску­па­ли видео­кар­ты и царил дух аван­тю­риз­ма. В те вре­ме­на все иска­ли про­ве­рен­ные источ­ни­ки, инсай­ды и ана­ли­ти­ку, кото­рые помо­гут при­нять вер­ное реше­ние о покупке.

Как и все­гда, про­гно­зы — дело небла­го­дар­ное, и про­гноз из серии «Паль­цем в небо» будет вся­ко точ­нее. Поэто­му мы реши­ли авто­ма­ти­зи­ро­вать этот про­цесс: сде­лать уни­вер­саль­ную ана­ли­ти­че­скую маши­ну, кото­рая спо­соб­на про­гно­зи­ро­вать что угод­но, от кур­са дол­ла­ра до марш­ру­та лосо­ся на нерест. Метод про­гно­зи­ро­ва­ния хоро­шо изу­чен — наугад.

Сей­час наша маши­на уме­ет пред­ска­зы­вать пове­де­ние кур­са крип­то­ва­лю­ты в бли­жай­шей перспективе:

Собрав такую же маши­ну у себя, вы смо­же­те пред­ска­зы­вать что угод­но. Вот как это сделать. 

Что происходит на самом деле

Вся эта исто­рия про ана­ли­ти­че­скую маши­ну — пол­ный фейк. Мы при­ду­ма­ли этот про­ект от нача­ла и до кон­ца, и логи­ка была такая:

  1. Пока­зать кра­си­вый прогресс-бар.
  2. Запу­стить его по кноп­ке, что­бы он начал работать.
  3. Пока он дви­жет­ся, выво­дим вся­кие сооб­ще­ния, что­бы создать види­мость рабо­ты. Очень важ­но, что­бы чело­век на том кон­це думал, что маши­на реаль­но что-то прогнозирует.
  4. В кон­це пока­зы­ва­ем какое-то сооб­ще­ние, мол, вот ваш про­гноз. Сооб­ще­ние может быть совер­шен­но ран­дом­ным, на каче­ство про­гно­за это не влияет.

В ито­ге полу­чи­лась маши­на, кото­рая про­сто пока­зы­ва­ет вам кра­си­вый прогресс-бар и выда­ёт в ито­ге ран­дом­ную фра­зу. В прин­ци­пе, ничем не хуже боль­шин­ства про­гноз­ных систем, толь­ко мы чест­но об этом говорим.

Зачем этот проект

Этот про­ект — упраж­не­ние в глав­ной состав­ля­ю­щей про­грам­мист­ско­го дзе­на: бери чужое, вкру­чи­вай в своё. Мы будем поль­зо­вать­ся гото­вы­ми чужи­ми реше­ни­я­ми и полу­чим в ито­ге рабо­та­ю­щий и доволь­но полез­ный продукт.

Прогресс-бар

Сде­лать кра­си­вый прогресс-бар — дело не пяти минут, поэто­му мы поис­ка­ли уже гото­вые реше­ния в Сети и нашли вот такое:

Там было почти всё, что нам нуж­но, кро­ме двух вещей:

  • прогресс-бар запол­нял­ся слиш­ком быстро,
  • не меня­лись над­пи­си в про­цес­се работы.

Мы не будем пока­зы­вать весь исход­ный код, пото­му что мож­но зай­ти в началь­ный про­ект и посмот­реть — вме­сто это­го пока­жем, что поменяли.

Замед­ля­ем прогресс-бар. Смот­рим в function preload(imgArray):

Было:

$(imgArray).each(function() { $('<img>').attr("src", this).load(function() { $progress.animate({ width: "+=" + increment + "%" }, 100); });

Ста­ло:

var i = 0;     while (i < 300) { $progress.animate({ width: "+=" + 0.25 + "%" }, 100);          i = i+1;              }

Вме­сто того, что­бы уве­ли­чи­вать про­гресс на какую-то непо­нят­ную вели­чи­ну, мы при­бав­ля­ем к нему на каж­дом шаге все­го по 0.25 пунк­та. Это мало, поэто­му дви­гать­ся будет мед­лен­но. Чис­ло 300 в цик­ле мы подо­бра­ли опыт­ным путём: почему-то если при­бав­лять по 0.25, то имен­но за 300 шагов мы набе­рём 100%.

Выво­дим сооб­ще­ния по ходу рабо­ты. Смот­рим в ту же функцию.

Было: ниче­го, пото­му что в исход­ном про­ек­те это­го не требовалось.

Ста­ло:

if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 1) {
  $('span').text(s1_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 20) {
  $('span').text(s20_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 40) {
  $('span').text(s40_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 60) {
  $('span').text(s60_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 80) {
  $('span').text(s80_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 100) {
  yes = false;
  $('span').text('Примерная точность прогноза: ' + percent_final + '%');
  $('p').text(result_final);
}

Пер­вые 5 раз мы срав­ни­ва­ем пока­за­те­ли про­цен­тов на шка­ле с каким-то чис­лом и если сов­па­да­ем — выво­дим то, что лежит в пере­мен­ной. В послед­нем срав­не­нии выда­ём про­цен­ты и финаль­ный текст. Про пере­мен­ные рас­ска­жем ниже.

Готовим текст

У нас уже был про­ект, где мы гене­ри­ро­ва­ли текст слу­чай­ным обра­зом. Возь­мём отту­да немно­го кода и пере­пи­шем для себя:

// этапы в прогресс-баре
var s1 = ['Собираю данные из интернета', 'Читаю Википедию', 'Смотрю паблики во ВКонтакте'];
var s20 = ['Обучаю нейросеть', 'Анализирую увиденное', 'Выделяю важное'];
var s40 = ['Готовлю технический анализ', 'Думаю о будущем', 'Осознаю важность момента'];
var s60 = ['Постулирую неведомое', 'Инкапсулирую полиморфизм', 'Кристаллизую суть'];
var s80 = ['Тыкаю пальцем в небо', 'Формализую данность как класс', 'Удаляю противоречия и вношу ясность'];
var result = ['Сначала крипта подрастет, потом обвалится. Потом снова подрастет, потом снова обвалится.', 'Можно закупать, хотя лучше сначала продать, но если важнее купить, то можно и не продавать', 'По всем показателям рецессия будет следовать за отскоком, но это вряд ли отразится на текущей ситуации, поэтому необходимо обратить внимание на волатильность.'];
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
// готовим итоговые значения
var s1_final = s1[randz(0, 2)];
var s20_final = s20[randz(0, 2)];
var s40_final = s40[randz(0, 2)];
var s60_final = s60[randz(0, 2)];
var s80_final = s80[randz(0, 2)];
var percent_final = randz(60, 99);
var result_final = result[randz(0, 2)];

Логи­ка простая:

  • гото­вим шаб­ло­ны тек­ста для каж­до­го этапа;
  • встав­ля­ем функ­цию, кото­рая вер­нёт нам слу­чай­ное чис­ло в нуж­ном диапазоне;
  • запол­ня­ем финаль­ные зна­че­ния слу­чай­ным образом.

Вер­ни­тесь немно­го назад и посмот­ри­те: имен­но эти пере­мен­ные мы исполь­зо­ва­ли в раз­де­ле, когда срав­ни­ва­ли зна­че­ния с про­цен­та­ми на шка­ле и выво­ди­ли результат.

Собираем страницу

Основ­ное мы сде­ла­ли, оста­лось всё это кра­си­во раз­ме­стить в бра­у­зе­ре. Берём код стра­ни­цы исход­но­го про­ек­та, немно­го пра­вим и полу­ча­ем такое:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Image Preloader Progress Bar</title>
  <link rel="stylesheet" href="./style.css">
</head>
<style type="text/css">
  button {
    font-size: 100%;
    padding: 5px 10px 10px 10px;
    margin-top: 10%;
  }
</style>

<body>
  <!-- кнопка для запуска -->
  <button value="Получить предсказание по крипте" onclick="tested();">Получить прогноз по криптовалюте</button>
  <!-- наш прогресс-бар -->
  <div class="loader">
    <div class="progress-bar">
      <div class="progress-stripes"></div>
      <div class="percentage">0%</div>
    </div>
  </div>
  <span>Система готова</span>
  <p>
    <!-- тут будет финальный текст -->
  </p>
  <!-- подключаем jQuery и сам скрипт -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
  <script src="./script.js"></script>
</body>

</html>

Мы доба­ви­ли кноп­ку и раз­дел с тегом <p> для финаль­но­го тек­ста. Всё осталь­ное дона­стро­им в скрипте.

Запуск по кнопке

В исход­ном про­ек­те прогресс-бар начи­на­ет рабо­тать авто­ма­ти­че­ски сра­зу после загруз­ки стра­ни­цы. Нам такое не нуж­но, а нуж­но, что­бы всё запус­ка­лось после нажа­тия на кноп­ку. Поэто­му сно­ва пра­вим скрипт.

Уби­ра­ем авто­за­груз­ку. За это отве­ча­ет свой­ство $(window).load(function(). Нахо­дим его и пол­но­стью уда­ля­ем эту функцию.

При­вя­зы­ва­ем запуск к кноп­ке. Помни­те, мы пра­ви­ли функ­цию function preload(imgArray)? Имен­но она отве­ча­ет за рабо­ту все­го прогресс-бара. Что­бы при­вя­зать её к кноп­ке, обо­ра­чи­ва­ем вызов функ­ции в такой код:

function launch() { preload(demoImgArray); }

Смот­рим, что­бы в опи­са­нии кноп­ки на стра­ни­це тоже всё было правильно:

<button value="Получить пред­ска­за­ние по крип­те" <strong>onclick="launch()</strong>;">Полу­чить про­гноз по криптовалюте</button>

Стро­ка onclick="launch()" озна­ча­ет, что при нажа­тии на кноп­ку сра­бо­та­ет функ­ция launch(), а имен­но она у нас в скрип­те отве­ча­ет за запуск прогресс-бара.

Дела­ем кноп­ку одно­ра­зо­вой. Что­бы всё рабо­та­ло без сбо­ев и пред­ска­зу­е­мо, давай­те запре­тим нажи­мать на кноп­ку боль­ше одно­го раза. Для это­го исполь­зу­ем jQuery в функ­ции preload(imgArray):

$('button').prop('disabled', true);

Этот код дела­ет следующее:

  • Нахо­дит на стра­ни­це эле­мент <button>.
  • При­сва­и­ва­ет свой­ству кноп­ки disabled зна­че­ние true. Это зна­чит, что кноп­ка ста­но­вит­ся неак­тив­ной — она вид­на, но нажать на неё нель­зя. То что нужно.

Что в результате

Мы раз­ме­сти­ли свою вер­сию про­ек­та на сай­те — мож­но поль­зо­вать­ся само­му и делить­ся с дру­зья­ми. Если вы хоти­те сде­лать себе такое же и даже кру­че — дер­жи­те исход­ный код:

index.html

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Image Preloader Progress Bar</title>
  <link rel="stylesheet" href="./style.css">
</head>
<style type="text/css">
  button {
    font-size: 100%;
    padding: 5px 10px 10px 10px;
    margin-top: 10%;
  }
</style>

<body>
  <!-- кнопка для запуска -->
  <button value="Получить предсказание по крипте" onclick="launch();">Получить прогноз по криптовалюте</button>
  <!-- наш прогресс-бар -->
  <div class="loader">
    <div class="progress-bar">
      <div class="progress-stripes"></div>
      <div class="percentage">0%</div>
    </div>
  </div>
  <span>Система готова</span>
  <p>
    <!-- тут будет финальный текст -->
  </p>
  <!-- подключаем jQuery и сам скрипт -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
  <script src="./script.js"></script>
</body>

</html>

style.css

body {
  background: #e7e7e7;
  font-family: Arial, Helvetica, Sans-serif;
  text-align: center;
  padding: 5px;
  margin: 0;
}
.loader {
  margin: 50px auto 10px;
  width: 300px;
  height: 25px;
  border-radius: 14px;
  border-bottom: 1px solid #fff;
  border-top: 1px solid #999;
  background: #ccc;
  overflow: hidden;
  position: relative;
}
.loader.gray {
  background: #999;
}
.progress-bar {
  height: inherit;
  width: 0%;
  border-radius: inherit;
  position: relative;
  overflow: hidden;
}
.progress-stripes {
  width: inherit;
  height: inherit;
  font-size: 180px;
  font-weight: bold;
  margin-top: -50px;
  letter-spacing: -14px;
}
.percentage {
  position: absolute;
  top: 4px;
  right: 5px;
  font-weight: bold;
  font-size: 16px;
}
/***************************************/
/* BELOW HERE IS SOLELY FOR AESTHETICS */
/*_____________________________________*/
/*** COLOR SCHEMES ***/
/* RED */
.red .progress-bar {
  background: #e74c3c;
}
.red .progress-stripes {
  color: #c0392b;
}
.red .percentage {
  color: #eee;
}
/* BLUE */
.blue .progress-bar {
  background: #3498db;
}
.blue .progress-stripes {
  color: #2980b9;
}
.blue .percentage {
  color: #eee;
}
/* GREEN */
.green .progress-bar {
  background: #2ecc71;
}
.green .progress-stripes {
  color: #27ae60;
}
.green .percentage {
  color: #fff;
}
/* YELLOW */
.yellow .progress-bar {
  background: #f1c40f;
}
.yellow .progress-stripes {
  color: #f39c12;
}
.yellow .percentage {
  color: #fff;
}
/* PURPLE */
.purple .progress-bar {
  background: #9b59b6;
}
.purple .progress-stripes {
  color: #8e44ad;
}
.purple .percentage {
  color: #eee;
}
/* GRAY */
.gray .progress-bar {
  background: #ecf0f1;
}
.gray .progress-stripes {
  color: #bdc3c7;
}
.gray .percentage {
  color: #333;
}
/*** LOADED ***/
span {
  color: #888;
  text-shadow: 0 1px 0 #fff;
}
span.loaded.red {
  color: #c0392b;
}
span.loaded.blue {
  color: #2980b9;
}
span.loaded.green {
  color: #27ae60;
}
span.loaded.yellow {
  color: #d35400;
}
span.loaded.purple {
  color: #8e44ad;
}
span.loaded.gray {
  color: #444;
}
/*** SKINS ***/
.skins {
  padding: 4px 0 8px;
  cursor: default;
  font-size: 14px;
  color: #666;
  background: #fff;
  opacity: 0.5;
  -moz-transition: opacity 0.25s linear;
  -webkit-transition: opacity 0.25s linear;
  transition: opacity 0.25s linear;
}
.skins:hover {
  opacity: 1;
}
.skin {
  width: 20px;
  height: 20px;
  cursor: pointer;
  margin-bottom: -7px;
  border: 1px solid #fff;
  display: inline-block;
  *display: inline;
  zoom: 1;
}
#red {
  background: #c0392b;
}
#red:hover {
  background: #e74c3c;
}
#blue {
  background: #2980b9;
}
#blue:hover {
  background: #3498db;
}
#green {
  background: #27ae60;
}
#green:hover {
  background: #2ecc71;
}
#yellow {
  background: #f39c12;
}
#yellow:hover {
  background: #f1c40f;
}
#purple {
  background: #8e44ad;
}
#purple:hover {
  background: #9b59b6;
}
#gray {
  background: #7f8c8d;
}
#gray:hover {
  background: #95a5a6;
}

script.js

// этапы в прогресс-баре
var s1 = ['Собираю данные из интернета', 'Читаю Википедию', 'Смотрю паблики во ВКонтакте'];
var s20 = ['Обучаю нейросеть', 'Анализирую увиденное', 'Выделяю важное'];
var s40 = ['Готовлю технический анализ', 'Думаю о будущем', 'Осознаю важность момента'];
var s60 = ['Постулирую неведомое', 'Инкапсулирую полиморфизм', 'Кристаллизую суть'];
var s80 = ['Тыкаю пальцем в небо', 'Формализую данность как класс', 'Удаляю противоречия и вношу ясность'];
var result = ['Сначала крипта подрастет, потом обвалится. Потом снова подрастет, потом снова обвалится.', 'Можно закупать, хотя лучше сначала продать, но если важнее купить, то можно и не продавать', 'По всем показателям рецессия будет следовать за отскоком, но это вряд ли отразится на текущей ситуации, поэтому необходимо обратить внимание на волатильность.'];
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
// готовим итоговые значения
var s1_final = s1[randz(0, 2)];
var s20_final = s20[randz(0, 2)];
var s40_final = s40[randz(0, 2)];
var s60_final = s60[randz(0, 2)];
var s80_final = s80[randz(0, 2)];
var percent_final = randz(60, 99);
var result_final = result[randz(0, 2)];
/* SET RANDOM LOADER COLORS FOR DEMO PURPOSES */
var demoColorArray = ['red', 'blue', 'green', 'yellow', 'purple', 'gray'];
var colorIndex = Math.floor(Math.random() * demoColorArray.length);
setSkin(demoColorArray[colorIndex]);
var yes = true;
/* RANDOM LARGE IMAGES FOR DEMO PURPOSES */
var demoImgArray = ['http://www.hdwallpapers.in/walls/halloween_2013-wide.jpg', 'http://www.hdwallpapers.in/walls/2013_print_tech_lamborghini_aventador-wide.jpg', 'http://www.hdwallpapers.in/walls/ama_dablam_himalaya_mountains-wide.jpg', 'http://www.hdwallpapers.in/walls/arrow_tv_series-wide.jpg', 'http://www.hdwallpapers.in/walls/anna_in_frozen-wide.jpg', 'http://www.hdwallpapers.in/walls/frozen_elsa-wide.jpg', 'http://www.hdwallpapers.in/walls/shraddha_kapoor-wide.jpg', 'http://www.hdwallpapers.in/walls/sahara_force_india_f1_team-HD.jpg', 'http://www.hdwallpapers.in/walls/lake_sunset-wide.jpg', 'http://www.hdwallpapers.in/walls/2013_movie_cloudy_with_a_chance_of_meatballs_2-wide.jpg', 'http://www.hdwallpapers.in/walls/bates_motel_2013_tv_series-wide.jpg', 'http://www.hdwallpapers.in/walls/krrish_3_movie-wide.jpg', 'http://www.hdwallpapers.in/walls/universe_door-wide.jpg', 'http://www.hdwallpapers.in/walls/night_rider-HD.jpg', 'http://www.hdwallpapers.in/walls/tide_and_waves-wide.jpg', 'http://www.hdwallpapers.in/walls/2014_lamborghini_veneno_roadster-wide.jpg', 'http://www.hdwallpapers.in/walls/peeta_katniss_the_hunger_games_catching_fire-wide.jpg', 'http://www.hdwallpapers.in/walls/captain_america_the_winter_soldier-wide.jpg', 'http://www.hdwallpapers.in/walls/puppeteer_ps3_game-wide.jpg', 'http://www.hdwallpapers.in/walls/lunar_space_galaxy-HD.jpg', 'http://www.hdwallpapers.in/walls/2013_wheelsandmore_lamborghini_aventador-wide.jpg', 'http://www.hdwallpapers.in/walls/destiny_2014_game-wide.jpg', 'http://www.hdwallpapers.in/colors_of_nature-wallpapers.html', 'http://www.hdwallpapers.in/walls/sunset_at_laguna_beach-wide.jpg'];
// Stripes interval
var stripesAnim;
var calcPercent;
$progress = $('.progress-bar');
$percent = $('.percentage');
$stripes = $('.progress-stripes');
$stripes.text('////////////////////////');
/* CHANGE LOADER SKIN */
$('.skin').click(function () {
  var whichColor = $(this).attr('id');
  setSkin(whichColor);
});
// Call function to load array of images
function launch() {
  preload(demoImgArray);
}
// Call function to animate stripes
stripesAnimate();
/*** FUNCTIONS ***/
/* LOADING */
function preload(imgArray) {
  $('button').prop('disabled', true);
  var increment = Math.floor(100 / imgArray.length);
  var i = 0;
  while (i < 300) {
    $progress.animate({
      width: "+=" + 0.25 + "%"
    }, 100);
    i = i + 1;
  }
  calcPercent = setInterval(function () {
    //loop through the items
    $percent.text(Math.floor(($progress.width() / $('.loader').width()) * 100) + '%');
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 1) {
      $('span').text(s1_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 20) {
      $('span').text(s20_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 40) {
      $('span').text(s40_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 60) {
      $('span').text(s60_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 80) {
      $('span').text(s80_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 100) {
      yes = false;
      $('span').text('Примерная точность прогноза: ' + percent_final + '%');
      $('p').text(result_final);
    }
  });
}
/* STRIPES ANIMATION */
function stripesAnimate() {
  if (yes) {
    animating();
    stripesAnim = setInterval(animating, 2500);
  }
}
function animating() {
  if (yes) {
    $stripes.animate({
      marginLeft: "-=30px"
    }, 2500, "linear").append('/');
  }
}
function setSkin(skin) {
  $('.loader').attr('class', 'loader ' + skin);
  $('span').hasClass('loaded') ? $('span').attr('class', 'loaded ' + skin) : $('span').attr('class', skin);
}

В этом коде мно­го лиш­них строк, он неопти­маль­ный и его мож­но суще­ствен­но улуч­шить. Но глав­ное — он уже рабо­та­ет, и его мож­но быст­ро допи­лить под свои зада­чи. Поэто­му вот домаш­нее зада­ние для тех, кто хочет идеал:

  • най­ти и убрать лиш­ние функции;
  • заме­нить насто­я­щую загруз­ку кар­ти­нок (посмот­ри­те вни­ма­тель­но код) на что-то не настоль­ко нагрузочное;
  • сде­лать боль­ше вари­ан­тов тек­ста на каж­дом этапе.

Предсказываем будущее рынка труда
В буду­щем оста­нут­ся толь­ко раз­ра­бот­чи­ки и мас­са­жи­сты. Если вы не мас­са­жист, при­хо­ди­те учить­ся на раз­ра­бот­чи­ка. Мяу.