Coverage for django_napse/api/fleets/serializers/fleet_serializers.py: 35%
128 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-12 13:49 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-12 13:49 +0000
1from rest_framework import serializers
3from django_napse.api.bots.serializers import BotSerializer
4from django_napse.api.fleets.serializers.cluster_serialisers import ClusterFormatterSerializer
5from django_napse.core.models import ConnectionWallet, Fleet, FleetHistory, NapseSpace, SpaceWallet
8class FleetSerializer(serializers.ModelSerializer):
9 value = serializers.SerializerMethodField(read_only=True)
10 bot_count = serializers.SerializerMethodField(read_only=True)
11 clusters = ClusterFormatterSerializer(
12 write_only=True,
13 many=True,
14 required=True,
15 )
16 space = serializers.UUIDField(write_only=True, required=True)
17 delta = serializers.SerializerMethodField(read_only=True)
18 exchange_account = serializers.SerializerMethodField(read_only=True)
20 class Meta:
21 model = Fleet
22 fields = [
23 "name",
24 # write-only
25 "clusters",
26 "space",
27 # read-only
28 "uuid",
29 "value",
30 "bot_count",
31 "delta",
32 "exchange_account",
33 ]
34 read_only_fields = [
35 "uuid",
36 "exchange_account",
37 ]
39 def __init__(self, instance=None, data=serializers.empty, space=None, **kwargs):
40 self.space = space
41 super().__init__(instance=instance, data=data, **kwargs)
43 def get_value(self, instance):
44 if self.space is None:
45 return instance.value
46 return instance.space_frame_value(space=self.space)
48 def get_bot_count(self, instance):
49 return instance.bot_count(space=self.space)
51 def get_delta(self, instance) -> float:
52 """Delta on the last 30 days."""
53 try:
54 history = FleetHistory.objects.get(owner=instance)
55 except FleetHistory.DoesNotExist:
56 return 0
57 return history.get_delta()
59 def get_exchange_account(self, instance):
60 return instance.exchange_account.uuid
62 def validate(self, attrs):
63 data = super().validate(attrs)
65 try:
66 self.space = NapseSpace.objects.get(uuid=attrs.pop("space"))
67 print("get space", self.space)
68 except NapseSpace.DoesNotExist:
69 error_msg: str = "Space does not exist."
70 raise serializers.ValidationError(error_msg) from None
72 data["exchange_account"] = self.space.exchange_account
73 return data
75 def to_representation(self, instance):
76 data = super().to_representation(instance)
77 if self.space is not None:
78 data["space"] = self.space.uuid
80 return data
82 def create(self, validated_data):
83 return Fleet.objects.create(**validated_data)
86class FleetDetailSerializer(serializers.ModelSerializer):
87 wallet = serializers.SerializerMethodField(read_only=True)
88 statistics = serializers.SerializerMethodField(read_only=True)
89 bots = BotSerializer(many=True, read_only=True)
91 class Meta:
92 model = Fleet
93 fields = [
94 "uuid",
95 "name",
96 "created_at",
97 "statistics",
98 "wallet",
99 "bots",
100 "exchange_account",
101 ]
103 def __init__(self, instance=None, data=serializers.empty, space=None, **kwargs):
104 self.space = space
105 super().__init__(instance=instance, data=data, **kwargs)
107 def get_statistics(self, instance):
108 return instance.get_stats()
110 def get_wallet(self, instance):
111 # Method not tested, high chance of being buggy
112 def _search_ticker(ticker: str, merged_wallet) -> int | None:
113 """Return the index of the currency in the list if found, None otherwise."""
114 for i, currency in enumerate(merged_wallet):
115 if currency.get("ticker").ticker == ticker:
116 return i
117 return None
119 def _update_merged_wallet(index: int, currency: str, merged_wallet) -> None:
120 """Update the merged wallet with the new currency."""
121 if index is None:
122 merged_wallet.append(
123 {
124 "ticker": currency.ticker,
125 "amount": currency.amount,
126 "mbp": currency.mbp,
127 },
128 )
129 else:
130 merged_wallet[index]["amount"] += currency.amount
132 if self.space is None:
133 return None
134 wallets = ConnectionWallet.objects.filter(owner__owner=self.space.wallet, owner__bot__in=instance.bots)
135 merged_wallet: list[dict[str, str | float]] = []
137 for wallet in wallets:
138 for currency in wallet.currencies.all():
139 index = _search_ticker(currency.ticker, merged_wallet)
140 _update_merged_wallet(index, currency, merged_wallet)
142 return merged_wallet
144 def to_representation(self, instance):
145 data = super().to_representation(instance)
146 if self.space is not None:
147 data["space"] = self.space.uuid
148 return data
150 def save(self, **kwargs):
151 error_msg: str = "Impossible to update a fleet through the detail serializer."
152 raise serializers.ValidationError(error_msg)
155class FleetMoneyFlowSerializer(serializers.Serializer):
156 amount = serializers.FloatField(write_only=True, required=True)
157 ticker = serializers.CharField(write_only=True, required=True)
159 def __init__(self, side, instance=None, data=serializers.empty, space=None, **kwargs):
160 self.side = side
161 self.space = space
162 super().__init__(instance=instance, data=data, **kwargs)
164 def _invest_validate(self, attrs):
165 if self.space.testing:
166 space_wallet = self.space.wallet
167 try:
168 currency: SpaceWallet = space_wallet.currencies.get(ticker=attrs["ticker"])
169 except SpaceWallet.DoesNotExist:
170 error_msg: str = f"{attrs['ticker']} does not exist in space ({self.space.name})."
171 raise serializers.ValidationError(error_msg) from None
173 if currency.amount < attrs["amount"]:
174 error_msg: str = f"Not enough {currency.ticker} in the wallet."
175 raise serializers.ValidationError(error_msg)
177 return attrs
179 error_msg: str = "Real invest is not implemented yet."
180 raise NotImplementedError(error_msg)
182 def _withdraw_validate(self, attrs):
183 if self.space.testing:
184 error_msg: str = "Withdraw is not implemented yet."
185 raise NotImplementedError(error_msg)
187 error_msg: str = "Real withdraw is not implemented yet."
188 raise NotImplementedError(error_msg)
190 def validate(self, attrs):
191 """Check if the wallet has enough money to invest."""
192 match self.side.upper():
193 case "INVEST":
194 return self._invest_validate(attrs)
195 case "WITHDRAW":
196 return self._withdraw_validate(attrs)
197 case _:
198 error_msg: str = "Invalid side."
199 raise ValueError(error_msg)
201 def save(self, **kwargs):
202 """Make the transaction."""
203 amount = self.validated_data["amount"]
204 ticker = self.validated_data["ticker"]
205 self.instance.invest(self.space, amount, ticker)