```import copy
import math

"""An object representing an open trade."""

def __init__(self, type, date):

:type type: float
:param date: When the trade was opened
:type date: datetime

"""
self.type = type
self.date = date

def __str__(self):
return "{0}\n{1}".format(self.type, self.date)

"""An object representing a closed trade."""

def __init__(self, type, date, shares, entry, exit):

:type type: float
:param date: When the trade was closed
:type date: datetime
:param shares: Number of shares
:type shares: float
:param entry: Entry price
:type entry: float
:param exit: Exit price
:type exit: float

"""
super().__init__(type, date)
self.shares = float(shares)
self.entry  = float(entry)
self.exit   = float(exit)

def __str__(self):
return "{0}\n{1}\n{2}\n{3}\n{4}".format(self.type,
self.date,
self.shares,
self.entry,
self.exit)

class position:
"""A parent object representing a position."""

def __init__(self, no, entry_price, shares, exit_price, stop_loss):
"""Open the position.

:param no: A unique position id number
:type no: float
:param entry_price: Entry price at which shares are longed/shorted
:type entry_price: float
:param shares: Number of shares to long/short
:type shares: float
:param exit_price: Price at which to take profit
:type exit_price: float
:param stop_loss: Price at which to cut losses
:type stop_loss: float

:return: A position
:rtype: position
"""
self.no            = no
self.type          = "None"
self.entry_price   = float(entry_price)
self.shares        = float(shares)
self.exit_price    = float(exit_price)
self.stop_loss     = float(stop_loss)

def show(self):
print("No.     {0}".format(self.no))
print("Type:   {0}".format(self.type))
print("Entry:  {0}".format(self.entry_price))
print("Shares: {0}".format(self.shares))
print("Exit:   {0}".format(self.exit_price))
print("Stop:   {0}\n".format(self.stop_loss))

class long_position(position):
"""A child object representing a long position."""

def __init__(self, no, entry_price, shares, exit_price=math.inf, stop_loss=0):
"""Open the position.

:param no: A unique position id number
:type no: float
:param entry_price: Entry price at which shares are longed
:type entry_price: float
:param shares: Number of shares to long
:type shares: float
:param exit_price: Price at which to take profit
:type exit_price: float
:param stop_loss: Price at which to cut losses
:type stop_loss: float

:return: A long position
:rtype: long_position
"""

if exit_price is False: exit_price = math.inf
if stop_loss is False: stop_loss = 0
super().__init__(no, entry_price, shares, exit_price, stop_loss)
self.type = 'long'

def close(self, percent, current_price):
"""Close the position.

:param percent: Percent of position size to close
:type percent: float
:param current_price: Closing price
:type current_price: float

:return: Amount of capital gained from closing position
:rtype: float
"""
shares = self.shares
self.shares *= 1.0 - percent
return shares * percent * current_price

def stop_hit(self, current_price):
if current_price <= self.stop_loss:
return(True)

class short_position(position):
"""A child object representing a short position."""

def __init__(self, no, entry_price, shares, exit_price=0, stop_loss=math.inf):
"""Open the position.

:param no: A unique position id number
:type no: int
:param entry_price: Entry price at which shares are shorted
:type entry_price: float
:param shares: Number of shares to short
:type shares: float
:param exit_price: Price at which to take profit
:type exit_price: float
:param stop_loss: Price at which to cut losses
:type stop_loss: float

:return: A short position
:rtype: short_position
"""
if exit_price is False: exit_price = 0
if stop_loss is False: stop_loss = math.inf
super().__init__(no, entry_price, shares, exit_price, stop_loss)
self.type = 'short'

def close(self, percent, current_price):
"""Close the position.

:param percent: Percent of position size to close
:type percent: float
:param current_price: Closing price
:type current_price: float

:return: Amount of capital gained from closing position
:rtype: float
"""
entry = self.shares * percent * self.entry_price
exit = self.shares * percent * current_price
self.shares *= 1.0 - percent
if entry - exit + entry <= 0:
return 0
else:
return entry - exit + entry

def stop_hit(self, current_price):
if current_price >= self.stop_loss:
return(True)

class account():
"""An object representing an exchange account."""

def __init__(self, initial_capital):
"""Initiate an account.

:param initial_capital: Starting capital to fund account
:type initial_capital: float

:return: An account object
:rtype: account
"""
self.initial_capital = float(initial_capital)
self.no              = 0
self.date            = None
self.equity          = []
self.positions       = []

def enter_position(self, type, entry_capital, entry_price, exit_price=False, stop_loss=False, commission=0):
"""Open a position.

:param type: Type of position e.g. ("long, short")
:type type: float
:param entry_price: Amount of capital invested into position
:type entry_price: float
:param entry_price: Entry price at which shares are longed/shorted
:type entry_price: float
:param exit_price: Price at which to take profit
:type exit_price: float
:param stop_loss: Price at which to cut losses
:type stop_loss: float
:param commision: Percent commission subtracted from position size
:type commision: float
"""
entry_capital = float(entry_capital)

if entry_capital < 0:
raise ValueError("Error: Entry capital must be positive")
elif entry_price < 0:
raise ValueError("Error: Entry price cannot be negative.")
raise ValueError("Error: Not enough buying power to enter position")
else:
if commission > 0:
shares = entry_capital / (entry_price + commission * entry_price)
else:
shares = entry_capital / entry_price

if type == 'long':
self.positions.append(long_position(self.no,
entry_price,
shares,
exit_price,
stop_loss))
elif type == 'short':
self.positions.append(short_position(self.no,
entry_price,
shares,
exit_price,
stop_loss))
else:
raise TypeError("Error: Invalid position type.")

self.no += 1

def close_position(self, position, percent, current_price, commission=0):
"""Close a position.

:param position: Position id number
:type position: int
:param percent: Percent of position size to close
:type percent: float
:param current_price: Price at which position is closed
:type current_price: float
:param commision: Percent commission subtracted from capital returned
:type commision: float
"""
if percent > 1 or percent < 0:
raise ValueError("Error: Percent must range between 0-1.")
elif current_price < 0:
raise ValueError("Error: Current price cannot be negative.")
else:
self.date,
position.shares * percent,
position.entry_price,
current_price))

if commission > 0:
closing_position_price = position.close(percent, current_price)
self.buying_power += (closing_position_price - closing_position_price * commission)
else:

def purge_positions(self):
"""Delete all empty positions."""
self.positions = [p for p in self.positions if p.shares > 0]

def show_positions(self):
"""Show all account positions."""
for p in self.positions: p.show()

def total_value(self, current_price):
"""Calculate total value of account

:param current_price: Price used to value open position sizes
:type current_price: float

:return: Total value of acocunt
:rtype: float
"""
temporary = copy.deepcopy(self)
for position in temporary.positions:
temporary.close_position(position, 1.0, current_price)