<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Neal Chambers</title>
    <description>The latest articles on DEV Community by Neal Chambers (@macanudo527).</description>
    <link>https://dev.to/macanudo527</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F876979%2F027c7cd3-3776-482a-b043-6aa173082013.jpeg</url>
      <title>DEV Community: Neal Chambers</title>
      <link>https://dev.to/macanudo527</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/macanudo527"/>
    <language>en</language>
    <item>
      <title>Dealing with Large CSV files in Python by Chunking</title>
      <dc:creator>Neal Chambers</dc:creator>
      <pubDate>Tue, 07 Mar 2023 00:59:00 +0000</pubDate>
      <link>https://dev.to/macanudo527/dealing-with-large-csv-files-in-python-by-chunking-4j1j</link>
      <guid>https://dev.to/macanudo527/dealing-with-large-csv-files-in-python-by-chunking-4j1j</guid>
      <description>&lt;p&gt;I'm continuing to work on &lt;a href="https://github.com/eprbell/dali-rp2"&gt;Dali-RP2&lt;/a&gt;, which helps collect data from crypto exchanges and then matches that with historical pricing in order to prepare one's taxes. Recently, I ran into some issues pulling prices for crypto assets. Typically, to find a price for a given crypto asset for a given time, you can just access the public REST API of an exchange and it will return a OHLCV &lt;strong&gt;bar&lt;/strong&gt;, which contains the &lt;strong&gt;O&lt;/strong&gt;pening price, &lt;strong&gt;H&lt;/strong&gt;igh price, &lt;strong&gt;L&lt;/strong&gt;ow price, &lt;strong&gt;C&lt;/strong&gt;losing price and &lt;strong&gt;V&lt;/strong&gt;olume.&lt;/p&gt;

&lt;p&gt;That is all fine and good, but some exchanges can be quite stingy with their API. This is understandable, since you don't want users spamming a server for data. However, if you happen to have 1000 or so transactions you want to price it can take a long long time to price everything.&lt;/p&gt;

&lt;p&gt;For instance, Kraken requires around 5 seconds between each call to its API. For 1000 transactions, this would take around 7 hours to pull all the pricing I need. Luckily, they have &lt;a href="https://support.kraken.com/hc/en-us/articles/360047124832-Downloadable-historical-OHLCVT-Open-High-Low-Close-Volume-Trades-data"&gt;downloadable CSV files&lt;/a&gt; that cover everything but the last quarter. So, you can just download the files and save them to cache, right? &lt;/p&gt;

&lt;p&gt;Unfortunately, this creates a massive cache that requires a lot of memory, and requires even more memory when it is saved to disk. Eventually, if you work with enough crypto assets this will cause the program to crash because it will run out of memory.&lt;/p&gt;

&lt;p&gt;To avoid this problem, we have to chunk the csv files into smaller files that we then read prices from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunk It, You must Chunk It
&lt;/h2&gt;

&lt;p&gt;I was able to find &lt;a href="https://coraspe-ramses.medium.com/working-with-large-csv-files-in-python-from-scratch-134587aed5f7"&gt;a good comprehensive overview&lt;/a&gt; on the myriad of ways one can chunk things. From there, I adapted it for my particular situation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def __split_process(self, csv_file: str, chunk_size: int = _CHUNK_SIZE) -&amp;gt; Generator[Tuple[str, List[List[str]]], None, None]:
    chunk: List[List[str]] = []

    lines = reader(csv_file.splitlines())
    position = _PAIR_START
    next_timestamp: Optional[int] = None

    for line in lines:
        if next_timestamp is None:
            next_timestamp = ((int(line[self.__TIMESTAMP_INDEX]) + chunk_size) // chunk_size) * chunk_size

        if int(line[self.__TIMESTAMP_INDEX]) % chunk_size == 0 or int(line[self.__TIMESTAMP_INDEX]) &amp;gt; next_timestamp:
            yield position, chunk
            if position == _PAIR_START:
                position = _PAIR_MIDDLE
            chunk = []
            next_timestamp += chunk_size
        chunk.append(line)
    if chunk:
        position = _PAIR_END
        yield position, chunk

def _split_chunks_size_n(self, file_name: str, csv_file: str, chunk_size: int = _CHUNK_SIZE) -&amp;gt; None:

    pair, duration_in_minutes = file_name.strip(".csv").split("_", 1)
    chunk_size *= min(int(duration_in_minutes), _MAX_MULTIPLIER)
    file_timestamp: str
    pair_start: Optional[int] = None
    pair_end: int
    pair_duration: str = pair + duration_in_minutes

    for position, chunk in self.__split_process(csv_file, chunk_size):
        file_timestamp = str((int(chunk[0][self.__TIMESTAMP_INDEX])) // chunk_size * chunk_size)
        if position == _PAIR_END:
            pair_end = int(chunk[-1][self.__TIMESTAMP_INDEX])
            if pair_start is None:
                pair_start = int(chunk[0][self.__TIMESTAMP_INDEX])
        elif position == _PAIR_START:
            pair_start = int(chunk[0][self.__TIMESTAMP_INDEX])

        chunk_filename: str = f'{pair}_{file_timestamp}_{duration_in_minutes}.{"csv.gz"}'
        chunk_filepath: str = path.join(self.__CACHE_DIRECTORY, chunk_filename)

        with gopen(chunk_filepath, "wt", encoding="utf-8", newline="") as chunk_file:
            csv_writer = writer(chunk_file)
            for row in chunk:
                csv_writer.writerow(row)

    if pair_start:
        self.__cached_pairs[pair_duration] = PairStartEnd(start=pair_start, end=pair_end)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take a look at that line by line. First in the &lt;code&gt;__split_process&lt;/code&gt; function. We want to be able to tell &lt;code&gt;_split_chunk_size_n&lt;/code&gt; the start and end times of the data to make it easier to retrieve the data later. We don't want to waste time attempting to access a time frame that doesn't exist. So, we keep track of where we are in each CSV file with the &lt;code&gt;position&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;We want to &lt;code&gt;yield&lt;/code&gt; the chunk when the &lt;code&gt;timestamp&lt;/code&gt; (stored in the &lt;code&gt;__TIMESTAMP_INDEX&lt;/code&gt; column) reaches a multiplier of the &lt;code&gt;chunk_size&lt;/code&gt; or goes above it. To do that, we need to find the next multiplier of the chunk_size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;next_timestamp = ((int(line[self.__TIMESTAMP_INDEX]) + chunk_size) // chunk_size) * chunk_size
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above, we first add the &lt;code&gt;chunk_size&lt;/code&gt; to the current timestamp in order to get a timestamp that is in the next chunk. We then "floor" the timestamp by using integer division &lt;code&gt;//&lt;/code&gt; to divide it by &lt;code&gt;chunk_size&lt;/code&gt; and then multiply it by the &lt;code&gt;chunk_size&lt;/code&gt;. This will round the number &lt;em&gt;down&lt;/em&gt; to the nearest &lt;code&gt;chunk_size&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Here is a simple example of what we are doing if we had a &lt;code&gt;chunk_size&lt;/code&gt; of 10.&lt;/p&gt;

&lt;p&gt;36 // 10 = 3&lt;br&gt;
  3 * 10 = 30 &lt;/p&gt;

&lt;p&gt;When we reach the next chunk we &lt;code&gt;yield&lt;/code&gt; the position and chunk, reset the chunk, and set the &lt;code&gt;next_timestamp&lt;/code&gt; to the next chunk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if int(line[self.__TIMESTAMP_INDEX]) % chunk_size == 0 or int(line[self.__TIMESTAMP_INDEX]) &amp;gt; next_timestamp:
    yield position, chunk
    if position == _PAIR_START:
        position = _PAIR_MIDDLE
    chunk = []
    next_timestamp += chunk_size
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise, we keep appending lines to the chunk. Note that the timestamp that is a multiple of the &lt;code&gt;chunk_size&lt;/code&gt; or just beyond it is the first bar in the next chunk. &lt;/p&gt;

&lt;p&gt;When we reach the end of the file we &lt;code&gt;yield&lt;/code&gt; the last chunk and tell &lt;code&gt;_split_chunk_size_n&lt;/code&gt; our position.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if chunk:
    position = _PAIR_END
    yield position, chunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Standardizing File Size
&lt;/h2&gt;

&lt;p&gt;That worked pretty well, but I ended up with a lot of files. I had around 460 files for each pair or asset I wanted to price. This isn't too bad, but I noticed files with longer durations were smaller. That seemed like wasted space to me, so I wanted to make the files all around the same size. &lt;/p&gt;

&lt;p&gt;The CSV files list pricing bars (OHLCV) of different durations. There are 6 files in total - 1 minute, 5 minute, 15 minute, 60 minute, 12 hour, and 24 hour. In theory, the 5 minute files would only contain one fifth of the bars that the 1 minute files did, and so forth and so on. Based on this, I decided to add a multiplier to the chunk_size, which would increase the timespan covered by the larger durations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pair, duration_in_minutes = file_name.strip(".csv").split("_", 1)
chunk_size *= min(int(duration_in_minutes), _MAX_MULTIPLIER)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduced the number of files generated to only about 140 or so per asset I want to price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I've just started working with Python. Before that, I was working with Ruby on Rails where everything just goes into a database. This was my first time to deal with a large chunk of data and how to manage it. On the surface of it, it seems like a rather primitive way to handle it, but it ended up being very fast for my needs. I'm able to price numerous assets in a relatively short period of time, and if need be fall back on the REST API for the last quarter.&lt;/p&gt;

&lt;p&gt;What do you think? Do you have other ways you deal with large pieces of data like this? I'd love to hear how others handle this. Let me know in the comments below.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Basic Troubleshooting with Docker and Ruby on Rails</title>
      <dc:creator>Neal Chambers</dc:creator>
      <pubDate>Fri, 24 Feb 2023 00:58:55 +0000</pubDate>
      <link>https://dev.to/macanudo527/basic-troubleshooting-with-docker-and-ruby-on-rails-2nn4</link>
      <guid>https://dev.to/macanudo527/basic-troubleshooting-with-docker-and-ruby-on-rails-2nn4</guid>
      <description>&lt;p&gt;I just started volunteering with the &lt;a href="https://github.com/openfoodfoundation/openfoodnetwork"&gt;Open Food Network&lt;/a&gt; to re-familiarize myself with Ruby on Rails. They are an open source, non-profit, online marketplace for food stores. They provide software to link up independent farmers with hubs and distributors and it all runs on Ruby on Rails. &lt;/p&gt;

&lt;p&gt;Like a lot of web apps, they have a Docker setup to make things easier. Docker helps eliminate the "it works on my computer" problem that can plague other development efforts when certain software packages are not installed, or the wrong version of them is installed.&lt;/p&gt;

&lt;p&gt;If you are not familiar, Docker is a piece of software that 'docks' system images on your computer that run a certain version of an OS. They can be customized to install certain packages when you build them. You can them perform tests with RSpec on them or boot up a server and work with the development version of the site and see if your code has the desired effect.&lt;/p&gt;

&lt;p&gt;I found troubleshooting a little difficult at first under this setup since you need to pass commands to Docker. You can't simply use &lt;code&gt;bash&lt;/code&gt; or any other terminal to run them. Also, the server has to be up and running in order to run those commands. This can lead to a lot of frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to the DB
&lt;/h2&gt;

&lt;p&gt;My first major hurdle was to connect to the database so I can see more directly what was happening 'under the hood'. Luckily, Docker, by default will forward it's ports to the localhost, so you can access it as if it were running on your own system.&lt;/p&gt;

&lt;p&gt;If that is not the case, and the Docker image is not being forwarded to the localhost for whatever reason, you can still find the ip of a Docker image pretty simply. First, we take a look at all the images running locally.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker ps&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will give you a list of images that are currently running that may look a little something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONTAINER ID   IMAGE                     COMMAND                  CREATED        STATUS          PORTS                                       NAMES
697b8685d509   openfoodnetwork_web       "bash -c 'wait-for-i…"   12 hours ago   Up 10 seconds   0.0.0.0:3000-&amp;gt;3000/tcp, :::3000-&amp;gt;3000/tcp   openfoodnetwork_web_1
8ec13ea014c2   openfoodnetwork_worker    "bash -c 'wait-for-i…"   12 hours ago   Up 11 seconds                                               openfoodnetwork_worker_1
9171e4c62f72   postgres:10.19            "docker-entrypoint.s…"   12 hours ago   Up 31 minutes   0.0.0.0:5432-&amp;gt;5432/tcp, :::5432-&amp;gt;5432/tcp   openfoodnetwork_db_1
5015b1895607   openfoodnetwork_webpack   "./bin/webpack-dev-s…"   12 hours ago   Up 11 seconds   0.0.0.0:3035-&amp;gt;3035/tcp, :::3035-&amp;gt;3035/tcp   openfoodnetwork_webpack_1
9067a38ec683   redis                     "docker-entrypoint.s…"   12 hours ago   Up 11 seconds   6379/tcp                                    openfoodnetwork_redis_1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can look up the ip address of any image with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' _container id_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there you can plug in your details into your favorite database viewer. My personal favorite is &lt;a href="https://dbeaver.io/"&gt;Dbeaver&lt;/a&gt;, which is regularly updated and makes it very easy to connect to databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running just the RSpec you want
&lt;/h2&gt;

&lt;p&gt;Another issue I was running into is the enormous test suite that comes with Open Food Network. It can easily take 20-30 minutes to run the entire suite which is simply not possible to do for every little change you want to make. It's much better to just run the specific test you want.&lt;/p&gt;

&lt;p&gt;To do this, you can run rspec with a specific line number. The following command will run the spec in &lt;code&gt;enterprise_relationship_spec.rb&lt;/code&gt; starting at line 258.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec rspec spec/models/enterprise_relationship_spec.rb:258
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run it inside your Docker image, you have two options.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. With a Script
&lt;/h3&gt;

&lt;p&gt;You can create a script that sets up and tears down your Docker image every time you want to run an RSpec. The Open Food Network have &lt;a href="https://github.com/openfoodfoundation/openfoodnetwork/blob/master/docker/run"&gt;just a script&lt;/a&gt; if you want to copy it for yourself.&lt;/p&gt;

&lt;p&gt;Using the script above you just have to prepend the previous command with &lt;code&gt;docker/run&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker/run bundle exec rspec spec/models/enterprise_relationship_spec.rb:258
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Start Interactive Terminal
&lt;/h3&gt;

&lt;p&gt;You can also start a container with an interactive terminal much like the terminal on your localhost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker exec -it $(docker-compose -f docker-compose.yml ps -q web) /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have the interactive terminal you can run commands normally as if you were using a terminal on your localhost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle exec rspec spec/models/enterprise_relationship_spec.rb:258
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using &lt;code&gt;binding.pry&lt;/code&gt; on the live server
&lt;/h2&gt;

&lt;p&gt;You can use &lt;code&gt;binding.pry&lt;/code&gt; anywhere in Rails code to stop the execution of the code and start an interactive terminal session. If you are running a rails server on your local machine, you can simply interact with the terminal window that you started the server from.&lt;/p&gt;

&lt;p&gt;However, with a Docker image things are a little different. When you use &lt;code&gt;binding.pry&lt;/code&gt; the server will still halt execution, but you will not be able to interact with it in anyway. So you need to attach to that image in order to run commands and see what is happening. &lt;/p&gt;

&lt;p&gt;First, we need to find the image that is running the webserver. To do that we need to list the running Docker images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we get a list of running images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONTAINER ID   IMAGE                     COMMAND                  CREATED        STATUS          PORTS                                       NAMES
697b8685d509   openfoodnetwork_web       "bash -c 'wait-for-i…"   12 hours ago   Up 10 seconds   0.0.0.0:3000-&amp;gt;3000/tcp, :::3000-&amp;gt;3000/tcp   openfoodnetwork_web_1
8ec13ea014c2   openfoodnetwork_worker    "bash -c 'wait-for-i…"   12 hours ago   Up 11 seconds                                               openfoodnetwork_worker_1
9171e4c62f72   postgres:10.19            "docker-entrypoint.s…"   12 hours ago   Up 31 minutes   0.0.0.0:5432-&amp;gt;5432/tcp, :::5432-&amp;gt;5432/tcp   openfoodnetwork_db_1
5015b1895607   openfoodnetwork_webpack   "./bin/webpack-dev-s…"   12 hours ago   Up 11 seconds   0.0.0.0:3035-&amp;gt;3035/tcp, :::3035-&amp;gt;3035/tcp   openfoodnetwork_webpack_1
9067a38ec683   redis                     "docker-entrypoint.s…"   12 hours ago   Up 11 seconds   6379/tcp                                    openfoodnetwork_redis_1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time we want to attach to the running webserver. In my case, that is &lt;code&gt;openfoodnetwork_web&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker attach 697b8685d509
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can query local variables and pull records to see what is happening and troubleshoot the issue. I should mention that if you are running specs using either of the two methods I mentioned above, the interactive terminal will start normally in the terminal window you used to run the Rspec command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Those are some of the basic tips I learned while trying to troubleshoot a Rails application running in Docker. Of course, there are numerous ways to setup and troubleshoot an app like this, but these are the ways I found the most useful.&lt;/p&gt;

&lt;p&gt;Do you have any other tips for using Docker? Let me know in the comments below.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>ruby</category>
      <category>rails</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Get Started with Test Driven Development with Python</title>
      <dc:creator>Neal Chambers</dc:creator>
      <pubDate>Mon, 06 Feb 2023 07:26:16 +0000</pubDate>
      <link>https://dev.to/macanudo527/how-to-get-started-with-test-driven-development-with-python-49pg</link>
      <guid>https://dev.to/macanudo527/how-to-get-started-with-test-driven-development-with-python-49pg</guid>
      <description>&lt;p&gt;Everyone starts out programming with a simple program. Typically, some variation of "Hello World!" That's where I started at least and at first you make simple systems that do simple things. For example, a program that helps keep track of your to-do list items. At this point the code is pretty simple to read and see what is going on. If there is a bug somewhere, you can easily track it down and fix it.&lt;/p&gt;

&lt;p&gt;Over time, you build more and more features on to your app. For example, you add the ability to put tags on your to-do items or you add the ability to export them to CSV. You keep adding more and more features that you need to use. Each time you add something new the code tends to get more and more complicated. &lt;/p&gt;

&lt;p&gt;At this point things start to break and it takes more and more time to track down the specific problem in the code since everything has become interconnected. On top of that, one of your users just emailed you to tell you that some of the basic functionality that was available in the first version of the app is now no longer working. &lt;/p&gt;

&lt;p&gt;How can you easily add functionality without running the risk of breaking a feature that already exists? Each time you add something, are you going to have to manually test every little feature to make sure it works?&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Test-Driven Development (TDD)
&lt;/h2&gt;

&lt;p&gt;Test-Driven Development, usually abbreviated as TDD, helps you catch and fix bugs early in the development process, improves the quality of your code, and keeps your code from regressing, (ie. another feature stops working when you add a new one). TDD can help provide a fast feedback loop that will spot bugs before you release it to new users. In mission critical systems, tests are essential to keeping your system from going down when it is needed the most.&lt;/p&gt;

&lt;p&gt;On top of that, it can help you get a clear understanding of what you actually want your code to do with the inputs you are providing it. TDD helps you define the system specifics so that you and your team has a clear understanding of what you are trying to build.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k5wc5BcJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skcl6fh7nx21e51f51ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k5wc5BcJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skcl6fh7nx21e51f51ui.png" alt="Drawing of a checklist with a Woman standing next to it." width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by Step: How to Write Tests for Software Development
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Identify the functionality to be added to your system or, if you have already written the code, the expected functionality you want to test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a test case for the functionality. If it is a large multi-step process your piece of software has to go through you might want to write one test for the whole process and one for each critical step in the process so that you can quickly isolate an issue later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run the test. Obviously, if you haven't written any code this will fail. If you have written code and the test fails, you will want to go back and verify if you have written the test properly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write/Refactor the code. Write code to fulfill the functionality. This is where writing tests for each step of the process is useful, since you can write each step and test to see if it is valid before moving on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat 4 &amp;amp; 5 to clean up and refactor your code. If the tests pass, ask yourself if there is anything you can do to make the process more efficient or simpler. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Continue this process until all functionality is implemented.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keep in mind that in the real world, we all have deadlines to meet and you might not be able to repeat steps 4 &amp;amp; 5 enough times to write good, clean code. You might have to simply settle for what works for now and move on. That's okay because you have written the test, and later, when you have time, you will be able to revisit the code and refactor it more. For now though, you know that it works. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to Install Pytest and Get Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install Pytest. If you are using a virtual environment, like &lt;a href="https://pypi.org/project/virtualenv/"&gt;virtualenv&lt;/a&gt;. Make sure you have it activated and simply type &lt;code&gt;pip install pytest&lt;/code&gt; in your terminal.&lt;/li&gt;
&lt;li&gt;Create a test directory. The industry standard is &lt;code&gt;\test&lt;/code&gt;, but it could be anything.&lt;/li&gt;
&lt;li&gt;Write tests. Write test functions in .py files within the test directory you created earlier. All test functions should start with the &lt;code&gt;test_&lt;/code&gt; prefix. We will go over an example later.&lt;/li&gt;
&lt;li&gt;Run pytest. If you run the command &lt;code&gt;pytest&lt;/code&gt; all tests in the current directory and subdirectory will be ran. I personally run it with &lt;code&gt;--tb=native&lt;/code&gt; to receive tracebacks in native format as well as &lt;code&gt;--verbose&lt;/code&gt; so that each test function is explicitly listed out.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How do you Write a Test?
&lt;/h2&gt;

&lt;p&gt;Tests are pretty straightforward. You have to first start by importing the module that you would like to test. Let's imagine we have a &lt;code&gt;hello&lt;/code&gt; module that has a &lt;code&gt;world&lt;/code&gt; class and we want to make sure it always returns the &lt;code&gt;str&lt;/code&gt; "Hello World!" when we call the &lt;code&gt;greeting()&lt;/code&gt; function. We can do that with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from hello import world

def test_greeting():
    new_world = world()
    result = new_world.greeting()
    assert result == "Hello World!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an obviously very simple test, but hopefully you can get the idea of how to get started with &lt;code&gt;pytest&lt;/code&gt; and make your code much more reliable. I'll be walking you through more and more complex examples over the next few weeks.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>testing</category>
    </item>
    <item>
      <title>Create a Simple Github Profile in 3 Steps</title>
      <dc:creator>Neal Chambers</dc:creator>
      <pubDate>Sat, 28 Jan 2023 00:22:07 +0000</pubDate>
      <link>https://dev.to/macanudo527/create-a-simple-github-profile-in-3-steps-1g3o</link>
      <guid>https://dev.to/macanudo527/create-a-simple-github-profile-in-3-steps-1g3o</guid>
      <description>&lt;p&gt;Having a nice looking Github profile is a huge plus for getting freelance work and introducing yourself to the world. On top of that, it doesn't take much work to set one up because there are numerous tools that you can make use of to have a snazzy profile without much effort. Recently, I took the time to actually put together a &lt;a href="https://github.com/macanudo527/macanudo527" rel="noopener noreferrer"&gt;nice looking profile&lt;/a&gt; and thought I would share some of the things I learned. Let's take a look at 3 basic parts of a profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Make a Purty Header
&lt;/h2&gt;

&lt;p&gt;First things first, it is always good to greet visitors to your page. After all, they took the time to visit your page. The least you can do is say hello. Luckily, there is an easy to use tool for creating nice looking header messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/kyechan99/capsule-render" rel="noopener noreferrer"&gt;Capsule Render&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Kyechan99 has a nice header maker with plenty of options to choose from. You can define colors, widths and add any kind of text you want. They even have a handy wavy animated banner (I used the &lt;code&gt;type=waving&lt;/code&gt; option), which is what I personally went with for my &lt;a href="https://github.com/macanudo527" rel="noopener noreferrer"&gt;profile page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqnx3akus7b19j737ybdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqnx3akus7b19j737ybdw.png" alt="Green Header" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, of course if you do any kind of UI/UX work or CSS animation work that you want to show off, this is the perfect place to use something more involved in order to show perspective employers or collaborators your skills.&lt;/p&gt;

&lt;p&gt;It is also a good idea to write a very brief description of yourself. This should typically be 2-3 sentences and to the point. This is your elevator pitch if, you will. &lt;/p&gt;

&lt;p&gt;And give people a way to get a hold of you or find out more about you. Personally, I posted links to my Twitter, Dev.to, and LinkedIn. I feel like that gives people 3 very different ways to get a hold of me, depending on what they are looking for. Here is a useful &lt;a href="https://github.com/gauravghongde/social-icons" rel="noopener noreferrer"&gt;repo&lt;/a&gt; that contains most of the main icons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Make a Bullet List of your Experience
&lt;/h2&gt;

&lt;p&gt;After a quick introduction, it's important to make a quick list of the tools and languages you have experience with. I personally broke these out into some common categories so visitors can quickly scan over the page and see if I can help them with anything.&lt;/p&gt;

&lt;p&gt;I personally made an unordered bold list to title each of the categories of tools/language I use. After that, I added some badges of the key languages and tools that I feel like I have a decent amount experience with. There is a very handy list of shield.io badges with accompanying markdown available at this &lt;a href="https://github.com/Envoy-VC/awesome-badges" rel="noopener noreferrer"&gt;repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add Some Stats
&lt;/h2&gt;

&lt;p&gt;You should show off the fact that you are in fact an active developer. There are a handful of very good stat widgets that can show the number of commits and PRs you make as well as what languages you most commonly use on Github.&lt;/p&gt;

&lt;p&gt;You can find a few different stat widgets on &lt;a href="https://github.com/anuraghazra/github-readme-stats" rel="noopener noreferrer"&gt;anuraghazra's repo&lt;/a&gt;. They are fairly customizable and allow you to hide stats that may not be that flattering or could be misleading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;It's important to have at least a small business card available on Github, especially if you work with a lot of other developer's on open source projects. Having a good Github profile can help ease networking and connect with other like-minded developers.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Converting a While Loop into a Function using an Iterator in Python</title>
      <dc:creator>Neal Chambers</dc:creator>
      <pubDate>Wed, 05 Oct 2022 14:14:09 +0000</pubDate>
      <link>https://dev.to/macanudo527/converting-a-while-loop-into-a-function-using-an-iterator-in-python-51ol</link>
      <guid>https://dev.to/macanudo527/converting-a-while-loop-into-a-function-using-an-iterator-in-python-51ol</guid>
      <description>&lt;p&gt;I've been working on tax software to help you calculate the taxes on all those hard-earned &lt;del&gt;losses&lt;/del&gt; gains you made on crypto over the last year. It has actually been a very interesting learning experience since there are so many exchanges out there and every one of them seems to have their own special way of building out their API. Earlier, &lt;a href="https://dev.tourl"&gt;I covered an amazing python module that helps bring some sanity to the chaos - CCXT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And it has helped speed things along with software development. I just finished a plugin for Binance.com that allows users to pull in most of the information they need to retrieve and process their transaction information from that exchange. However, only Binance.com is available currently, and I would like to make it easier to build out more plugins for more exchanges more quickly.&lt;br&gt;
That's why we decided to abstract out the CCXT goodness into an abstract class that other users can then extend and add exchange-specific logic to. Easy enough, right?&lt;/p&gt;

&lt;p&gt;Every exchange seems to have a different way to do things though. One issue with retrieving transaction data from a server is that there could potentially be hundreds if not thousands of transactions that need to be downloaded. That obviously takes up a lot of resources that an exchange wouldn't like to tie up. So, it breaks larger pieces of data into pieces called pages. Moving through this data is done by pagination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0r4b5t069h70ea2p7tsx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0r4b5t069h70ea2p7tsx.jpg" alt="An illustration of the advantages of pagination. Woman is holding a scroll of paper. The label "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, every exchange seems to have its own way of pagination. Binance.com uses date-based pagination while Coinbase uses page-based pagination. Still other exchanges use id-based pagination. All of these pagination methods are essentially while loops that keep pulling data from the server until all the data has been retrieved. Parameters will have to be adjusted on each call of the endpoint based on the data previously retrieved.&lt;/p&gt;

&lt;p&gt;My first idea was to build a class that resolved the condition of the while loop. It would take the previous result set, evaluate and return a &lt;code&gt;bool&lt;/code&gt;. In that same function, it would readjust the values of the parameters to pull the new set of data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def evaluate_loop_expression(self, current_results: Any) -&amp;gt; bool:

        # First time the loop is executed, results will be None
        if current_results is None:
            return True

        # Did we reach the end of this market?
        end_market: bool = False

        if len(current_results):
            # All times are inclusive
            self.__since = current_results[len(current_results) - 1][_TIMESTAMP] + 1
        else:
            end_market = True

        if end_market and self.has_more_markets():
            self.__since = self.__exchange_start_time
            self.next_market()
            return True

        return False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, it shifts &lt;code&gt;self.__since&lt;/code&gt;, which marks the first timestamp to start pulling data from forward to the end of the &lt;code&gt;current_results&lt;/code&gt;. Or, if we pulled everything from this market (eg. BTCUSD) we can move on to the next market (eg. ETHUSD). Then, it returns &lt;code&gt;True&lt;/code&gt; if there is more data to pull or &lt;code&gt;False&lt;/code&gt; if not.&lt;/p&gt;

&lt;p&gt;This would be the condition evaluated in order to keep the while loop going:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while self.evaluate_loop_expression(results):
  # Fetch a new set of results with changed parameters.
  results = client.fetchTrades(self.parameters)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked okay. However, one function was serving two purposes and that is a bit of no-no in programming. So, how do you make this more readable and keep the same function?&lt;/p&gt;

&lt;p&gt;A lot of things are happening here. We need to change the fields being used to pull data based on previous data and then break out of the while loop if there is no more data to be retrieved.&lt;/p&gt;

&lt;p&gt;A simple iterator doesn't seem to work since we need to edit what is being returned by the iterator depending on what the previous results were.&lt;/p&gt;

&lt;p&gt;To make something &lt;em&gt;iterable&lt;/em&gt; in Python, we need to define &lt;code&gt;__iter__&lt;/code&gt;, which will initialize and return an iterator. &lt;/p&gt;

&lt;p&gt;An &lt;em&gt;iterator&lt;/em&gt; is a class that implements &lt;code&gt;__next__&lt;/code&gt;, which returns the next set of objects being iterated over.&lt;/p&gt;

&lt;p&gt;Although an iterator can be iterable (implement &lt;code&gt;__iter__&lt;/code&gt;), it doesn't have to be. For example, &lt;code&gt;str&lt;/code&gt; is iterable and returns an iterator that iterates over the characters in the string.&lt;/p&gt;

&lt;p&gt;So, what we need here is an iterator that we can update on each loop.&lt;/p&gt;

&lt;p&gt;What I ended up with was 3 classes:&lt;br&gt;
1) a pagination detail set that was iterable (returns an iterator when &lt;code&gt;iter()&lt;/code&gt; is called on it)&lt;br&gt;
2) a pagination iterator returned from 1) that returns new pagination details when &lt;code&gt;next()&lt;/code&gt; is called on it.&lt;br&gt;
3) a &lt;code&gt;NamedTuple&lt;/code&gt; of the pagination details.&lt;/p&gt;

&lt;p&gt;This is a lot more work, but it has a much cleaner design.&lt;/p&gt;

&lt;p&gt;Here is the &lt;code&gt;DateBasedPaginationDetailSet&lt;/code&gt; version of 1):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AbstractPaginationDetailSet:
    def __iter__(self) -&amp;gt; "AbstractPaginationDetailsIterator":
        raise NotImplementedError("Abstract method")


class DateBasedPaginationDetailSet(AbstractPaginationDetailSet):
    def __init__(
        self,
        exchange_start_time: int,
        limit: Optional[int] = None,
        markets: Optional[List[str]] = None,
        params: Optional[Dict[str, Union[int, str, None]]] = None,
        window: Optional[int] = None,
    ) -&amp;gt; None:

        super().__init__()
        self.__exchange_start_time: int = exchange_start_time
        self.__limit: Optional[int] = limit
        self.__markets: Optional[List[str]] = markets
        self.__params: Optional[Dict[str, Union[int, str, None]]] = params
        self.__window: Optional[int] = window

    def __iter__(self) -&amp;gt; "DateBasedPaginationDetailsIterator":
        return DateBasedPaginationDetailsIterator(
            self.__exchange_start_time,
            self.__limit,
            self.__markets,
            self.__params,
            self.__window,
        )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that this essentially only contains the details needed to build the iterator. This makes it so all we need to do is pass 2-3 parameters to initialize it and the plugin pulling the data will do the rest.&lt;/p&gt;

&lt;p&gt;And the iterator that &lt;code&gt;__iter__&lt;/code&gt; returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AbstractPaginationDetailsIterator:
    def __init__(self, limit: Optional[int], markets: Optional[List[str]] = None, params: Optional[Dict[str, Union[int, str, None]]] = None) -&amp;gt; None:
        self.__limit: Optional[int] = limit
        self.__markets: Optional[List[str]] = markets
        self.__market_count: int = 0
        self.__params: Optional[Dict[str, Union[int, str, None]]] = params

    def _get_market(self) -&amp;gt; Optional[str]:
        return self.__markets[self.__market_count] if self.__markets else None

    def _has_more_markets(self) -&amp;gt; bool:
        return self.__market_count &amp;lt;= len(self.__markets) if self.__markets else False

    def _next_market(self) -&amp;gt; None:
        self.__market_count += 1

    def _get_limit(self) -&amp;gt; Optional[int]:
        return self.__limit

    def _get_params(self) -&amp;gt; Optional[Dict[str, Union[int, str, None]]]:
        return self.__params

    def _get_since(self) -&amp;gt; Optional[int]:
        return None

    def update_fetched_elements(self, current_results: Any) -&amp;gt; None:
        raise NotImplementedError("Abstract method")

    def __next__(self) -&amp;gt; PaginationDetails:
        raise NotImplementedError("Abstract method")


class DateBasedPaginationDetailsIterator(AbstractPaginationDetailsIterator):
    def __init__(
        self,
        exchange_start_time: int,
        limit: Optional[int] = None,
        markets: Optional[List[str]] = None,
        params: Optional[Dict[str, Union[int, str, None]]] = None,
        window: Optional[int] = None,
    ) -&amp;gt; None:

        super().__init__(limit, markets, params)
        self.__end_of_data = False
        self.__since: int = exchange_start_time
        self.__exchange_start_time: int = exchange_start_time
        self.__now: int = int(datetime.now().timestamp()) * _MS_IN_SECOND
        self.__window: int = window if window else _DEFAULT_WINDOW

    def update_fetched_elements(self, current_results: Any) -&amp;gt; None:

        end_of_market: bool = False

        # Update Since if needed otherwise end_of_market
        if len(current_results):
            # All times are inclusive
            self.__since = current_results[len(current_results) - 1][_TIMESTAMP] + 1
        elif self.__window:
            self.__since += self.__window

        if self.__since &amp;gt; self.__now:
            end_of_market = True

        if end_of_market and self._has_more_markets():
            # we have reached the end of one market, now let's move on to the next
            self.__since = self.__exchange_start_time
            self._next_market()
        else:
            self.__end_of_data = True

    def _is_end_of_data(self) -&amp;gt; bool:
        return self.__end_of_data

    def _get_since(self) -&amp;gt; int:
        return self.__since

    def _get_end_of_window(self) -&amp;gt; int:
        return self.__since + self.__window

    def __next__(self) -&amp;gt; PaginationDetails:
        while not self._is_end_of_data():
            return PaginationDetails(
                symbol=self._get_market(),
                since=self._get_since(),
                limit=self._get_limit(),
                params=self._get_params(),
            )
        raise StopIteration(self)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I added a &lt;code&gt;update_fetched_elements&lt;/code&gt; to shift the window of time backward if we pulled the record limit (ie. we didn't pull all the records with one call). If the number of retrieve records is under the limit, I just need to move on to the next market if one exists.&lt;/p&gt;

&lt;p&gt;And finally the &lt;code&gt;NamedTuple&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PaginationDetails(NamedTuple):
    symbol: Optional[str]
    since: Optional[int]
    limit: Optional[int]
    params: Optional[Dict[str, Union[int, str, None]]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we have a custom iterator to use when pulling data via the REST API of the exchange.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>architecture</category>
      <category>api</category>
    </item>
    <item>
      <title>Using CCXT Implicit API methods</title>
      <dc:creator>Neal Chambers</dc:creator>
      <pubDate>Fri, 22 Jul 2022 07:32:43 +0000</pubDate>
      <link>https://dev.to/macanudo527/using-ccxt-implicit-api-methods-1on8</link>
      <guid>https://dev.to/macanudo527/using-ccxt-implicit-api-methods-1on8</guid>
      <description>&lt;p&gt;I wrote about the invaluable CCXT library before, which can be used to access the common methods of an exchange's API. CCXT supports a whole host of unified methods out-of-the-box. CCXT does all the heavy lifting in terms of giving you a standard set of data from each exchange.&lt;/p&gt;

&lt;p&gt;However, as we all know, each exchange can have its own quirks and special features. For instance, Binance, as well as several other exchanges, issues dividends if you choose to lock up certain crypto assets. For example, if you keep USDT on &lt;a href="https://accounts.binance.com/en/register?ref=72627473"&gt;Binance&lt;/a&gt;, you can earn up to 10% APR for the first $2000. &lt;/p&gt;

&lt;p&gt;There isn't a unified set of methods for retrieving or dealing with dividends built into CCXT, which means we need to use the exchange-specific methods or API endpoints to pull the data. Lucky for us, CCXT has a way of doing that.&lt;/p&gt;

&lt;p&gt;To use any of the underlying endpoints you simply string together the parts of the endpoint to form a function, leaving out the version number. For example, &lt;a href="https://binance-docs.github.io/apidocs/spot/en/#asset-dividend-record-user_data"&gt;the endpoint for dividends&lt;/a&gt; is &lt;code&gt;/sapi/v1/asset/assetDividend&lt;/code&gt;, and this is a &lt;code&gt;GET&lt;/code&gt; request as well so the function name becomes &lt;code&gt;.sapiGetAssetAssetDividend()&lt;/code&gt;. Note the use of CamelCase and the addition of 'Get' between the name of the API and the endpoint. &lt;/p&gt;

&lt;p&gt;It might be useful to look at the &lt;code&gt;describe()&lt;/code&gt; function of the class for each exchange to get an idea of how these functions are formed. You can take a look at the one for Binance &lt;a href="https://github.com/ccxt/ccxt/blob/315e9aba84a2acbeef9dbcb1b7afb5d9e42e72af/python/ccxt/binance.py#L36"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have the implicit API function, you will just need to pass the params for the endpoint in a Dict:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from ccxt import binance

client = binance(
  {
    "apiKey": "the_api_key_from_binance"
    "secret": "the_api_secret_from_binance"
  }
)

response = client.sapiGetAssetAssetDividend({"startTime":
    1656635948000, "endTime": 1658191192000, "limit": 500})

for dividend in response["rows"]:
    print(f"You received {dividend["amount"]} of
    {dividend["asset"] from {dividend["enInfo"]}.")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each endpoint has a different set of necessary and optional parameters that you can give it. You can check the details of what is required for the Dividend Endpoint &lt;a href="https://binance-docs.github.io/apidocs/spot/en/#asset-dividend-record-user_data"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most exchanges will have documentation for their API similar to Binance. Keep in mind that they may not be up-to-date, so you will want to sample the json response from an example call to see if it is what they say it is.&lt;/p&gt;

&lt;p&gt;Another thing I learned is that the standard set of CCXT calls will sort returned data with the most recent record in the first position, and the oldest in the last position. However, Binance and some other exchanges returns this in the opposite order. Again, double-check your data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;CCXT will provide a very good set of standard calls to get and post data to exchanges, but exchanges come in all sorts of shapes and sizes. Sometimes, you will need to get under the hood and call some of the endpoints unique to the exchange. These implicit API calls help you do just that. Don't be afraid to get your hands a little dirty. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post includes affiliate links; At no additional cost to you, I may receive compensation if you purchase products or services from the links provided in this article. Buying, selling, and trading crypto assets involves risk. Always Do Your Own Research before investing any money. This post is Not Financial Advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>blockchain</category>
      <category>api</category>
    </item>
    <item>
      <title>Working with CCXT Python Library</title>
      <dc:creator>Neal Chambers</dc:creator>
      <pubDate>Wed, 15 Jun 2022 13:39:46 +0000</pubDate>
      <link>https://dev.to/macanudo527/working-with-ccxt-python-library-2koe</link>
      <guid>https://dev.to/macanudo527/working-with-ccxt-python-library-2koe</guid>
      <description>&lt;p&gt;The &lt;a href="https://github.com/ccxt/ccxt"&gt;CCXT library&lt;/a&gt; is a powerful Python, JS, and PHP library that can be used to pull information from all of the major crypto asset exchanges. &lt;/p&gt;

&lt;p&gt;It has made my life a lot easier since it abstracts out the interface of each exchange. Instead of having to learn each API for each exchange or importing a separate library for each one, I just need to use one module - CCXT.&lt;/p&gt;

&lt;p&gt;It also eliminates having to worry about keeping up with all the updates of a particular API. CCXT takes care of all the updates for me, which good because some of these APIs have missing documentation. For example, Coinbase has outdated json responses.&lt;/p&gt;

&lt;p&gt;Getting started is easy. Just instantiate an exchange with your api key and api secret, which should have the basic permissions assigned to it. I got mine off of &lt;a href="https://accounts.binance.com/en/register?ref=72627473"&gt;Binance.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, navigate to the site, login and click 'API Management' under your profile:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dvjIQKcM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/non07yg65y0rocfmheyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dvjIQKcM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/non07yg65y0rocfmheyi.png" alt="Binance - Api Management" width="463" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on create API:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_4v5ZsVe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gikw1zns5du5czy0k6z5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_4v5ZsVe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gikw1zns5du5czy0k6z5.png" alt="Binance - Create API" width="510" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Give it a name:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---A28rVZT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/evhj8po9oxlugl4ix153.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---A28rVZT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/evhj8po9oxlugl4ix153.png" alt="Binance - Naming your API" width="467" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then you will be met with this screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RkNNOkqI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u53r4pjcvadhnjc5d392.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RkNNOkqI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u53r4pjcvadhnjc5d392.png" alt="Binance - Create an API" width="880" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1) Be sure to copy down your API key AND API secret. On Binance and a lot of other exchanges, it might only give you a copy button for the API key. And the QR Code may only contain the API key, but, generally speaking, you will need both when programming or doing anything with the API really.&lt;/p&gt;

&lt;p&gt;On top of that, you might not be able to copy the API secret once you save the new API, so be sure to copy it and save it for later.&lt;/p&gt;

&lt;p&gt;2) Optionally, you can 'edit restrictions' and limit what can be done with your API keys. I would start with reading permissions when you are first programming. You do not want to give your program the ability to make orders until you are sure you can lock it down.&lt;/p&gt;

&lt;p&gt;3) You can also limit access to certain IP addresses, which is really handy if you are accessing the exchange from a static IP. If you are accessing it from home, chances are you will not have a static IP though, so it is better not to check this.&lt;/p&gt;

&lt;p&gt;After that you are all set. To initialize the exchange in CCXT, you simply have to use the constructor for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from ccxt import binance

client = binance(
  {
    "apiKey": "the_api_key_from_binance"
    "secret": "the_api_secret_from_binance"
  }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like to import only the exchange I'm using to keep my code clean and less confusing.&lt;/p&gt;

&lt;p&gt;Once you have instantiated the exchange, you can reuse it many times to make calls to the private or public API. For example, you can retrieve historical pricing data in order to run backtests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;results = binance.fetchOHLCV("BTC/USDT", "1m", 1655298673, 1)

print(results)
[
     [
         165529867000, // UTC timestamp in milliseconds, integer
         24235.4,        // (O)pen price, float
         24240.6,        // (H)ighest price, float
         24230.0,        // (L)owest price, float
         24230.7,        // (C)losing price, float
         37.72941911    // (V)olume (in terms of the base currency), float
     ],
     ...
 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are numerous functions you can call on an exchange like this. For a full list, be sure to check the &lt;a href="https://docs.ccxt.com/en/latest/manual.html"&gt;CCXT documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you are anything like me, you use more than a few different exchanges when you trade crypto assets. In this world, of ever-changing policy and sudden crashes, you never know when you will have to jump off an exchange and move to another, so it is good to have more than one account. &lt;/p&gt;

&lt;p&gt;That's where learning a library like CCXT once comes in handy. You don't have to learn multiple libraries or have multiple dependencies for your project. It can also be used in javascript or PHP. Give it a try and let me know if it works for you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post includes affiliate links; At no additional cost to you, I may receive compensation if you purchase products or services from the links provided in this article. Buying, selling, and trading crypto assets involves risk. Always Do Your Own Research before investing any money. This post is Not Financial Advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
