mirror of
https://github.com/wassname/catalyst.git
synced 2026-09-09 11:19:23 +08:00
Merge pull request #1213 from quantopian/youre-charging-me-what
DEV: Re-implement commission models to return correct results in the case of multiple fills
This commit is contained in:
Binary file not shown.
@@ -11,9 +11,11 @@ import pandas as pd
|
|||||||
from zipline import examples, run_algorithm
|
from zipline import examples, run_algorithm
|
||||||
from zipline.testing import test_resource_path, tmp_dir
|
from zipline.testing import test_resource_path, tmp_dir
|
||||||
from zipline.utils.cache import dataframe_cache
|
from zipline.utils.cache import dataframe_cache
|
||||||
|
from zipline.data.bundles import register
|
||||||
|
|
||||||
banner = """
|
banner = """
|
||||||
Please verify that the new perfomance is more correct than the old performance.
|
Please verify that the new performance is more correct than the old
|
||||||
|
performance.
|
||||||
|
|
||||||
To do this, please inspect `new` and `old` which are mappings from the name of
|
To do this, please inspect `new` and `old` which are mappings from the name of
|
||||||
the example to the results.
|
the example to the results.
|
||||||
@@ -37,6 +39,9 @@ def main(ctx):
|
|||||||
"""Rebuild the perf data for test_examples
|
"""Rebuild the perf data for test_examples
|
||||||
"""
|
"""
|
||||||
example_path = test_resource_path('example_data.tar.gz')
|
example_path = test_resource_path('example_data.tar.gz')
|
||||||
|
|
||||||
|
register('test', lambda *args: None)
|
||||||
|
|
||||||
with tmp_dir() as d:
|
with tmp_dir() as d:
|
||||||
with tarfile.open(example_path) as tar:
|
with tarfile.open(example_path) as tar:
|
||||||
tar.extractall(d.path)
|
tar.extractall(d.path)
|
||||||
@@ -67,6 +72,7 @@ def main(ctx):
|
|||||||
environ={
|
environ={
|
||||||
'ZIPLINE_ROOT': d.getpath('example_data/root'),
|
'ZIPLINE_ROOT': d.getpath('example_data/root'),
|
||||||
},
|
},
|
||||||
|
capital_base=1e7,
|
||||||
**mod._test_args()
|
**mod._test_args()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+32
-13
@@ -19,6 +19,7 @@ from textwrap import dedent
|
|||||||
from unittest import TestCase, skip
|
from unittest import TestCase, skip
|
||||||
|
|
||||||
import logbook
|
import logbook
|
||||||
|
import toolz
|
||||||
from logbook import TestHandler, WARNING
|
from logbook import TestHandler, WARNING
|
||||||
from mock import MagicMock
|
from mock import MagicMock
|
||||||
from nose_parameterized import parameterized
|
from nose_parameterized import parameterized
|
||||||
@@ -1488,14 +1489,18 @@ def handle_data(context, data):
|
|||||||
self.assertEqual(len(all_txns), 1)
|
self.assertEqual(len(all_txns), 1)
|
||||||
txn = all_txns[0]
|
txn = all_txns[0]
|
||||||
|
|
||||||
self.assertEqual(100.0, txn["commission"])
|
|
||||||
expected_spread = 0.05
|
expected_spread = 0.05
|
||||||
expected_commish = 0.10
|
expected_price = test_algo.recorded_vars["price"] - expected_spread
|
||||||
expected_price = test_algo.recorded_vars["price"] - expected_spread \
|
|
||||||
- expected_commish
|
|
||||||
|
|
||||||
self.assertEqual(expected_price, txn['price'])
|
self.assertEqual(expected_price, txn['price'])
|
||||||
|
|
||||||
|
# make sure that the $100 commission was applied to our cash
|
||||||
|
# the txn was for -1000 shares at 9.95, means -9.95k. our capital_used
|
||||||
|
# for that day was therefore 9.95k, but after the $100 commission,
|
||||||
|
# it should be 9.85k.
|
||||||
|
self.assertEqual(9850, results.capital_used[1])
|
||||||
|
self.assertEqual(100, results["orders"][1][0]["commission"])
|
||||||
|
|
||||||
@parameterized.expand(
|
@parameterized.expand(
|
||||||
[
|
[
|
||||||
('no_minimum_commission', 0,),
|
('no_minimum_commission', 0,),
|
||||||
@@ -1543,7 +1548,6 @@ def handle_data(context, data):
|
|||||||
sim_params=self.sim_params,
|
sim_params=self.sim_params,
|
||||||
env=self.env,
|
env=self.env,
|
||||||
)
|
)
|
||||||
set_algo_instance(test_algo)
|
|
||||||
trades = factory.create_daily_trade_source(
|
trades = factory.create_daily_trade_source(
|
||||||
[0], self.sim_params, self.env)
|
[0], self.sim_params, self.env)
|
||||||
data_portal = create_data_portal_from_trade_history(
|
data_portal = create_data_portal_from_trade_history(
|
||||||
@@ -1555,13 +1559,28 @@ def handle_data(context, data):
|
|||||||
for val in sublist]
|
for val in sublist]
|
||||||
|
|
||||||
self.assertEqual(len(all_txns), 67)
|
self.assertEqual(len(all_txns), 67)
|
||||||
first_txn = all_txns[0]
|
# all_orders are all the incremental versions of the
|
||||||
|
# orders as each new fill comes in.
|
||||||
|
all_orders = list(toolz.concat(results['orders']))
|
||||||
|
|
||||||
if minimum_commission == 0:
|
if minimum_commission == 0:
|
||||||
commish = first_txn["amount"] * 0.02
|
# for each incremental version of each order, the commission
|
||||||
self.assertEqual(commish, first_txn["commission"])
|
# should be its filled amount * 0.02
|
||||||
|
for order_ in all_orders:
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
order_["filled"] * 0.02,
|
||||||
|
order_["commission"]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.assertEqual(minimum_commission, first_txn["commission"])
|
# the commission should be at least the min_trade_cost
|
||||||
|
for order_ in all_orders:
|
||||||
|
if order_["filled"] > 0:
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
max(order_["filled"] * 0.02, minimum_commission),
|
||||||
|
order_["commission"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assertEqual(0, order_["commission"])
|
||||||
finally:
|
finally:
|
||||||
tempdir.cleanup()
|
tempdir.cleanup()
|
||||||
|
|
||||||
@@ -3046,7 +3065,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
|||||||
self.assertDictContainsSubset(
|
self.assertDictContainsSubset(
|
||||||
{
|
{
|
||||||
'amount': order_size,
|
'amount': order_size,
|
||||||
'commission': 0.0,
|
'commission': None,
|
||||||
'dt': self.test_days[1],
|
'dt': self.test_days[1],
|
||||||
'price': initial_fill_prices[sid],
|
'price': initial_fill_prices[sid],
|
||||||
'sid': sid,
|
'sid': sid,
|
||||||
@@ -3143,7 +3162,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
|||||||
self.assertDictContainsSubset(
|
self.assertDictContainsSubset(
|
||||||
{
|
{
|
||||||
'amount': 10,
|
'amount': 10,
|
||||||
'commission': None,
|
'commission': 0,
|
||||||
'created': first_asset_end_date,
|
'created': first_asset_end_date,
|
||||||
'dt': first_asset_end_date,
|
'dt': first_asset_end_date,
|
||||||
'sid': assets[0],
|
'sid': assets[0],
|
||||||
@@ -3158,7 +3177,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
|||||||
self.assertDictContainsSubset(
|
self.assertDictContainsSubset(
|
||||||
{
|
{
|
||||||
'amount': 10,
|
'amount': 10,
|
||||||
'commission': None,
|
'commission': 0,
|
||||||
'created': first_asset_end_date,
|
'created': first_asset_end_date,
|
||||||
'dt': first_asset_auto_close_date,
|
'dt': first_asset_auto_close_date,
|
||||||
'sid': assets[0],
|
'sid': assets[0],
|
||||||
@@ -3252,7 +3271,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
|||||||
self.assertDictContainsSubset(
|
self.assertDictContainsSubset(
|
||||||
{
|
{
|
||||||
'amount': order_size,
|
'amount': order_size,
|
||||||
'commission': 0.0,
|
'commission': None,
|
||||||
'dt': backtest_minutes[1],
|
'dt': backtest_minutes[1],
|
||||||
'price': initial_fill_prices[sid],
|
'price': initial_fill_prices[sid],
|
||||||
'sid': sid,
|
'sid': sid,
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
from textwrap import dedent
|
||||||
|
|
||||||
|
from zipline import TradingAlgorithm
|
||||||
|
from zipline.finance.commission import PerTrade, PerShare, PerDollar
|
||||||
|
from zipline.finance.order import Order
|
||||||
|
from zipline.finance.transaction import Transaction
|
||||||
|
from zipline.testing import ZiplineTestCase, trades_by_sid_to_dfs
|
||||||
|
from zipline.testing.fixtures import (
|
||||||
|
WithAssetFinder,
|
||||||
|
WithSimParams,
|
||||||
|
WithDataPortal
|
||||||
|
)
|
||||||
|
from zipline.utils import factory
|
||||||
|
|
||||||
|
|
||||||
|
class CommissionUnitTests(WithAssetFinder, ZiplineTestCase):
|
||||||
|
ASSET_FINDER_EQUITY_SIDS = 1, 2
|
||||||
|
|
||||||
|
def generate_order_and_txns(self):
|
||||||
|
asset1 = self.asset_finder.retrieve_asset(1)
|
||||||
|
|
||||||
|
# one order
|
||||||
|
order = Order(dt=None, sid=asset1, amount=500)
|
||||||
|
|
||||||
|
# three fills
|
||||||
|
txn1 = Transaction(sid=asset1, amount=230, dt=None,
|
||||||
|
price=100, order_id=order.id)
|
||||||
|
|
||||||
|
txn2 = Transaction(sid=asset1, amount=170, dt=None,
|
||||||
|
price=101, order_id=order.id)
|
||||||
|
|
||||||
|
txn3 = Transaction(sid=asset1, amount=100, dt=None,
|
||||||
|
price=102, order_id=order.id)
|
||||||
|
|
||||||
|
return order, [txn1, txn2, txn3]
|
||||||
|
|
||||||
|
def test_per_trade(self):
|
||||||
|
model = PerTrade(cost=10)
|
||||||
|
|
||||||
|
order, txns = self.generate_order_and_txns()
|
||||||
|
|
||||||
|
self.assertEqual(10, model.calculate(order, txns[0]))
|
||||||
|
|
||||||
|
order.commission = 10
|
||||||
|
|
||||||
|
self.assertEqual(0, model.calculate(order, txns[1]))
|
||||||
|
self.assertEqual(0, model.calculate(order, txns[2]))
|
||||||
|
|
||||||
|
def test_per_share_no_minimum(self):
|
||||||
|
model = PerShare(cost=0.0075, min_trade_cost=None)
|
||||||
|
|
||||||
|
order, txns = self.generate_order_and_txns()
|
||||||
|
|
||||||
|
# make sure each commission is pro-rated
|
||||||
|
self.assertAlmostEqual(1.725, model.calculate(order, txns[0]))
|
||||||
|
self.assertAlmostEqual(1.275, model.calculate(order, txns[1]))
|
||||||
|
self.assertAlmostEqual(0.75, model.calculate(order, txns[2]))
|
||||||
|
|
||||||
|
def verify_per_share_commissions(self, model, commission_totals):
|
||||||
|
order, txns = self.generate_order_and_txns()
|
||||||
|
|
||||||
|
for i, commission_total in enumerate(commission_totals):
|
||||||
|
order.commission += model.calculate(order, txns[i])
|
||||||
|
self.assertAlmostEqual(commission_total, order.commission)
|
||||||
|
order.filled += txns[i].amount
|
||||||
|
|
||||||
|
def test_per_share_with_minimum(self):
|
||||||
|
# minimum is met by the first trade
|
||||||
|
self.verify_per_share_commissions(
|
||||||
|
PerShare(cost=0.0075, min_trade_cost=1),
|
||||||
|
[1.725, 3, 3.75]
|
||||||
|
)
|
||||||
|
|
||||||
|
# minimum is met by the second trade
|
||||||
|
self.verify_per_share_commissions(
|
||||||
|
PerShare(cost=0.0075, min_trade_cost=2.5),
|
||||||
|
[2.5, 3, 3.75]
|
||||||
|
)
|
||||||
|
|
||||||
|
# minimum is met by the third trade
|
||||||
|
self.verify_per_share_commissions(
|
||||||
|
PerShare(cost=0.0075, min_trade_cost=3.5),
|
||||||
|
[3.5, 3.5, 3.75]
|
||||||
|
)
|
||||||
|
|
||||||
|
# minimum is not met by any of the trades
|
||||||
|
self.verify_per_share_commissions(
|
||||||
|
PerShare(cost=0.0075, min_trade_cost=5.5),
|
||||||
|
[5.5, 5.5, 5.5]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_per_dollar(self):
|
||||||
|
model = PerDollar(cost=0.0015)
|
||||||
|
|
||||||
|
order, txns = self.generate_order_and_txns()
|
||||||
|
|
||||||
|
# make sure each commission is pro-rated
|
||||||
|
self.assertAlmostEqual(34.5, model.calculate(order, txns[0]))
|
||||||
|
self.assertAlmostEqual(25.755, model.calculate(order, txns[1]))
|
||||||
|
self.assertAlmostEqual(15.3, model.calculate(order, txns[2]))
|
||||||
|
|
||||||
|
|
||||||
|
class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||||
|
# make sure order commissions are properly incremented
|
||||||
|
|
||||||
|
sidint, = ASSET_FINDER_EQUITY_SIDS = (133,)
|
||||||
|
|
||||||
|
code = dedent(
|
||||||
|
"""
|
||||||
|
from zipline.api import (
|
||||||
|
sid, order, set_slippage, slippage, FixedSlippage,
|
||||||
|
set_commission, commission
|
||||||
|
)
|
||||||
|
|
||||||
|
def initialize(context):
|
||||||
|
# for these tests, let us take out the entire bar with no price
|
||||||
|
# impact
|
||||||
|
set_slippage(slippage.VolumeShareSlippage(1.0, 0))
|
||||||
|
|
||||||
|
{0}
|
||||||
|
context.ordered = False
|
||||||
|
|
||||||
|
|
||||||
|
def handle_data(context, data):
|
||||||
|
if not context.ordered:
|
||||||
|
order(sid(133), {1})
|
||||||
|
context.ordered = True
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_daily_bar_data(cls):
|
||||||
|
num_days = len(cls.sim_params.trading_days)
|
||||||
|
|
||||||
|
return trades_by_sid_to_dfs(
|
||||||
|
{
|
||||||
|
cls.sidint: factory.create_trade_history(
|
||||||
|
cls.sidint,
|
||||||
|
[10.0] * num_days,
|
||||||
|
[100.0] * num_days,
|
||||||
|
timedelta(days=1),
|
||||||
|
cls.sim_params,
|
||||||
|
cls.env,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
index=cls.sim_params.trading_days,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_results(self, algo_code):
|
||||||
|
algo = TradingAlgorithm(
|
||||||
|
script=algo_code,
|
||||||
|
env=self.env,
|
||||||
|
sim_params=self.sim_params
|
||||||
|
)
|
||||||
|
|
||||||
|
return algo.run(self.data_portal)
|
||||||
|
|
||||||
|
def test_per_trade(self):
|
||||||
|
results = self.get_results(
|
||||||
|
self.code.format("set_commission(commission.PerTrade(1))", 300)
|
||||||
|
)
|
||||||
|
|
||||||
|
# should be 3 fills at 100 shares apiece
|
||||||
|
# one order split among 3 days, each copy of the order should have a
|
||||||
|
# commission of one dollar
|
||||||
|
for orders in results.orders[1:4]:
|
||||||
|
self.assertEqual(1, orders[0]["commission"])
|
||||||
|
|
||||||
|
self.verify_capital_used(results, [-1001, -1000, -1000])
|
||||||
|
|
||||||
|
def test_per_share_no_minimum(self):
|
||||||
|
results = self.get_results(
|
||||||
|
self.code.format("set_commission(commission.PerShare(0.05, None))",
|
||||||
|
300)
|
||||||
|
)
|
||||||
|
|
||||||
|
# should be 3 fills at 100 shares apiece
|
||||||
|
# one order split among 3 days, each fill generates an additional
|
||||||
|
# 100 * 0.05 = $5 in commission
|
||||||
|
for i, orders in enumerate(results.orders[1:4]):
|
||||||
|
self.assertEqual((i + 1) * 5, orders[0]["commission"])
|
||||||
|
|
||||||
|
self.verify_capital_used(results, [-1005, -1005, -1005])
|
||||||
|
|
||||||
|
def test_per_share_with_minimum(self):
|
||||||
|
# minimum hit by first trade
|
||||||
|
results = self.get_results(
|
||||||
|
self.code.format("set_commission(commission.PerShare(0.05, 3))",
|
||||||
|
300)
|
||||||
|
)
|
||||||
|
|
||||||
|
# commissions should be 5, 10, 15
|
||||||
|
for i, orders in enumerate(results.orders[1:4]):
|
||||||
|
self.assertEqual((i + 1) * 5, orders[0]["commission"])
|
||||||
|
|
||||||
|
self.verify_capital_used(results, [-1005, -1005, -1005])
|
||||||
|
|
||||||
|
# minimum hit by second trade
|
||||||
|
results = self.get_results(
|
||||||
|
self.code.format("set_commission(commission.PerShare(0.05, 8))",
|
||||||
|
300)
|
||||||
|
)
|
||||||
|
|
||||||
|
# commissions should be 8, 10, 15
|
||||||
|
self.assertEqual(8, results.orders[1][0]["commission"])
|
||||||
|
self.assertEqual(10, results.orders[2][0]["commission"])
|
||||||
|
self.assertEqual(15, results.orders[3][0]["commission"])
|
||||||
|
|
||||||
|
self.verify_capital_used(results, [-1008, -1002, -1005])
|
||||||
|
|
||||||
|
# minimum hit by third trade
|
||||||
|
results = self.get_results(
|
||||||
|
self.code.format("set_commission(commission.PerShare(0.05, 12))",
|
||||||
|
300)
|
||||||
|
)
|
||||||
|
|
||||||
|
# commissions should be 12, 12, 15
|
||||||
|
self.assertEqual(12, results.orders[1][0]["commission"])
|
||||||
|
self.assertEqual(12, results.orders[2][0]["commission"])
|
||||||
|
self.assertEqual(15, results.orders[3][0]["commission"])
|
||||||
|
|
||||||
|
self.verify_capital_used(results, [-1012, -1000, -1003])
|
||||||
|
|
||||||
|
# minimum never hit
|
||||||
|
results = self.get_results(
|
||||||
|
self.code.format("set_commission(commission.PerShare(0.05, 18))",
|
||||||
|
300)
|
||||||
|
)
|
||||||
|
|
||||||
|
# commissions should be 18, 18, 18
|
||||||
|
self.assertEqual(18, results.orders[1][0]["commission"])
|
||||||
|
self.assertEqual(18, results.orders[2][0]["commission"])
|
||||||
|
self.assertEqual(18, results.orders[3][0]["commission"])
|
||||||
|
|
||||||
|
self.verify_capital_used(results, [-1018, -1000, -1000])
|
||||||
|
|
||||||
|
def test_per_dollar(self):
|
||||||
|
results = self.get_results(
|
||||||
|
self.code.format("set_commission(commission.PerDollar(0.01))", 300)
|
||||||
|
)
|
||||||
|
|
||||||
|
# should be 3 fills at 100 shares apiece, each fill is worth $1k, so
|
||||||
|
# incremental commission of $1000 * 0.01 = $10
|
||||||
|
|
||||||
|
# commissions should be $10, $20, $30
|
||||||
|
for i, orders in enumerate(results.orders[1:4]):
|
||||||
|
self.assertEqual((i + 1) * 10, orders[0]["commission"])
|
||||||
|
|
||||||
|
self.verify_capital_used(results, [-1010, -1010, -1010])
|
||||||
|
|
||||||
|
def verify_capital_used(self, results, values):
|
||||||
|
self.assertEqual(values[0], results.capital_used[1])
|
||||||
|
self.assertEqual(values[1], results.capital_used[2])
|
||||||
|
self.assertEqual(values[2], results.capital_used[3])
|
||||||
@@ -41,7 +41,6 @@ from zipline.finance.transaction import create_transaction
|
|||||||
import zipline.utils.math_utils as zp_math
|
import zipline.utils.math_utils as zp_math
|
||||||
|
|
||||||
from zipline.finance.blotter import Order
|
from zipline.finance.blotter import Order
|
||||||
from zipline.finance.commission import PerShare, PerTrade, PerDollar
|
|
||||||
from zipline.finance.performance.position import Position
|
from zipline.finance.performance.position import Position
|
||||||
from zipline.utils.factory import create_simulation_parameters
|
from zipline.utils.factory import create_simulation_parameters
|
||||||
from zipline.utils.serialization_utils import (
|
from zipline.utils.serialization_utils import (
|
||||||
@@ -392,183 +391,6 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
|
|||||||
(i, perf_kind, perf_result['returns']))
|
(i, perf_kind, perf_result['returns']))
|
||||||
|
|
||||||
|
|
||||||
class TestCommissionEvents(WithSimParams, WithTmpDir, ZiplineTestCase):
|
|
||||||
START_DATE = pd.Timestamp('2006-01-03', tz='utc')
|
|
||||||
END_DATE = pd.Timestamp('2006-01-09', tz='utc')
|
|
||||||
ASSET_FINDER_EQUITY_SIDS = 0, 1, 133
|
|
||||||
SIM_PARAMS_CAPITAL_BASE = 10e3
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def init_class_fixtures(cls):
|
|
||||||
super(TestCommissionEvents, cls).init_class_fixtures()
|
|
||||||
cls.asset1 = cls.env.asset_finder.retrieve_asset(1)
|
|
||||||
|
|
||||||
def test_commission_event(self):
|
|
||||||
trade_events = factory.create_trade_history(
|
|
||||||
self.asset1,
|
|
||||||
[10, 10, 10, 10, 10],
|
|
||||||
[100, 100, 100, 100, 100],
|
|
||||||
oneday,
|
|
||||||
self.sim_params,
|
|
||||||
env=self.env
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test commission models and validate result
|
|
||||||
# Expected commission amounts:
|
|
||||||
# PerShare commission: 1.00, 1.00, 1.50 = $3.50
|
|
||||||
# PerTrade commission: 5.00, 5.00, 5.00 = $15.00
|
|
||||||
# PerDollar commission: 1.50, 3.00, 4.50 = $9.00
|
|
||||||
# Total commission = $3.50 + $15.00 + $9.00 = $27.50
|
|
||||||
|
|
||||||
data_portal = create_data_portal_from_trade_history(
|
|
||||||
self.env,
|
|
||||||
self.tmpdir,
|
|
||||||
self.sim_params,
|
|
||||||
{1: trade_events},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create 3 transactions: 50, 100, 150 shares traded @ $20
|
|
||||||
first_trade = trade_events[0]
|
|
||||||
transactions = [create_txn(first_trade.sid, first_trade.dt, 20, i)
|
|
||||||
for i in [50, 100, 150]]
|
|
||||||
|
|
||||||
# Create commission models and validate that produce expected
|
|
||||||
# commissions.
|
|
||||||
models = [PerShare(cost=0.01, min_trade_cost=1.00),
|
|
||||||
PerTrade(cost=5.00),
|
|
||||||
PerDollar(cost=0.0015)]
|
|
||||||
expected_results = [3.50, 15.0, 9.0]
|
|
||||||
|
|
||||||
for model, expected in zip(models, expected_results):
|
|
||||||
total_commission = 0
|
|
||||||
for trade in transactions:
|
|
||||||
total_commission += model.calculate(trade)[1]
|
|
||||||
self.assertEqual(total_commission, expected)
|
|
||||||
|
|
||||||
# Verify that commission events are handled correctly by
|
|
||||||
# PerformanceTracker.
|
|
||||||
commissions = {}
|
|
||||||
cash_adj_dt = trade_events[0].dt
|
|
||||||
cash_adjustment = factory.create_commission(1, 300.0, cash_adj_dt)
|
|
||||||
commissions[cash_adj_dt] = [cash_adjustment]
|
|
||||||
|
|
||||||
# Insert a purchase order.
|
|
||||||
txns = [create_txn(first_trade.sid, first_trade.dt, 20, 1)]
|
|
||||||
results = calculate_results(self.sim_params,
|
|
||||||
self.env,
|
|
||||||
data_portal,
|
|
||||||
txns=txns,
|
|
||||||
commissions=commissions)
|
|
||||||
|
|
||||||
# Validate that we lost 320 dollars from our cash pool.
|
|
||||||
self.assertEqual(results[-1]['cumulative_perf']['ending_cash'],
|
|
||||||
9680, "Should have lost 320 from cash pool.")
|
|
||||||
# Validate that the cost basis of our position changed.
|
|
||||||
self.assertEqual(results[-1]['daily_perf']['positions']
|
|
||||||
[0]['cost_basis'], 320.0)
|
|
||||||
# Validate that the account attributes were updated.
|
|
||||||
account = results[1]['account']
|
|
||||||
self.assertEqual(float('inf'), account['day_trades_remaining'])
|
|
||||||
np.testing.assert_allclose(0.001, account['leverage'], rtol=1e-3,
|
|
||||||
atol=1e-4)
|
|
||||||
np.testing.assert_allclose(9680, account['regt_equity'], rtol=1e-3)
|
|
||||||
self.assertEqual(float('inf'), account['regt_margin'])
|
|
||||||
np.testing.assert_allclose(9680, account['available_funds'],
|
|
||||||
rtol=1e-3)
|
|
||||||
self.assertEqual(0, account['maintenance_margin_requirement'])
|
|
||||||
np.testing.assert_allclose(9690,
|
|
||||||
account['equity_with_loan'], rtol=1e-3)
|
|
||||||
self.assertEqual(float('inf'), account['buying_power'])
|
|
||||||
self.assertEqual(0, account['initial_margin_requirement'])
|
|
||||||
np.testing.assert_allclose(9680, account['excess_liquidity'],
|
|
||||||
rtol=1e-3)
|
|
||||||
np.testing.assert_allclose(9680, account['settled_cash'],
|
|
||||||
rtol=1e-3)
|
|
||||||
np.testing.assert_allclose(9690, account['net_liquidation'],
|
|
||||||
rtol=1e-3)
|
|
||||||
np.testing.assert_allclose(0.999, account['cushion'], rtol=1e-3)
|
|
||||||
np.testing.assert_allclose(10, account['total_positions_value'],
|
|
||||||
rtol=1e-3)
|
|
||||||
self.assertEqual(0, account['accrued_interest'])
|
|
||||||
|
|
||||||
def test_commission_zero_position(self):
|
|
||||||
"""
|
|
||||||
Ensure no div-by-zero errors.
|
|
||||||
"""
|
|
||||||
events = factory.create_trade_history(
|
|
||||||
self.asset1,
|
|
||||||
[10, 10, 10, 10, 10],
|
|
||||||
[100, 100, 100, 100, 100],
|
|
||||||
oneday,
|
|
||||||
self.sim_params,
|
|
||||||
env=self.env
|
|
||||||
)
|
|
||||||
|
|
||||||
data_portal = create_data_portal_from_trade_history(
|
|
||||||
self.env,
|
|
||||||
self.tmpdir,
|
|
||||||
self.sim_params,
|
|
||||||
{1: events},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Buy and sell the same sid so that we have a zero position by the
|
|
||||||
# time of events[3].
|
|
||||||
txns = [
|
|
||||||
create_txn(self.asset1, events[0].dt, 20, 1),
|
|
||||||
create_txn(self.asset1, events[0].dt, 20, -1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add a cash adjustment at the time of event[3].
|
|
||||||
cash_adj_dt = events[3].dt
|
|
||||||
commissions = {}
|
|
||||||
cash_adjustment = factory.create_commission(1, 300.0, cash_adj_dt)
|
|
||||||
commissions[cash_adj_dt] = [cash_adjustment]
|
|
||||||
|
|
||||||
results = calculate_results(self.sim_params,
|
|
||||||
self.env,
|
|
||||||
data_portal,
|
|
||||||
txns=txns,
|
|
||||||
commissions=commissions)
|
|
||||||
# Validate that we lost 300 dollars from our cash pool.
|
|
||||||
self.assertEqual(results[-1]['cumulative_perf']['ending_cash'],
|
|
||||||
9700)
|
|
||||||
|
|
||||||
def test_commission_no_position(self):
|
|
||||||
"""
|
|
||||||
Ensure no position-not-found or sid-not-found errors.
|
|
||||||
"""
|
|
||||||
events = factory.create_trade_history(
|
|
||||||
self.asset1,
|
|
||||||
[10, 10, 10, 10, 10],
|
|
||||||
[100, 100, 100, 100, 100],
|
|
||||||
oneday,
|
|
||||||
self.sim_params,
|
|
||||||
env=self.env
|
|
||||||
)
|
|
||||||
|
|
||||||
data_portal = create_data_portal_from_trade_history(
|
|
||||||
self.env,
|
|
||||||
self.tmpdir,
|
|
||||||
self.sim_params,
|
|
||||||
{1: events},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add a cash adjustment at the time of event[3].
|
|
||||||
cash_adj_dt = events[3].dt
|
|
||||||
commissions = {}
|
|
||||||
cash_adjustment = factory.create_commission(self.asset1,
|
|
||||||
300.0, cash_adj_dt)
|
|
||||||
commissions[cash_adj_dt] = [cash_adjustment]
|
|
||||||
|
|
||||||
results = calculate_results(self.sim_params,
|
|
||||||
self.env,
|
|
||||||
data_portal,
|
|
||||||
commissions=commissions)
|
|
||||||
# Validate that we lost 300 dollars from our cash pool.
|
|
||||||
self.assertEqual(results[-1]['cumulative_perf']['ending_cash'],
|
|
||||||
9700)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDividendPerformance(WithSimParams,
|
class TestDividendPerformance(WithSimParams,
|
||||||
WithInstanceTmpDir,
|
WithInstanceTmpDir,
|
||||||
ZiplineTestCase):
|
ZiplineTestCase):
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ from zipline.errors import (
|
|||||||
OrderInBeforeTradingStart)
|
OrderInBeforeTradingStart)
|
||||||
from zipline.finance.trading import TradingEnvironment
|
from zipline.finance.trading import TradingEnvironment
|
||||||
from zipline.finance.blotter import Blotter
|
from zipline.finance.blotter import Blotter
|
||||||
from zipline.finance.commission import PerShare, PerTrade, PerDollar
|
from zipline.finance.commission import PerShare, CommissionModel
|
||||||
from zipline.finance.controls import (
|
from zipline.finance.controls import (
|
||||||
LongOnly,
|
LongOnly,
|
||||||
MaxOrderCount,
|
MaxOrderCount,
|
||||||
@@ -1483,11 +1483,11 @@ class TradingAlgorithm(object):
|
|||||||
|
|
||||||
@api_method
|
@api_method
|
||||||
def set_commission(self, commission):
|
def set_commission(self, commission):
|
||||||
"""Sets the commision model for the simulation.
|
"""Sets the commission model for the simulation.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
commission : PerShare, PerTrade, or PerDollar
|
commission : CommissionModel
|
||||||
The commission model to use.
|
The commission model to use.
|
||||||
|
|
||||||
See Also
|
See Also
|
||||||
@@ -1496,11 +1496,12 @@ class TradingAlgorithm(object):
|
|||||||
:class:`zipline.finance.commission.PerTrade`
|
:class:`zipline.finance.commission.PerTrade`
|
||||||
:class:`zipline.finance.commission.PerDollar`
|
:class:`zipline.finance.commission.PerDollar`
|
||||||
"""
|
"""
|
||||||
if not isinstance(commission, (PerShare, PerTrade, PerDollar)):
|
if not isinstance(commission, CommissionModel):
|
||||||
raise UnsupportedCommissionModel()
|
raise UnsupportedCommissionModel()
|
||||||
|
|
||||||
if self.initialized:
|
if self.initialized:
|
||||||
raise SetCommissionPostInit()
|
raise SetCommissionPostInit()
|
||||||
|
|
||||||
self.blotter.commission = commission
|
self.blotter.commission = commission
|
||||||
|
|
||||||
@api_method
|
@api_method
|
||||||
|
|||||||
+14
-18
@@ -12,17 +12,13 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
import math
|
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
from zipline.finance.order import Order
|
from zipline.finance.order import Order
|
||||||
|
|
||||||
from zipline.finance.slippage import VolumeShareSlippage
|
from zipline.finance.slippage import VolumeShareSlippage
|
||||||
from zipline.finance.commission import PerShare
|
from zipline.finance.commission import PerShare
|
||||||
from zipline.finance.cancel_policy import NeverCancel
|
from zipline.finance.cancel_policy import NeverCancel
|
||||||
@@ -293,9 +289,7 @@ class Blotter(object):
|
|||||||
commissions_list: List
|
commissions_list: List
|
||||||
commissions_list: list of commissions resulting from filling the
|
commissions_list: list of commissions resulting from filling the
|
||||||
open orders. A commission is an object with "sid" and "cost"
|
open orders. A commission is an object with "sid" and "cost"
|
||||||
parameters. If there are no commission events (because, for
|
parameters.
|
||||||
example, Zipline models the commission cost into the fill price
|
|
||||||
of the transaction), then this is None.
|
|
||||||
|
|
||||||
closed_orders: List
|
closed_orders: List
|
||||||
closed_orders: list of all the orders that have filled.
|
closed_orders: list of all the orders that have filled.
|
||||||
@@ -303,6 +297,7 @@ class Blotter(object):
|
|||||||
|
|
||||||
closed_orders = []
|
closed_orders = []
|
||||||
transactions = []
|
transactions = []
|
||||||
|
commissions = []
|
||||||
|
|
||||||
if self.open_orders:
|
if self.open_orders:
|
||||||
assets = self.asset_finder.retrieve_all(self.open_orders)
|
assets = self.asset_finder.retrieve_all(self.open_orders)
|
||||||
@@ -313,18 +308,19 @@ class Blotter(object):
|
|||||||
|
|
||||||
for order, txn in \
|
for order, txn in \
|
||||||
self.slippage_func(bar_data, asset, asset_orders):
|
self.slippage_func(bar_data, asset, asset_orders):
|
||||||
direction = math.copysign(1, txn.amount)
|
additional_commission = \
|
||||||
per_share, total_commission = \
|
self.commission.calculate(order, txn)
|
||||||
self.commission.calculate(txn)
|
|
||||||
txn.price += per_share * direction
|
if additional_commission > 0:
|
||||||
txn.commission = total_commission
|
commissions.append({
|
||||||
|
"sid": order.sid,
|
||||||
|
"order": order,
|
||||||
|
"cost": additional_commission
|
||||||
|
})
|
||||||
|
|
||||||
order.filled += txn.amount
|
order.filled += txn.amount
|
||||||
|
order.commission += additional_commission
|
||||||
|
|
||||||
if txn.commission is not None:
|
|
||||||
order.commission = (order.commission or 0.0) + \
|
|
||||||
txn.commission
|
|
||||||
|
|
||||||
txn.dt = pd.Timestamp(txn.dt, tz='UTC')
|
|
||||||
order.dt = txn.dt
|
order.dt = txn.dt
|
||||||
|
|
||||||
transactions.append(txn)
|
transactions.append(txn)
|
||||||
@@ -332,7 +328,7 @@ class Blotter(object):
|
|||||||
if not order.open:
|
if not order.open:
|
||||||
closed_orders.append(order)
|
closed_orders.append(order)
|
||||||
|
|
||||||
return transactions, None, closed_orders
|
return transactions, commissions, closed_orders
|
||||||
|
|
||||||
def prune_orders(self, closed_orders):
|
def prune_orders(self, closed_orders):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#
|
#
|
||||||
# Copyright 2014 Quantopian, Inc.
|
# Copyright 2016 Quantopian, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@@ -12,113 +12,160 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import abc
|
||||||
|
|
||||||
|
from abc import abstractmethod
|
||||||
|
from six import with_metaclass
|
||||||
|
|
||||||
DEFAULT_PER_SHARE_COST = 0.0075 # 0.75 cents per share
|
DEFAULT_PER_SHARE_COST = 0.0075 # 0.75 cents per share
|
||||||
DEFAULT_MINIMUM_COST_PER_TRADE = 1.0 # $1 per trade
|
DEFAULT_MINIMUM_COST_PER_TRADE = 1.0 # $1 per trade
|
||||||
|
|
||||||
|
|
||||||
class PerShare(object):
|
class CommissionModel(with_metaclass(abc.ABCMeta)):
|
||||||
"""Calculates a commission for a transaction based on a per
|
"""
|
||||||
share cost with an optional minimum cost per trade.
|
Abstract commission model interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def calculate(self, order, transaction):
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
order: the order whose transaction we are processing. Its `commission`
|
||||||
|
field is up to date with how much commission has been attributed
|
||||||
|
to this order so far.
|
||||||
|
|
||||||
|
transaction: the transaction we are processing.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float: The additional commission, in dollars, that we should attribute
|
||||||
|
to this order.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PerShare(CommissionModel):
|
||||||
|
"""
|
||||||
|
Calculates a commission for a transaction based on a per share cost with
|
||||||
|
an optional minimum cost per trade.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
cost : float, optional
|
cost : float, optional
|
||||||
The amount of commissions paid per share traded.
|
The amount of commissions paid per share traded.
|
||||||
min_trade_cost : optional
|
min_trade_cost : optional
|
||||||
The minimum amount of commisions paid per trade.
|
The minimum amount of commissions paid per trade.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
cost=DEFAULT_PER_SHARE_COST,
|
cost=DEFAULT_PER_SHARE_COST,
|
||||||
min_trade_cost=DEFAULT_MINIMUM_COST_PER_TRADE):
|
min_trade_cost=DEFAULT_MINIMUM_COST_PER_TRADE):
|
||||||
"""
|
self.cost_per_share = float(cost)
|
||||||
Cost parameter is the cost of a trade per-share. $0.03
|
self.min_trade_cost = min_trade_cost
|
||||||
means three cents per share, which is a very conservative
|
|
||||||
(quite high) for per share costs.
|
|
||||||
min_trade_cost parameter is the minimum trade cost
|
|
||||||
regardless of the number of shares traded (e.g. $1.00).
|
|
||||||
"""
|
|
||||||
self.cost = float(cost)
|
|
||||||
self.min_trade_cost = None if min_trade_cost is None\
|
|
||||||
else float(min_trade_cost)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{class_name}(cost={cost}, min trade cost={min_trade_cost})"\
|
return "{class_name}(cost_per_share={cost}, " \
|
||||||
|
"min trade cost={min_trade_cost})" \
|
||||||
.format(class_name=self.__class__.__name__,
|
.format(class_name=self.__class__.__name__,
|
||||||
cost=self.cost,
|
cost_per_share=self.cost_per_share,
|
||||||
min_trade_cost=self.min_trade_cost)
|
min_trade_cost=self.min_trade_cost)
|
||||||
|
|
||||||
def calculate(self, transaction):
|
def calculate(self, order, transaction):
|
||||||
"""
|
"""
|
||||||
returns a tuple of:
|
If there is a minimum commission:
|
||||||
(per share commission, total transaction commission)
|
If the order hasn't had a commission paid yet, pay the minimum
|
||||||
|
commission.
|
||||||
|
|
||||||
|
If the order has paid a commission, start paying additional
|
||||||
|
commission once the minimum commission has been reached.
|
||||||
|
|
||||||
|
If there is no minimum commission:
|
||||||
|
Pay commission based on number of shares in the transaction.
|
||||||
"""
|
"""
|
||||||
commission = abs(transaction.amount * self.cost)
|
additional_commission = abs(transaction.amount * self.cost_per_share)
|
||||||
|
|
||||||
if self.min_trade_cost is None:
|
if self.min_trade_cost is None:
|
||||||
return self.cost, commission
|
# no min trade cost, so just return the cost for this transaction
|
||||||
|
return additional_commission
|
||||||
|
|
||||||
|
if order.commission == 0:
|
||||||
|
# no commission paid yet, pay at least the minimum
|
||||||
|
return max(self.min_trade_cost, additional_commission)
|
||||||
else:
|
else:
|
||||||
commission = max(commission, self.min_trade_cost)
|
# we've already paid some commission, so figure out how much we
|
||||||
return abs(commission / transaction.amount), commission
|
# would be paying if we only counted per share.
|
||||||
|
per_share_total = \
|
||||||
|
(order.filled * self.cost_per_share) + additional_commission
|
||||||
|
|
||||||
|
if per_share_total < self.min_trade_cost:
|
||||||
|
# if we haven't hit the minimum threshold yet, don't pay
|
||||||
|
# additional commission
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
# we've exceeded the threshold, so pay more commission.
|
||||||
|
return per_share_total - order.commission
|
||||||
|
|
||||||
|
|
||||||
class PerTrade(object):
|
class PerTrade(CommissionModel):
|
||||||
"""Calculates a commission for a transaction based on a per
|
"""
|
||||||
trade cost.
|
Calculates a commission for a transaction based on a per trade cost.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
cost : float, optional
|
cost : float, optional
|
||||||
The flat amount of commisions paid per trade.
|
The flat amount of commissions paid per trade.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cost=DEFAULT_MINIMUM_COST_PER_TRADE):
|
def __init__(self, cost=DEFAULT_MINIMUM_COST_PER_TRADE):
|
||||||
"""
|
"""
|
||||||
Cost parameter is the cost of a trade, regardless of
|
Cost parameter is the cost of a trade, regardless of share count.
|
||||||
share count. $5.00 per trade is fairly typical of
|
$5.00 per trade is fairly typical of discount brokers.
|
||||||
discount brokers.
|
|
||||||
"""
|
"""
|
||||||
# Cost needs to be floating point so that calculation using division
|
# Cost needs to be floating point so that calculation using division
|
||||||
# logic does not floor to an integer.
|
# logic does not floor to an integer.
|
||||||
self.cost = float(cost)
|
self.cost = float(cost)
|
||||||
|
|
||||||
def calculate(self, transaction):
|
def calculate(self, order, transaction):
|
||||||
"""
|
"""
|
||||||
returns a tuple of:
|
If the order hasn't had a commission paid yet, pay the fixed
|
||||||
(per share commission, total transaction commission)
|
commission.
|
||||||
"""
|
"""
|
||||||
if transaction.amount == 0:
|
if order.commission == 0:
|
||||||
return 0.0, 0.0
|
# if the order hasn't had a commission attributed to it yet,
|
||||||
|
# that's what we need to pay.
|
||||||
return abs(self.cost / transaction.amount), self.cost
|
return self.cost
|
||||||
|
else:
|
||||||
|
# order has already had commission attributed, so no more
|
||||||
|
# commission.
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
class PerDollar(object):
|
class PerDollar(CommissionModel):
|
||||||
"""Calculates a commission for a transaction based on a per
|
"""
|
||||||
dollar cost.
|
Calculates a commission for a transaction based on a per trade cost.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
cost : float, optional
|
cost : float
|
||||||
The amount of commissions paid per dollar traded.
|
The flat amount of commissions paid per trade.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cost=0.0015):
|
def __init__(self, cost=0.0015):
|
||||||
"""
|
"""
|
||||||
Cost parameter is the cost of a trade per-dollar. 0.0015
|
Cost parameter is the cost of a trade per-dollar. 0.0015
|
||||||
on $1 million means $1,500 commission (=1,000,000 x 0.0015)
|
on $1 million means $1,500 commission (=1M * 0.0015)
|
||||||
"""
|
"""
|
||||||
self.cost = float(cost)
|
self.cost_per_dollar = float(cost)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{class_name}(cost={cost})".format(
|
return "{class_name}(cost_per_dollar={cost})".format(
|
||||||
class_name=self.__class__.__name__,
|
class_name=self.__class__.__name__,
|
||||||
cost=self.cost)
|
cost=self.cost_per_dollar)
|
||||||
|
|
||||||
def calculate(self, transaction):
|
def calculate(self, order, transaction):
|
||||||
"""
|
"""
|
||||||
returns a tuple of:
|
Pay commission based on dollar value of shares.
|
||||||
(per share commission, total transaction commission)
|
|
||||||
"""
|
"""
|
||||||
cost_per_share = transaction.price * self.cost
|
cost_per_share = transaction.price * self.cost_per_dollar
|
||||||
return cost_per_share, abs(transaction.amount) * cost_per_share
|
return abs(transaction.amount) * cost_per_share
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class Order(object):
|
|||||||
"limit_reached", "direction", "type", "broker_order_id"]
|
"limit_reached", "direction", "type", "broker_order_id"]
|
||||||
|
|
||||||
def __init__(self, dt, sid, amount, stop=None, limit=None, filled=0,
|
def __init__(self, dt, sid, amount, stop=None, limit=None, filled=0,
|
||||||
commission=None, id=None):
|
commission=0, id=None):
|
||||||
"""
|
"""
|
||||||
@dt - datetime.datetime that the order was placed
|
@dt - datetime.datetime that the order was placed
|
||||||
@sid - asset for the order. called sid for historical reasons.
|
@sid - asset for the order. called sid for historical reasons.
|
||||||
|
|||||||
Reference in New Issue
Block a user