When do whales upvote?

Ever wondered when whales are upvoting? I was wondering the same thing. I thought: perhaps some times are better to post than others.

So I wrote a program in Python to plot the upvote times for July 2016 for a random selection of whales. Here are the results:

The founders

@ned and @dan appear very limited in the time they have to upvote. They're both similar in the number of votes they give.

If you get upvoted by @dan or @ned, you've done well. Not only because the founders like your work, but also because they aren't the most active upvoters.

Whale bots

The accounts @steemed, @itsascam, and @steemroller are all bots run by the same whale. These graphs illustrate bot behaviour: the distribution of upvotes is more even, and of course, they never seem to sleep.

All these graphs really tell us are when the authors in the bot's author list have published an article.

Others

Here are a bunch of other whales. Maybe some of them are bots, let me know if that's the case. It's fun making assumptions about the whales, like when they sleep.

berniesanders

Between 07:00 UTC and 14:00 UTC @berniesanders is mostly inactive. At the other times though he's a generous upvoter and quite consistent.

blocktrades

@blocktrades is very likely asleep between 06:00 UTC and 14:00 UTC. After this time there's a bit of voting, but not a great deal.

complexring

@complexring is either or bot or someone who has trouble sleeping. While upvoting between 06:00 and 12:00 UTC is significantly lower, there are still some votes taking place. @complexring is much more active in upvoting than @berniesanders.

nextgencrypto

@nextgencrypto does most upvoting during the weekend with some activity in the evening (their local time), assuming the gaps indicate sleep.

pharesim

@pharesim's behaviour resembles @complexring. Perhaps a bot. There are lower periods of activity which could indicate sleep. Friday seems to be the most stable day where there's a clear pickup in voting after 12:00 UTC.

rainmain

It's clear that @rainman is sleeping between around 22:00 UTC and 04:00 UTC. Another low upvoting whale, @rainman is pretty consistent during the day.

smooth

@smooth's behaviour is similar to rainmain in that it's consistent during voting times, but there's no clear downtime, which raises the suspicion of bot use. @smooth is a slightly more active upvoter during the weekend.

tombstone

@tombstone's graph looks empty, but that's caused by the bizarre outlier on Friday at 10:00 UTC. I have no idea what @tombstone was doing then, but it was an upvote frenzy.

The rest of the time @tombstone is quite consistent with voting, with slightly more activity between 02:00 UTC and 08:00 UTC.

When should I post?

I've intentionally not drawn too many conclusion from these graphs. Perhaps the most useful information is knowing which whales upvote the most and when, so you can time your publication correctly.

Do you want to increase the chance that @berniesanders will upvote you? Don't post at 07:00 UTC because for the next 8 hours he's unlikely to see it; better to post around 03:00 UTC on a Wednesday.

With so many whales you're more or less covered whenever you post. What you're posting is far more important than when you post it.

Show me the code

Feel free to use and adapt the code below as you like. Any bugs, please let me know in the comments. Read @furion's post for more information on parsing the blockchain.

The program requires Python 3 and the following libraries, which you can install with pip:

  • matplotlib
  • numpy
  • pandas
  • seaborn
  • steem

The program runs on the command-line. Provide a list of whale username and optionally, either the start and end blocks to analyze or start and end dates. The latter will convert the dates into the appropriate block numbers (this isn't fast - there's probably a better way to do it).

python3 vote_dist.py dan ned smooth berniesanders -f 2016-07-01 -t 2016-07-31

Enjoy!

import argparse
import datetime
import math
from collections import defaultdict

import pandas as pd
import seaborn as sns
import numpy as np
from steemapi.steemnoderpc import SteemNodeRPC


def get_stats(rpc, users, beg_block, end_block):
    print("Getting stats for {} from block {} to block {}".format(
          users, beg_block, end_block))

    stats = defaultdict(list)
    current_block = beg_block
    while current_block < end_block:
        block = rpc.get_block(current_block)

        if "transactions" not in block:
            continue

        for tx in block["transactions"]:
            for op in tx["operations"]:
                op_type = op[0]
                op_data = op[1]

                timestamp = pd.to_datetime(tx['expiration'])

                if op_type == "vote":
                    author = op_data['author']
                    voter = op_data['voter']
                    weight = op_data['weight']
                    if voter in users and weight > 0:
                        stats[voter].append((timestamp, weight))
        current_block += 1
    return stats


def get_block_num_for_date(rpc, date):
    """
    Gets the first block number for the given date.

    This is anything but fast. There's probably a better way, but this was the
    first thing that came to mind.
    """
    print("Getting block num for {}".format(date))

    block = rpc.get_block(1)
    bc_date = pd.to_datetime(block['timestamp'])

    SECONDS_IN_DAY = 24 * 60 * 60
    NUM_BLOCKS_PER_DAY = math.floor(SECONDS_IN_DAY / 3)

    # Estimate the block number
    block_num = (date - bc_date).days * NUM_BLOCKS_PER_DAY

    # Use estimation to find the actual block number
    best_block_num = block_num
    best_block_diff = None
    while True:
        block = rpc.get_block(block_num)
        block_date = pd.to_datetime(block['timestamp'])
        diff = (date - block_date).total_seconds()

        if best_block_diff:
            if abs(diff) > abs(best_block_diff):
                break

        best_block_num = block_num
        best_block_diff = diff
        if diff > 0:
            block_num += 1
        elif diff < 0:
            block_num -= 1
        else:
            break
    return best_block_num


def create_plot(user, votes):
    print("Creating plot for {}".format(user))

    df = pd.DataFrame.from_records(votes, columns=['time', 'weight'])
    df['day'] = df['time'].dt.weekday_name
    df['hour'] = df['time'].dt.hour

    col_order = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
                 "Saturday", "Sunday"]

    plot = sns.FacetGrid(df, col="day", col_order=col_order, col_wrap=3)
    plot = plot.map(sns.plt.hist, "hour", bins=np.arange(0, 23),
                    color="c").set_titles("{col_name}")
    plot.set(xticks=np.arange(23, step=2), xlim=(0, 23))
    plot.set_axis_labels('Hour', 'Upvotes')
    plot.fig.suptitle(user, size=16)
    plot.fig.subplots_adjust(top=.9)

    return plot


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('usernames', nargs='*',
                        help='Usernames to show statistics for')
    parser.add_argument('-s', '--server', default='ws://localhost:8090',
                        dest='server',
                        help='Address of the steem JSON-RPC server')
    block_group = parser.add_argument_group('blocks')
    block_group.add_argument('-b', '--begin-block', default=1, type=int,
                        dest='beg_block',
                        help='The block to begin on.')
    block_group.add_argument('-e', '--end-block', default=None, type=int,
                        dest='end_block',
                        help='The block to end on. Default is last_irreversible_block_num.')
    date_group = parser.add_argument_group('dates')
    date_group.add_argument('-f', '--from-date', type=str, dest='from_date',
                        help='The date to end on.')
    date_group.add_argument('-t', '--to-date', type=str, dest='to_date',
                        help='The date to end on.')
    args = parser.parse_args()

    # Connect to steem rpc server
    rpc = SteemNodeRPC(args.server, "", "")

    # Get the block numbers
    if args.from_date and args.to_date:
        args.from_date = pd.to_datetime(args.from_date)
        args.to_date = pd.to_datetime(args.to_date)
        args.beg_block = get_block_num_for_date(rpc, args.from_date)
        args.end_block = get_block_num_for_date(rpc, args.to_date + datetime.timedelta(days=1)) - 1
    else:
        props = rpc.get_dynamic_global_properties()
        if args.end_block:
            if args.end_block > props['last_irreversible_block_num']:
                args.end_block = props['last_irreversible_block_num']
        else:
            args.end_block = props['last_irreversible_block_num']

    # Validate block numbers
    if args.beg_block < 0:
        print("begin-block must be greater than 0")
        sys.exit(1)

    if args.end_block < args.beg_block:
        print("end-block must be greater than beg-block")
        sys.exit(1)

    # Get the stats
    stats = get_stats(rpc, args.usernames, args.beg_block, args.end_block)
    for user, votes in stats.items():
        plot = create_plot(user, votes)
        plot.savefig('{}.png'.format(user))

    if len(stats) == 0:
        print("Nothing found for the given parameters")

Like my post? Don't forget to follow me!

H2
H3
H4
3 columns
2 columns
1 column
120 Comments