2
\$\begingroup\$

I am coming from C# and I am learning how to use Python more seriously.

So I decided to try a little side project to help me see how much I earn (or lose) from my trades on the stock market.

I would like some advice about how I structured my project, in C# I am used to put each class in its own file, but I read that in Python we work with module, so I put several classes in the trades.py module. But does it make sense to put the trade importer in the same module as the calculator?

Here is a screenshot of my project hierarchy so far:

enter image description here

and a description of the files:

trades.py

import calculator.trades as t


from enum import Enum
import numpy as np


class TradeType(Enum):
    Buy = 1
    Sell = -1


class Trade:
    def __init__(self, id, trade_date, type, quantity, price, currency_id, company_id, account_id):
        self.id = id
        self.type = type
        self.account_id = account_id
        self.company_id = company_id
        self.currency_id = currency_id
        self.trade_date = trade_date
        self.quantity = quantity
        self.price = price


class TradePnlCalculator:
    def compute_pnl(self, trades):
        signed_quantities = np.array([-1 * t.type.value * t.quantity for t in trades])
        prices = np.array([t.price for t in trades])
        return np.dot(signed_quantities, prices)


class TradeImporter:
    def load_trades(self):
        # TODO Read the trades from a file
        return []

test_tradePnlCalculator.py

from unittest import TestCase

from calculator.trades import Trade, TradeType, TradePnlCalculator
import datetime


class TestTradePnlCalculator(TestCase):

    def setUp(self):
        now = datetime.datetime.now()
        self.trade1 = Trade(1, trade_date=now, type=TradeType.Buy, quantity=100, 
                            price=34.5, currency_id=1, company_id=1, account_id=1)
        self.trade2 = Trade(2, trade_date=now, type=TradeType.Sell, quantity=100, 
                            price=37.4, currency_id=1, company_id=1, account_id=1)

        self.calculator = TradePnlCalculator()

    def test_compute_pnl_returns_zero_when_there_is_no_trades(self):
        self.assertEquals(0, self.calculator.compute_pnl([]))


    def test_compute_pnl_returns_negative_flow_for_single_buy_trade(self):
        self.assertEquals(-3450, self.calculator.compute_pnl([self.trade1]))

    def test_compute_pnl_returns_positive_flow_for_single_sell_trade(self):
        self.assertEquals(3740, self.calculator.compute_pnl([self.trade2]))

    def test_compute_pnl_returns_correct_value_for_cancelling_trades(self):
        self.assertEquals(3740-3450, self.calculator.compute_pnl([self.trade1, self.trade2]))

run.py

importer = t.TradeImporter()
trades = importer.load_trades()

calculator = t.TradePnlCalculator()
pnl = calculator.compute_pnl(trades)

print('The profit for the trades is {} euros'.format(pnl))
\$\endgroup\$
1
  • 1
    \$\begingroup\$ As we all want to make our code more efficient or improve it in one way or another, try to write a title that summarizes what your code does, not what you want to get out of a review. Please see How to get the best value out of Code Review - Asking Questions for guidance on writing good question titles. \$\endgroup\$ Commented Oct 4, 2016 at 12:42

1 Answer 1

5
\$\begingroup\$

This is too sketchy to be able to do a proper review. But just based on what you've written in the post:

  1. None of this code updates the attributes of a Trade. So you could use a collections.namedtuple which avoids some duplication:

    Trade = namedtuple('Trade', 'id trade_date type quantity price currency_id '
                       'company_id account_id')
    
  2. In some languages (especially Java) everything has to be a member of a class. But this isn't the case in Python: you can just write a function if you want, without having to make a class whose only role is to act as a container for your function.

    So here there's no need for the TradePnlCalculator class — you could just define a function compute_pnl. And there's no need for the TradeImporter class — you could just define a function load_trades.

  3. In compute_pnl, you're not gaining anything from using NumPy — it costs more to build the NumPy arrays than you gain in faster operations. It would be simpler and faster to write:

    -sum(t.type.value * t.quantity * t.price for t in trades)
    
  4. The need for the negation of the result of this computation suggests that you have the values for Buy and Sell the wrong way round.

  5. The code in compute_pnl doesn't consult the currency_id so it would go wrong if given a list of trades in a mixture of currencies.

\$\endgroup\$
2
  • \$\begingroup\$ 4. When you buy a share, you get a positive amount of share, but a negative amount of currency is going out of your account. So if I create a function "compute_position" using the trade type, this time the value of Buy and Sell make sense... \$\endgroup\$ Commented Oct 4, 2016 at 16:46
  • \$\begingroup\$ 2. Indeed, in C# too everything has to be a member of a class. I try to respect the Single Reponsibility Principle and I still have some difficulties figuring out what to put in a module. 5. Yes, I tried to add other fields not used yet, because I did not want to provide a too simplistic example. I will provide complete code on my next questions. \$\endgroup\$ Commented Oct 4, 2016 at 16:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.