11 changed files with 309 additions and 19 deletions
@ -0,0 +1,91 @@ |
|||
from typing import List, Dict, Union |
|||
|
|||
import pandas as pd # type: ignore |
|||
|
|||
from ..orders import Long, Short |
|||
from ..interfaces import Strategy, Order, PTFState, Indicator |
|||
from ..errors import StrategyInitError |
|||
|
|||
|
|||
class Prop(Strategy): |
|||
"""Proportional repartion from the indicator.""" |
|||
|
|||
def __init__(self, indicators: Dict[str, Indicator] = None, max_delta: float = 10): |
|||
"""Init the class. |
|||
|
|||
Only have one indicator. |
|||
The max delta is the maximum difference between the actual and the desired state. |
|||
""" |
|||
if not indicators or len(indicators) != 1: |
|||
raise StrategyInitError( |
|||
"This strat must be initialized with one indicator." |
|||
) |
|||
super().__init__(indicators=indicators) |
|||
self.indicator = list(indicators.keys())[0] |
|||
self.max_delta = max_delta |
|||
|
|||
def filter_order(self, order: Union[Long, Short]) -> bool: |
|||
"""Return if the order value is high enough.""" |
|||
return order.amount_usd > self.max_delta |
|||
|
|||
def execute( |
|||
self, data: pd.DataFrame, indicators_results: pd.DataFrame, state: PTFState |
|||
) -> List[Order]: |
|||
"""Just hold the value [to_hold]. |
|||
|
|||
Args: |
|||
data (DataFrame): The Data broker output. |
|||
For each time and each stock give (high, low, open, close). |
|||
|
|||
indicators_results (DataFrame): Indicator-Stock valuated float. |
|||
For each indicator and each stock give -1 if realy bad and +1 if realy good. |
|||
|
|||
Returns: |
|||
List[Order]: A list of orders to execute. |
|||
""" |
|||
orders: List[Union[Long, Short]] = [] |
|||
conversion_rate = data.loc[data.index[-1][0]].close.to_dict() |
|||
results = indicators_results.T[self.indicator] |
|||
stock_to_sell = [] |
|||
|
|||
# Remove all action that the indicator place as negative. |
|||
for stock in results[results < 0].index: |
|||
order = Short(stock, state.stocks[stock], conversion_rate[stock]) |
|||
if self.filter_order(order): |
|||
orders.append(order) |
|||
stock_to_sell.append(stock) |
|||
|
|||
total_money = state.balance + sum( |
|||
conversion_rate[stock_name] * amount |
|||
for stock_name, amount in state.stocks.items() |
|||
if results[stock] > 0 or stock_name in stock_to_sell |
|||
) |
|||
|
|||
# Desired state |
|||
desired_state_precent = results[results > 0] / results[results > 0].sum() |
|||
desired_state_dolards = total_money * desired_state_precent |
|||
|
|||
# Create the new buy orders from with the delta between the actual and the desired state. |
|||
for stock, amount in desired_state_dolards.items(): |
|||
if stock in state.stocks: |
|||
if amount > state.stocks[stock]: |
|||
orders.append( |
|||
Long( |
|||
stock, |
|||
amount / conversion_rate[stock] - state.stocks[stock], |
|||
conversion_rate[stock], |
|||
) |
|||
) |
|||
else: |
|||
orders.append( |
|||
Short( |
|||
stock, |
|||
state.stocks[stock] - amount / conversion_rate[stock], |
|||
conversion_rate[stock], |
|||
) |
|||
) |
|||
|
|||
# Filter low orders |
|||
orders = list(filter(self.filter_order, orders)) |
|||
|
|||
return orders # type: ignore |
@ -0,0 +1,59 @@ |
|||
"""Yoyo strategy.""" |
|||
from auto_trading.interfaces import Strategy, Order, PTFState |
|||
from auto_trading.orders import Long, Short |
|||
from typing import List, Dict, cast, Callable |
|||
|
|||
import pandas as pd # type: ignore |
|||
|
|||
|
|||
def is_positive(number: float | None) -> bool: |
|||
"""Return True if the price is positive.""" |
|||
return number is not None and number > 0 |
|||
|
|||
|
|||
class Yoyo(Strategy): |
|||
|
|||
"""A strat that buy a stock one time then sell it.""" |
|||
|
|||
def __init__(self, stock_name: str): |
|||
super().__init__() |
|||
self.stock_name = stock_name |
|||
|
|||
def execute( |
|||
self, |
|||
data: pd.DataFrame, |
|||
indicators_results: pd.DataFrame, |
|||
ptf_state: PTFState, |
|||
) -> List[Order]: |
|||
try: |
|||
market_price = ( |
|||
data.loc[data.index[-1][0]].close.to_dict().get(self.stock_name) |
|||
) |
|||
except: |
|||
return [] |
|||
if not market_price: |
|||
return [] |
|||
|
|||
todo: Order |
|||
if self.as_stocks(ptf_state.stocks): |
|||
self.logger.info("sell") |
|||
todo = self.create_sell_order(ptf_state, market_price) |
|||
else: |
|||
self.logger.info("buy") |
|||
todo = self.create_buy_order(ptf_state, market_price) |
|||
|
|||
return [todo] |
|||
|
|||
def as_stocks(self, stocks: Dict[str, float]) -> bool: |
|||
"""Check if we currently have some stocks to sell.""" |
|||
return is_positive(stocks.get(self.stock_name)) |
|||
|
|||
def create_sell_order(self, ptf: PTFState, current_price: float) -> Order: |
|||
"""Create a sell order.""" |
|||
|
|||
return Short(self.stock_name, ptf.stocks[self.stock_name], current_price) |
|||
|
|||
def create_buy_order(self, ptf: PTFState, current_price: float) -> Order: |
|||
"""Create a buy order.""" |
|||
|
|||
return Long(self.stock_name, ptf.balance / current_price, current_price) |
@ -0,0 +1,68 @@ |
|||
import pytest |
|||
from datetime import datetime |
|||
from pandas import DataFrame, Series # type: ignore |
|||
|
|||
from auto_trading.strat.prop import Prop |
|||
from auto_trading.indicators.dumb import Dumb |
|||
from auto_trading.interfaces import PTFState |
|||
from auto_trading.orders import Long, Short |
|||
from auto_trading.errors import StrategyInitError |
|||
|
|||
|
|||
date = datetime.strptime("2015-03-31", "%Y-%m-%d") |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
"max_delta, data, indicators_results, state, results", |
|||
[ |
|||
( |
|||
10, |
|||
DataFrame({"close": {(date, "GOOG"): 10, (date, "GOOGL"): 10}}), |
|||
Series({"GOOG": 1, "GOOGL": -1}), |
|||
PTFState(50, {"GOOG": 0, "GOOGL": 0}), |
|||
[Long("GOOG", price=10, amount=5)], |
|||
), |
|||
( |
|||
10, |
|||
DataFrame({"close": {(date, "GOOG"): 10, (date, "GOOGL"): 10}}), |
|||
Series({"GOOG": 1, "GOOGL": -1}), |
|||
PTFState(5, {"GOOG": 0, "GOOGL": 0}), |
|||
[], |
|||
), |
|||
( |
|||
1, |
|||
DataFrame({"close": {(date, "GOOG"): 10, (date, "GOOGL"): 10}}), |
|||
Series({"GOOG": 1, "GOOGL": -1}), |
|||
PTFState(0, {"GOOG": 0, "GOOGL": 1}), |
|||
[Short("GOOGL", price=10, amount=1), Long("GOOG", price=10, amount=1)], |
|||
), |
|||
( |
|||
20, |
|||
DataFrame( |
|||
{ |
|||
"close": { |
|||
(date, "A"): 10, |
|||
(date, "B"): 10, |
|||
(date, "C"): 10, |
|||
(date, "D"): 10, |
|||
} |
|||
} |
|||
), |
|||
Series({"A": 1, "B": -1, "C": -1, "D": -1}), |
|||
PTFState(0, {"A": 0, "B": 1, "C": 1, "D": 1}), |
|||
[], |
|||
), |
|||
], |
|||
) |
|||
def test_prop(max_delta, data, indicators_results, state, results): |
|||
strat = Prop(max_delta=max_delta, indicators={"ind": Dumb(indicators_results)}) |
|||
res = strat.run(data, state) |
|||
assert len(res) == len(results) |
|||
for result in results: |
|||
assert result in res |
|||
|
|||
|
|||
def test_not_multiples_indicators(): |
|||
ind = Dumb(Series({"GOOG": 1, "GOOGL": -1})) |
|||
with pytest.raises(StrategyInitError): |
|||
Prop(indicators={"one": ind, "two": ind}) |
@ -0,0 +1,32 @@ |
|||
import pytest |
|||
from datetime import datetime |
|||
from pandas import DataFrame # type: ignore |
|||
|
|||
from auto_trading.strat.yoyo import Yoyo |
|||
from auto_trading.interfaces import PTFState as State |
|||
from auto_trading.orders import Long, Short |
|||
|
|||
date = datetime.strptime("2015-03-31", "%Y-%m-%d") |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
"data, state, output", |
|||
[ |
|||
(DataFrame(), State(balance=0, stocks={}), None), |
|||
(DataFrame({"close": {(date, "AAPL"): 0}}), State(balance=0, stocks={}), None), |
|||
( |
|||
DataFrame({"close": {(date, "AAPL"): 10}}), |
|||
State(balance=0, stocks={"AAPL": 1}), |
|||
Short, |
|||
), |
|||
(DataFrame({"close": {(date, "AAPL"): 10}}), State(balance=10, stocks={}), Long), |
|||
], |
|||
) |
|||
def test_yoyo(data, state, output): |
|||
strat = Yoyo("AAPL") |
|||
res = strat.run(data, state) |
|||
if output is None: |
|||
assert len(res) == 0 |
|||
else: |
|||
assert len(res) == 1 |
|||
assert isinstance(res[0], output) |
Loading…
Reference in new issue