dev || bet - the battle of programmers and technology



    Dogs vs cats, drivers vs pedestrians, Coca-Cola vs Pepsi, BMW vs Mercedes, sausage vs cheese, uzvar vs fruit drink, good vs evil, after all! But no, we, as always, argued about which programming language is better. The classic arguments about performance were used, with links to benchmarks that no one tested, syntax buns that you use once a year, popularity graphs, lists of reputable programmers using one or another language ... Then the conversation smoothly turned to a discussion of the Oxymiron battle with someone there. Well, any conversation longer than 20 minutes comes down to discussing the price of bitcoin.

    In a strange way, the three topics merged into one, and so the idea of ​​dev || bet was born.


    The essence of the project


    Two developers representing different technologies solve the same problem. The winner is the one who will solve it in the shortest time, or get a better result in a limited time.

    Three commentators are watching the process on the screens of the participants. They discuss their approach to solving, notice interesting moments, non-standard moves and just interesting chips that any programmer has. And on the other hand, they pay attention to unsuccessful decisions and failures.

    We viewers want to see how ordinary developers think, google, make mistakes, and, in the end, come to a decision. Moreover, it is more interesting for us to observe the plug of leads over the library function than the clicking of algorithms by the dragged Olympiad.

    For the pilot release, as participants, we called friends who identified the technologies used: Python vs JavaScript.

    First release challenge


    In order for the project to be successful in 2018, its name must necessarily mention cryptocurrencies or blockchain. Therefore, having a little dream, we came up with a simple task. The formal description that the participants received looked like this:

    Today everyone is talking about cryptocurrencies. There have been many cases where people sold bitcoins at an incredibly low price. Well, I once thought to buy 150 BTC for $ 15 ...
    But what if we had a time machine that could transfer commands to a crypto broker in the past? Of course, we would bring down the global financial system. Nevertheless, let's imagine that we have a car, or, at least, we are close to its creation. We want you, a dirty hungry freelancer working for food, to create an algorithm for us that generates a sequence of commands for our broker client. (Of which we ourselves created a year ago specifically for future use, of course.)

    You can use Python or JavaScript. Please check your solution with the included test runner. The one whose decision will earn more money will be chosen to write a bot for a real client.

    API specification v0.0.18


    The runner of the task should be a pure function that takes two arguments:
    prices => [{"btc": 1000}, ...]- An array of bitcoin prices by day
    initialSum => 100000- Starting balance in USD
    The function should return an array of commands. Teams are called one at a time. That is, for 14 days you should have exactly 14 teams.
    Example:
    [{"op":"buy","amount":1},{"op":"sell","amount":1}]
    Teams:
    "buy" additional attributes: amount [float] - Buy BTC using the current USD account
    "sell"additional attributes: amount [float] - Sell BTC using the current USD account
    "pass"- Skip the day

    The task description was intentionally made confusing and fuzzy to make the process more interesting. True, as a result, this played a cruel joke, and greatly increased the decision time of the participants.

    In fact, it was necessary to write a function that, according to the already known history of price changes, will give instructions when to buy bitcoins, when to sell, and when to just wait.

    Implementation


    The platform Codewars was chosen as the platform . Not that we chose for a long time, but it had everything we needed: support for more than 20 languages, a simple interface, the ability to add a draft task available by reference.

    The codebattle.hexlet.io project about which already wrote on Habr seems quite interesting . But the opportunity to see the opponent’s code so far seemed superfluous to us.

    Since the participants used Python and JavaScript, the test runners were implemented on them:

    Javascript
    'use strict';
    const https = require('https');
    const currencies = [
        'btc',
        'eth',
    ];
    const BASE_CURRENCY = 'usd';
    const DEFAULT_CURRENCY = 'btc';
    const fetchRates = (days, currency) => new Promise((res, rej) => {
        https.get(`https://min-api.cryptocompare.com/data/histoday?aggregate=1&e=CCCAGG&extraParams=CryptoCompare&limit=${days}&tryConversion=false&tsym=${BASE_CURRENCY.toUpperCase()}&fsym=${currency.toUpperCase()}`, (resp) => {
            let data = '';
            resp.on('data', (chunk) => {
                data += chunk;
            });
            resp.on('end', () => {
                data = JSON.parse(data);
                data = data.Data;
                res(data.map(datum => datum['close']).slice(0, -1));
            });
        }).on("error", err => rej(err));
    });
    const fetchAllRates = async (days, currencies) => {
      const prices = {};
      for (let currency of currencies) {
          prices[currency] = await fetchRates(days, currency);
      }
      const len = prices[Object.keys(prices)[0]].length;
      const ret = [];
      for (let i = 0; i < len; i++) {
          let price = {};
          for (let currency of currencies) {
              price[currency] = prices[currency][i];
          }
          ret.push(price);
      }
      return ret;
    };
    const checkStash = stash => {
        const vals = Object.values(stash);
        for (let val of vals) {
            Test.expect(val >= -Math.pow(10, -6), 'Invalid operation');
            if (val < -Math.pow(10, -6)) {
                throw new Error(`Debts are not supported. Stash: ${JSON.stringify(stash)}`)
            }
        }
    };
    const applyTask = (stash, task, prices) => {
        console.log('- performing task', stash, task, prices);
        const currency = task.currency || DEFAULT_CURRENCY;
        switch(task.op) {
            case 'buy':
                stash[currency] += task.amount;
                stash[BASE_CURRENCY] -= task.amount * prices[currency];
                break;
            case 'sell':
                stash[currency] -= task.amount;
                stash[BASE_CURRENCY] += task.amount * prices[currency];
                break;
            case 'pass':
                break;
        }
        return stash;
    };
    const runner = async (trader, cases) => {
        for (let testCase of cases) {
            let prices = await fetchAllRates(testCase.days, currencies);
            let stash = testCase.amount;
            for (let currency of currencies) {
                stash[currency] = stash[currency] || 0;
            }
            console.log(`Testing amount ${stash[BASE_CURRENCY]}, days ${testCase.days}`);
            let tasks = await trader(prices, stash[BASE_CURRENCY]);
            for (let i in tasks) {
                if (!tasks.hasOwnProperty(i)) {
                    continue;
                }
                let job = tasks[i];
                let todo = job.length ? job : [job];
                for (let row of todo) {
                    await applyTask(stash, row, prices[i]);
                }
                checkStash(stash);
            }
            let result = Math.floor(stash[BASE_CURRENCY] * 100) / 100;
            console.log(`finished. Resulting amount: ${result}`);
        }
    };
    runner(trader, [
        {
            amount: {
                [BASE_CURRENCY]: 100,
            },
            days: 100,
        },
    ]);
    


    Python
    import urllib2
    import json
    import math
    currencies = [
        'btc',
        'eth',
    ]
    BASE_CURRENCY = 'usd'
    DEFAULT_CURRENCY = 'btc'
    def fetch_rates(days, currency):
        data = urllib2.urlopen(
            'https://min-api.cryptocompare.com/data/histoday?aggregate=1&e=CCCAGG&extraParams=CryptoCompare&limit={}&tryConversion=false&tsym={}&fsym={}'.format(
                days, BASE_CURRENCY.upper(), currency.upper())).read()
        data = json.loads(data)['Data']
        return [row['close'] for row in data][:-1]
    def fetch_all_rates(days, currencies):
        prices = {currency: fetch_rates(days, currency) for currency in currencies}
        return [{currency: prices[currency][i] for currency in currencies} for i in range(days)]
    def check_stash(stash):
        for currency in stash:
            test.assert_equals(stash[currency] >= -0.000001, True, 'Invalid operation')
            if stash[currency] < -0.000001:
                raise Exception('Debts are not supported. Stash: {}'.format(stash))
    def apply_task(stash, task, prices):
        print '- performing task {} {} {}'.format(stash, task, prices)
        currency = task['currency'] if 'currency' in task else DEFAULT_CURRENCY
        if task['op'] == 'buy':
            stash[currency] += task['amount']
            stash[BASE_CURRENCY] -= task['amount'] * prices[currency]
        elif task['op'] == 'sell':
            stash[currency] -= task['amount']
            stash[BASE_CURRENCY] += task['amount'] * prices[currency]
        elif task['op'] == 'pass':
            pass
        return stash
    def runner(trader, cases):
        for testCase in cases:
            prices = fetch_all_rates(testCase['days'], currencies)
            stash = testCase['amount']
            for currency in currencies:
                if currency not in stash:
                    stash[currency] = 0
            print 'Testing amount {}, days {}'.format(stash[BASE_CURRENCY], testCase['days'])
            tasks = trader(prices, stash[BASE_CURRENCY])
            for i, job in enumerate(tasks):
                todo = job if isinstance(job, list) else [job]
                for row in todo:
                    stash = apply_task(stash, row, prices[i])
                check_stash(stash)
            result = math.floor(stash[BASE_CURRENCY] * 100) / 100
            print 'finished. Resulting amount: {}'.format(result)
    runner(trader, [
        {
            "amount": {
                BASE_CURRENCY: 100,
            },
            "days": 100,
        }
    ])
    


    The task itself is available on Codewars.
    And the decisions of the participants on Github

    Thank you for your attention! We look forward to your comments, criticisms and opinions.

    What next? PHP vs. JS, .Net vs. Java, iOS vs. Android, React vs. Vue.js?

    Also popular now: